mirror of
https://github.com/cloudflare/cloudflared
synced 2026-06-08 13:33:07 +00:00
Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 420e80ea50 | |||
| 337591b2bb | |||
| fa6bcdad04 | |||
| ee87c43eb9 | |||
| bccc58b54d | |||
| 3da1c25471 | |||
| 7d0a271000 | |||
| 102631d98d | |||
| 6c3d2fc339 | |||
| 1d79831651 | |||
| 0458ad41dd | |||
| b9453b84bb | |||
| ab81ff8bfb | |||
| f2339a7244 | |||
| 978e01f77e | |||
| 1275930f99 | |||
| 8e9091cc48 | |||
| 76add5ca77 | |||
| e8407848ec | |||
| 29d809535e | |||
| cc1c6d9abc | |||
| 69b28e358c | |||
| 5ed3d4e29a | |||
| 4f468b8a5d | |||
| e3aad7799e | |||
| cc8aa0efb5 | |||
| 475939a77f | |||
| 4ccef23dbc | |||
| 2b0d704777 | |||
| ee80e55833 | |||
| 2345720b2b | |||
| d714a62bd3 | |||
| 7d4afd4ae0 | |||
| 056693c814 | |||
| 73d948bc32 | |||
| 5e6f606f4e | |||
| 919227fc91 | |||
| b8ba5b444c | |||
| baed5f4eea | |||
| 08a8101308 | |||
| a2a4b06eb4 | |||
| ec509e114a | |||
| 7bc2462e36 | |||
| 92f647d45c | |||
| b2ac885370 | |||
| 2c480a72db | |||
| 32739e9f98 | |||
| 7ce2bb8b2f | |||
| 6f78ccde04 | |||
| 26a7b59f6f | |||
| 4b6437cc60 | |||
| f7fd4ea71c | |||
| 7bcab138c5 | |||
| f758361730 | |||
| fa2234d639 | |||
| 99d4e48656 | |||
| 0180b6d733 | |||
| 9ef6191515 | |||
| 2cf43abe8c | |||
| 46c147a1b2 | |||
| 1e71202c89 | |||
| 8250708b37 | |||
| 7499e5fa00 | |||
| e8fe34773c | |||
| 3254d08173 | |||
| d68ad89159 | |||
| f3244db861 | |||
| d22cb4a6ca | |||
| 8f0498f66a | |||
| a97233bb3e | |||
| 775c2bc93e | |||
| e4278bab97 | |||
| f81b0ee9e8 | |||
| 8a07a900fd | |||
| d727d3ade6 | |||
| 7a6ab54fcb | |||
| def8f57dbc | |||
| 9cde11f8e0 | |||
| d1a4710aa2 | |||
| 0dc3428424 | |||
| b07b8b4d4b | |||
| d433a0fa54 | |||
| 2f05f969e2 | |||
| 14002e44e1 | |||
| b12272529f | |||
| 377a9a8d27 | |||
| a0f6eb9d5e | |||
| 12302ba1bf | |||
| 317a7ea7e5 | |||
| 636ec75010 | |||
| 98deb95eae | |||
| c0f85ab85b | |||
| c5d1662244 | |||
| 8fd6074d67 | |||
| 7e6fc49979 |
@@ -2,6 +2,8 @@ images:
|
||||
- name: cloudflared
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
versions:
|
||||
- latest
|
||||
registries:
|
||||
- name: docker.io/cloudflare
|
||||
user: env:DOCKER_USER
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: "Bug report \U0001F41B"
|
||||
name: "\U0001F41B Bug report"
|
||||
about: Create a report to help us improve cloudflared
|
||||
title: ''
|
||||
labels: awaiting reply, bug
|
||||
assignees: ''
|
||||
title: "\U0001F41B"
|
||||
labels: 'Priority: Normal, Type: Bug'
|
||||
assignees: abelinkinbio
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: "\U0001F4DD Documentation"
|
||||
about: Request new or updated documentation for cloudflared
|
||||
title: "\U0001F4DD"
|
||||
labels: 'Priority: Normal, Type: Documentation'
|
||||
assignees: abelinkinbio, ranbel
|
||||
|
||||
---
|
||||
|
||||
**Available Documentation**
|
||||
A link to the documentation that is available today and the areas which could be improved.
|
||||
|
||||
**Suggested Documentation**
|
||||
A clear and concise description of the documentation, tutorial, or guide that should be added.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the documentation request here.
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: "Feature request \U0001F4A1"
|
||||
name: "\U0001F4A1 Feature request"
|
||||
about: Suggest a feature or enhancement for cloudflared
|
||||
title: ''
|
||||
labels: awaiting reply, feature-request
|
||||
assignees: ''
|
||||
title: "\U0001F4A1"
|
||||
labels: 'Priority: Normal, Type: Feature Request'
|
||||
assignees: abelinkinbio, sssilver
|
||||
|
||||
---
|
||||
|
||||
@@ -12,8 +12,6 @@ jobs:
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Install go-sumtype
|
||||
run: go get github.com/sudarshan-reddy/go-sumtype
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Test
|
||||
|
||||
Vendored
+4
-1
@@ -19,5 +19,8 @@ eval "$(tmp/homebrew/bin/brew shellenv)"
|
||||
brew update --force --quiet
|
||||
chmod -R go-w "$(brew --prefix)/share/zsh"
|
||||
|
||||
git config --global user.name "cloudflare-warp-bot"
|
||||
git config --global user.email "warp-bot@cloudflare.com"
|
||||
|
||||
# bump formula pr
|
||||
brew bump-formula-pr cloudflared
|
||||
brew bump-formula-pr cloudflared --version="$VERSION" --no-browse
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
## 2022.4.0
|
||||
### Bug Fixes
|
||||
- `cloudflared tunnel run` no longer logs the Tunnel token or JSON credentials in clear text as those are the secret
|
||||
that allows to run the Tunnel.
|
||||
|
||||
## 2022.3.4
|
||||
### New Features
|
||||
- It is now possible to retrieve the credentials that allow to run a Tunnel in case you forgot/lost them. This is
|
||||
|
||||
@@ -6,6 +6,8 @@ ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0 \
|
||||
TARGET_GOOS=${TARGET_GOOS} \
|
||||
TARGET_GOARCH=${TARGET_GOARCH}
|
||||
|
||||
LABEL org.opencontainers.image.source="https://github.com/cloudflare/cloudflared"
|
||||
|
||||
WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||
|
||||
|
||||
@@ -40,6 +40,10 @@ ifneq ($(GO_BUILD_TAGS),)
|
||||
GO_BUILD_TAGS := -tags "$(GO_BUILD_TAGS)"
|
||||
endif
|
||||
|
||||
ifeq ($(debug), 1)
|
||||
GO_BUILD_TAGS += -gcflags="all=-N -l"
|
||||
endif
|
||||
|
||||
IMPORT_PATH := github.com/cloudflare/cloudflared
|
||||
PACKAGE_DIR := $(CURDIR)/packaging
|
||||
PREFIX := /usr
|
||||
@@ -63,6 +67,8 @@ else ifeq ($(LOCAL_ARCH),arm64)
|
||||
TARGET_ARCH ?= arm64
|
||||
else ifeq ($(shell echo $(LOCAL_ARCH) | head -c 4),armv)
|
||||
TARGET_ARCH ?= arm
|
||||
else ifeq ($(LOCAL_ARCH),s390x)
|
||||
TARGET_ARCH ?= s390x
|
||||
else
|
||||
$(error This system's architecture $(LOCAL_ARCH) isn't supported)
|
||||
endif
|
||||
@@ -259,6 +265,10 @@ github-release: cloudflared
|
||||
github-release-built-pkgs:
|
||||
python3 github_release.py --path $(PWD)/built_artifacts --release-version $(VERSION)
|
||||
|
||||
.PHONY: release-pkgs-linux
|
||||
release-pkgs-linux:
|
||||
python3 ./release_pkgs.py
|
||||
|
||||
.PHONY: github-message
|
||||
github-message:
|
||||
python3 github_message.py --release-version $(VERSION)
|
||||
@@ -283,12 +293,7 @@ quic-deps:
|
||||
.PHONY: vet
|
||||
vet:
|
||||
go vet -mod=vendor ./...
|
||||
# go get github.com/sudarshan-reddy/go-sumtype (don't do this in build directory or this will cause vendor issues)
|
||||
# Note: If you have github.com/BurntSushi/go-sumtype then you might have to use the repo above instead
|
||||
# for now because it uses an older version of golang.org/x/tools.
|
||||
which go-sumtype
|
||||
go-sumtype $$(go list -mod=vendor ./...)
|
||||
|
||||
.PHONY: goimports
|
||||
goimports:
|
||||
for d in $$(go list -mod=readonly -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc) ; do goimports -format-only -local github.com/cloudflare/cloudflared -w $$d ; done
|
||||
for d in $$(go list -mod=readonly -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc) ; do goimports -format-only -local github.com/cloudflare/cloudflared -w $$d ; done
|
||||
|
||||
@@ -43,7 +43,7 @@ Once installed, you can authenticate `cloudflared` into your Cloudflare account
|
||||
* Route traffic to that Tunnel:
|
||||
* Via public [DNS records in Cloudflare](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/routing-to-tunnel/dns)
|
||||
* Or via a public hostname guided by a [Cloudflare Load Balancer](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/routing-to-tunnel/lb)
|
||||
* Or from [WARP client private traffic](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/private-networks)
|
||||
* Or from [WARP client private traffic](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/private-net/)
|
||||
|
||||
|
||||
## TryCloudflare
|
||||
|
||||
@@ -1,3 +1,99 @@
|
||||
2022.6.2
|
||||
- 2022-06-13 TUN-6381: Write error data on QUIC stream when we fail to talk to the origin; separate logging for protocol errors vs. origin errors.
|
||||
- 2022-06-17 TUN-6414: Remove go-sumtype from cloudflared build process
|
||||
- 2022-06-01 Add Http2Origin option to force HTTP/2 origin connections
|
||||
- 2022-06-02 fix ingress rules unit test
|
||||
- 2022-06-09 Update remaining OriginRequestConfig functions for Http2Origins
|
||||
- 2022-05-31 Add image source label to docker container.
|
||||
- 2022-05-10 Warp Private Network link updated
|
||||
|
||||
2022.6.1
|
||||
- 2022-06-14 TUN-6395: Fix writing RPM repo data
|
||||
|
||||
2022.6.0
|
||||
- 2022-06-14 Revert "TUN-6010: Add component tests for --edge-ip-version"
|
||||
- 2022-06-14 Revert "TUN-6373: Add edge-ip-version to remotely pushed configuration"
|
||||
- 2022-06-14 Revert "TUN-6384: Correct duplicate connection error to fetch new IP first"
|
||||
- 2022-06-14 Revert "TUN-6007: Implement new edge discovery algorithm"
|
||||
- 2022-06-13 TUN-6385: Don't share err between acceptStream loop and per-stream goroutines
|
||||
- 2022-06-13 TUN-6384: Correct duplicate connection error to fetch new IP first
|
||||
- 2022-06-13 TUN-6373: Add edge-ip-version to remotely pushed configuration
|
||||
- 2022-06-13 TUN-6380: Enforce connect and keep-alive timeouts for TCP connections in both WARP routing and websocket based TCP proxy.
|
||||
- 2022-06-11 Update issue templates
|
||||
- 2022-06-11 Amendment to previous PR
|
||||
- 2022-06-09 TUN-6347: Add TCP stream logs with FlowID
|
||||
- 2022-06-08 TUN-6361: Add cloudflared arm builds to pkging as well
|
||||
- 2022-06-07 TUN-6357: Add connector id to ready check endpoint
|
||||
- 2022-06-07 TUN-6010: Add component tests for --edge-ip-version
|
||||
- 2022-06-06 TUN-6191: Update quic-go to v0.27.1 and with custom patch to allow keep alive period to be configurable
|
||||
- 2022-06-03 TUN-6343: Fix QUIC->HTTP2 fallback
|
||||
- 2022-06-02 TUN-6339: Add config for IPv6 support
|
||||
- 2022-06-02 TUN-6341: Fix default config value for edge-ip-version
|
||||
- 2022-06-01 TUN-6323: Add Xenial and Trusty for Ubuntu pkging
|
||||
- 2022-05-31 TUN-6210: Add cloudflared.repo to make it easy for yum installs
|
||||
- 2022-05-30 TUN-6293: Update yaml v3 to latest hotfix
|
||||
- 2022-05-20 TUN-6007: Implement new edge discovery algorithm
|
||||
2022.5.3
|
||||
- 2022-05-30 TUN-6308: Add debug logs to see if packets are sent/received from edge
|
||||
- 2022-05-30 TUN-6301: Allow to update logger used by UDP session manager
|
||||
|
||||
2022.5.2
|
||||
- 2022-05-23 TUN-6270: Import gpg keys from environment variables
|
||||
- 2022-05-24 TUN-6209: Improve feedback process if release_pkgs to deb and rpm fail
|
||||
- 2022-05-24 TUN-6280: Don't wrap qlog connection tracer for gatethering QUIC metrics since we're not writing qlog files.
|
||||
- 2022-05-25 TUN-6209: Sign RPM packages
|
||||
- 2022-05-25 TUN-6285: Upload pkg assets to repos when cloudflared is released.
|
||||
- 2022-05-24 TUN-6282: Upgrade golang to 1.17.10, go-boring to 1.17.9
|
||||
- 2022-05-26 TUN-6292: Debug builds for cloudflared
|
||||
- 2022-05-28 TUN-6304: Fixed some file permission issues
|
||||
- 2022-05-11 TUN-6197: Publish to brew core should not try to open the browser
|
||||
- 2022-05-12 TUN-5943: Add RPM support
|
||||
- 2022-05-18 TUN-6248: Fix panic in cloudflared during tracing when origin doesn't provide header map
|
||||
- 2022-05-18 TUN-6250: Add upstream response status code to tracing span attributes
|
||||
|
||||
2022.5.1
|
||||
- 2022-05-06 TUN-6146: Release_pkgs is now a generic command line script
|
||||
- 2022-05-06 TUN-6185: Fix tcpOverWSOriginService not using original scheme for String representation
|
||||
- 2022-05-05 TUN-6175: Simply debian packaging by structural upload
|
||||
- 2022-05-05 TUN-5945: Added support for Ubuntu releases
|
||||
- 2022-05-04 TUN-6054: Create and upload deb packages to R2
|
||||
- 2022-05-03 TUN-6161: Set git user/email for brew core release
|
||||
- 2022-05-03 TUN-6166: Fix mocked QUIC transport for UDP proxy manager to return expected error
|
||||
- 2022-04-27 TUN-6016: Push local managed tunnels configuration to the edge
|
||||
2022.5.0
|
||||
- 2022-05-02 TUN-6158: Update golang.org/x/crypto
|
||||
- 2022-04-20 VULN-8383 Bump yaml.v2 to yaml.v3
|
||||
- 2022-04-21 TUN-6123: For a given connection with edge, close all datagram sessions through this connection when it's closed
|
||||
- 2022-04-20 TUN-6015: Add RPC method for pushing local config
|
||||
- 2022-04-21 TUN-6130: Fix vendoring due to case sensitive typo in package
|
||||
- 2022-04-27 TUN-6142: Add tunnel details support to RPC
|
||||
- 2022-04-28 TUN-6014: Add remote config flag as default feature
|
||||
- 2022-04-12 TUN-6000: Another fix for publishing to brew core
|
||||
- 2022-04-11 TUN-5990: Add otlp span export to response header
|
||||
- 2022-04-19 TUN-6070: First connection retries other edge IPs if the error is quic timeout(likely due to firewall blocking UDP)
|
||||
- 2022-04-11 TUN-6030: Add ttfb span for origin http request
|
||||
|
||||
2022.4.1
|
||||
- 2022-04-11 TUN-6035: Reduce buffer size when proxying data
|
||||
- 2022-04-11 TUN-6038: Reduce buffer size used for proxying data
|
||||
- 2022-04-11 TUN-6043: Allow UI-managed Tunnels to fallback from QUIC but warn about that
|
||||
- 2022-04-07 TUN-6000 add version argument to bump-formula-pr
|
||||
- 2022-04-06 TUN-5989: Add in-memory otlp exporter
|
||||
|
||||
2022.4.0
|
||||
- 2022-04-01 TUN-5973: Add backoff for non-recoverable errors as well
|
||||
- 2022-04-05 TUN-5992: Use QUIC protocol for remotely managed tunnels when protocol is unspecified
|
||||
- 2022-04-06 Update Makefile
|
||||
- 2022-04-06 TUN-5995: Update prometheus to 1.12.1 to avoid vulnerabilities
|
||||
- 2022-04-07 TUN-5995: Force prometheus v1.12.1 usage
|
||||
- 2022-04-07 TUN-4130: cloudflared docker images now have a latest tag
|
||||
- 2022-03-30 TUN-5842: Fix flaky TestConcurrentUpdateAndRead by making sure resources are released
|
||||
- 2022-03-30 carrier: fix dropped errors
|
||||
- 2022-03-25 TUN-5959: tidy go.mod
|
||||
- 2022-03-25 TUN-5958: Fix release to homebrew core
|
||||
- 2022-03-28 TUN-5960: Do not log the tunnel token or json credentials
|
||||
- 2022-03-28 TUN-5956: Add timeout to session manager APIs
|
||||
|
||||
2022.3.4
|
||||
- 2022-03-22 TUN-5918: Clean up text in cloudflared tunnel --help
|
||||
- 2022-03-22 TUN-5895 run brew bump-formula-pr on release
|
||||
|
||||
@@ -55,6 +55,9 @@ func createWebsocketStream(options *StartOptions, log *zerolog.Logger) (*cfwebso
|
||||
}
|
||||
|
||||
dump, err := httputil.DumpRequest(req, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug().Msgf("Websocket request: %s", string(dump))
|
||||
|
||||
dialer := &websocket.Dialer{
|
||||
@@ -182,6 +185,9 @@ func createAccessWebSocketStream(options *StartOptions, log *zerolog.Logger) (*w
|
||||
}
|
||||
|
||||
dump, err := httputil.DumpRequest(req, false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
log.Debug().Msgf("Access Websocket request: %s", string(dump))
|
||||
|
||||
conn, resp, err := clientConnect(req, nil)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package cfio
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const defaultBufferSize = 16 * 1024
|
||||
|
||||
var bufferPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, defaultBufferSize)
|
||||
},
|
||||
}
|
||||
|
||||
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
|
||||
_, okWriteTo := src.(io.WriterTo)
|
||||
_, okReadFrom := dst.(io.ReaderFrom)
|
||||
var buffer []byte = nil
|
||||
|
||||
if !(okWriteTo || okReadFrom) {
|
||||
buffer = bufferPool.Get().([]byte)
|
||||
defer bufferPool.Put(buffer)
|
||||
}
|
||||
|
||||
return io.CopyBuffer(dst, src, buffer)
|
||||
}
|
||||
+12
-6
@@ -1,5 +1,5 @@
|
||||
pinned_go: &pinned_go go=1.17.5-1
|
||||
pinned_go_fips: &pinned_go_fips go-boring=1.17.5-1
|
||||
pinned_go: &pinned_go go=1.17.10-1
|
||||
pinned_go_fips: &pinned_go_fips go-boring=1.17.9-1
|
||||
|
||||
build_dir: &build_dir /cfsetup_build
|
||||
default-flavor: buster
|
||||
@@ -40,16 +40,22 @@ stretch: &stretch
|
||||
- libffi-dev
|
||||
- python3-setuptools
|
||||
- python3-pip
|
||||
- reprepro
|
||||
- createrepo
|
||||
pre-cache: &github_release_pkgs_pre_cache
|
||||
- wget https://github.com/sudarshan-reddy/msitools/releases/download/v0.101b/wixl -P /usr/local/bin
|
||||
- chmod a+x /usr/local/bin/wixl
|
||||
- pip3 install pynacl==1.4.0
|
||||
- pip3 install pygithub==1.55
|
||||
- pip3 install boto3==1.22.9
|
||||
- pip3 install python-gnupg==0.4.9
|
||||
post-cache:
|
||||
# build all packages (except macos and FIPS) and move them to /cfsetup/built_artifacts
|
||||
- ./build-packages.sh
|
||||
# release the packages built and moved to /cfsetup/built_artifacts
|
||||
- make github-release-built-pkgs
|
||||
# publish packages to linux repos
|
||||
- make release-pkgs-linux
|
||||
# handle FIPS separately so that we built with gofips compiler
|
||||
github-fips-release-pkgs:
|
||||
build_dir: *build_dir
|
||||
@@ -147,7 +153,6 @@ stretch: &stretch
|
||||
- gotest-to-teamcity
|
||||
pre-cache: &test_pre_cache
|
||||
- go get golang.org/x/tools/cmd/goimports
|
||||
- go get github.com/sudarshan-reddy/go-sumtype@v0.0.0-20210827105221-82eca7e5abb1
|
||||
post-cache:
|
||||
- export GOOS=linux
|
||||
- export GOARCH=amd64
|
||||
@@ -193,6 +198,7 @@ stretch: &stretch
|
||||
- openssh-client
|
||||
- s3cmd
|
||||
- jq
|
||||
- build-essential
|
||||
post-cache:
|
||||
- .teamcity/update-homebrew.sh
|
||||
- .teamcity/update-homebrew-core.sh
|
||||
@@ -248,10 +254,10 @@ centos-7:
|
||||
pre-cache:
|
||||
- yum install -y fakeroot
|
||||
- yum upgrade -y binutils-2.27-44.base.el7.x86_64
|
||||
- wget https://go.dev/dl/go1.17.5.linux-amd64.tar.gz -P /tmp/
|
||||
- tar -C /usr/local -xzf /tmp/go1.17.5.linux-amd64.tar.gz
|
||||
- wget https://go.dev/dl/go1.17.10.linux-amd64.tar.gz -P /tmp/
|
||||
- tar -C /usr/local -xzf /tmp/go1.17.10.linux-amd64.tar.gz
|
||||
post-cache:
|
||||
- export PATH=$PATH:/usr/local/go/bin
|
||||
- export GOOS=linux
|
||||
- export GOARCH=amd64
|
||||
- make publish-rpm
|
||||
- make publish-rpm
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/metrics"
|
||||
"github.com/cloudflare/cloudflared/overwatch"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
"github.com/cloudflare/cloudflared/watcher"
|
||||
)
|
||||
|
||||
@@ -86,6 +87,7 @@ func main() {
|
||||
tunnel.Init(bInfo, graceShutdownC) // we need this to support the tunnel sub command...
|
||||
access.Init(graceShutdownC)
|
||||
updater.Init(Version)
|
||||
tracing.Init(Version)
|
||||
runApp(app, graceShutdownC)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/coreos/go-systemd/daemon"
|
||||
"github.com/facebookgo/grace/gracenet"
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/google/uuid"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
@@ -343,13 +344,21 @@ func StartServer(
|
||||
observer.SendURL(quickTunnelURL)
|
||||
}
|
||||
|
||||
tunnelConfig, dynamicConfig, err := prepareTunnelConfig(c, info, log, logTransport, observer, namedTunnel)
|
||||
tunnelConfig, orchestratorConfig, err := prepareTunnelConfig(c, info, log, logTransport, observer, namedTunnel)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Couldn't start tunnel")
|
||||
return err
|
||||
}
|
||||
var clientID uuid.UUID
|
||||
if tunnelConfig.NamedTunnel != nil {
|
||||
clientID, err = uuid.FromBytes(tunnelConfig.NamedTunnel.Client.ClientID)
|
||||
if err != nil {
|
||||
// set to nil for classic tunnels
|
||||
clientID = uuid.Nil
|
||||
}
|
||||
}
|
||||
|
||||
orchestrator, err := orchestration.NewOrchestrator(ctx, dynamicConfig, tunnelConfig.Tags, tunnelConfig.Log)
|
||||
orchestrator, err := orchestration.NewOrchestrator(ctx, orchestratorConfig, tunnelConfig.Tags, tunnelConfig.Log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -363,7 +372,7 @@ func StartServer(
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
readinessServer := metrics.NewReadyServer(log)
|
||||
readinessServer := metrics.NewReadyServer(log, clientID)
|
||||
observer.RegisterSink(readinessServer)
|
||||
errC <- metrics.ServeMetrics(metricsListener, ctx.Done(), readinessServer, quickTunnelURL, orchestrator, log)
|
||||
}()
|
||||
@@ -388,7 +397,7 @@ func StartServer(
|
||||
info.Version(),
|
||||
hostname,
|
||||
metricsListener.Addr().String(),
|
||||
dynamicConfig.Ingress,
|
||||
orchestratorConfig.Ingress,
|
||||
tunnelConfig.HAConnections,
|
||||
)
|
||||
app := tunnelUI.Launch(ctx, log, logTransport)
|
||||
@@ -514,6 +523,13 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
||||
Usage: "Cloudflare Edge region to connect to. Omit or set to empty to connect to the global region.",
|
||||
EnvVars: []string{"TUNNEL_REGION"},
|
||||
}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{
|
||||
Name: "edge-ip-version",
|
||||
Usage: "Cloudflare Edge ip address version to connect with. {4, 6, auto}",
|
||||
EnvVars: []string{"TUNNEL_EDGE_IP_VERSION"},
|
||||
Value: "4",
|
||||
Hidden: true,
|
||||
}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{
|
||||
Name: tlsconfig.CaCertFlag,
|
||||
Usage: "Certificate Authority authenticating connections with Cloudflare's edge network.",
|
||||
@@ -821,6 +837,13 @@ func configureProxyFlags(shouldHide bool) []cli.Flag {
|
||||
EnvVars: []string{"TUNNEL_NO_CHUNKED_ENCODING"},
|
||||
Hidden: shouldHide,
|
||||
}),
|
||||
altsrc.NewBoolFlag(&cli.BoolFlag{
|
||||
Name: ingress.Http2OriginFlag,
|
||||
Usage: "Enables HTTP/2 origin servers.",
|
||||
EnvVars: []string{"TUNNEL_ORIGIN_ENABLE_HTTP2"},
|
||||
Hidden: shouldHide,
|
||||
Value: false,
|
||||
}),
|
||||
}
|
||||
return append(flags, sshFlags(shouldHide)...)
|
||||
}
|
||||
|
||||
@@ -14,9 +14,11 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/altsrc"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
@@ -31,14 +33,19 @@ import (
|
||||
)
|
||||
|
||||
const LogFieldOriginCertPath = "originCertPath"
|
||||
const secretValue = "*****"
|
||||
|
||||
var (
|
||||
developerPortal = "https://developers.cloudflare.com/argo-tunnel"
|
||||
quickStartUrl = developerPortal + "/quickstart/quickstart/"
|
||||
serviceUrl = developerPortal + "/reference/service/"
|
||||
argumentsUrl = developerPortal + "/reference/arguments/"
|
||||
|
||||
LogFieldHostname = "hostname"
|
||||
|
||||
secretFlags = [2]*altsrc.StringFlag{credentialsContentsFlag, tunnelTokenFlag}
|
||||
defaultFeatures = []string{supervisor.FeatureAllowRemoteConfig, supervisor.FeatureSerializedHeaders}
|
||||
|
||||
configFlags = []string{"autoupdate-freq", "no-autoupdate", "retries", "protocol", "loglevel", "transport-loglevel", "origincert", "metrics", "metrics-update-freq"}
|
||||
)
|
||||
|
||||
// returns the first path that contains a cert.pem file. If none of the DefaultConfigSearchDirectories
|
||||
@@ -65,7 +72,11 @@ func generateRandomClientID(log *zerolog.Logger) (string, error) {
|
||||
func logClientOptions(c *cli.Context, log *zerolog.Logger) {
|
||||
flags := make(map[string]interface{})
|
||||
for _, flag := range c.FlagNames() {
|
||||
flags[flag] = c.Generic(flag)
|
||||
if isSecretFlag(flag) {
|
||||
flags[flag] = secretValue
|
||||
} else {
|
||||
flags[flag] = c.Generic(flag)
|
||||
}
|
||||
}
|
||||
|
||||
if len(flags) > 0 {
|
||||
@@ -79,7 +90,11 @@ func logClientOptions(c *cli.Context, log *zerolog.Logger) {
|
||||
if strings.Contains(env, "TUNNEL_") {
|
||||
vars := strings.Split(env, "=")
|
||||
if len(vars) == 2 {
|
||||
envs[vars[0]] = vars[1]
|
||||
if isSecretEnvVar(vars[0]) {
|
||||
envs[vars[0]] = secretValue
|
||||
} else {
|
||||
envs[vars[0]] = vars[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,6 +103,26 @@ func logClientOptions(c *cli.Context, log *zerolog.Logger) {
|
||||
}
|
||||
}
|
||||
|
||||
func isSecretFlag(key string) bool {
|
||||
for _, flag := range secretFlags {
|
||||
if flag.Name == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isSecretEnvVar(key string) bool {
|
||||
for _, flag := range secretFlags {
|
||||
for _, secretEnvVar := range flag.EnvVars {
|
||||
if secretEnvVar == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func dnsProxyStandAlone(c *cli.Context, namedTunnel *connection.NamedTunnelProperties) bool {
|
||||
return c.IsSet("proxy-dns") && (!c.IsSet("hostname") && !c.IsSet("tag") && !c.IsSet("hello-world") && namedTunnel == nil)
|
||||
}
|
||||
@@ -183,6 +218,10 @@ func prepareTunnelConfig(
|
||||
ingressRules ingress.Ingress
|
||||
classicTunnel *connection.ClassicTunnelProperties
|
||||
)
|
||||
|
||||
transportProtocol := c.String("protocol")
|
||||
protocolFetcher := edgediscovery.ProtocolPercentage
|
||||
|
||||
cfg := config.GetConfiguration()
|
||||
if isNamedTunnel {
|
||||
clientUUID, err := uuid.NewRandom()
|
||||
@@ -190,10 +229,25 @@ func prepareTunnelConfig(
|
||||
return nil, nil, errors.Wrap(err, "can't generate connector UUID")
|
||||
}
|
||||
log.Info().Msgf("Generated Connector ID: %s", clientUUID)
|
||||
features := append(c.StringSlice("features"), supervisor.FeatureSerializedHeaders)
|
||||
features := append(c.StringSlice("features"), defaultFeatures...)
|
||||
if c.IsSet(TunnelTokenFlag) {
|
||||
features = append(features, supervisor.FeatureAllowRemoteConfig)
|
||||
log.Info().Msg("Will be fetching remotely managed configuration from Cloudflare API")
|
||||
if transportProtocol == connection.AutoSelectFlag {
|
||||
protocolFetcher = func() (edgediscovery.ProtocolPercents, error) {
|
||||
// If the Tunnel is remotely managed and no protocol is set, we prefer QUIC, but still allow fall-back.
|
||||
preferQuic := []edgediscovery.ProtocolPercent{
|
||||
{
|
||||
Protocol: connection.QUIC.String(),
|
||||
Percentage: 100,
|
||||
},
|
||||
{
|
||||
Protocol: connection.HTTP2.String(),
|
||||
Percentage: 100,
|
||||
},
|
||||
}
|
||||
return preferQuic, nil
|
||||
}
|
||||
}
|
||||
log.Info().Msg("Will be fetching remotely managed configuration from Cloudflare API. Defaulting to protocol: quic")
|
||||
}
|
||||
namedTunnel.Client = tunnelpogs.ClientInfo{
|
||||
ClientID: clientUUID[:],
|
||||
@@ -237,7 +291,7 @@ func prepareTunnelConfig(
|
||||
}
|
||||
|
||||
warpRoutingEnabled := isWarpRoutingEnabled(cfg.WarpRouting, isNamedTunnel)
|
||||
protocolSelector, err := connection.NewProtocolSelector(c.String("protocol"), warpRoutingEnabled, namedTunnel, edgediscovery.ProtocolPercentage, supervisor.ResolveTTL, log)
|
||||
protocolSelector, err := connection.NewProtocolSelector(transportProtocol, warpRoutingEnabled, namedTunnel, protocolFetcher, supervisor.ResolveTTL, log)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -271,6 +325,10 @@ func prepareTunnelConfig(
|
||||
CompressionSetting: h2mux.CompressionSetting(uint64(c.Int("compression-quality"))),
|
||||
MetricsUpdateFreq: c.Duration("metrics-update-freq"),
|
||||
}
|
||||
edgeIPVersion, err := parseConfigIPVersion(c.String("edge-ip-version"))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
tunnelConfig := &supervisor.TunnelConfig{
|
||||
GracePeriod: gracePeriod,
|
||||
@@ -279,6 +337,7 @@ func prepareTunnelConfig(
|
||||
ClientID: clientID,
|
||||
EdgeAddrs: c.StringSlice("edge"),
|
||||
Region: c.String("region"),
|
||||
EdgeIPVersion: edgeIPVersion,
|
||||
HAConnections: c.Int("ha-connections"),
|
||||
IncidentLookup: supervisor.NewIncidentLookup(),
|
||||
IsAutoupdated: c.Bool("is-autoupdated"),
|
||||
@@ -297,11 +356,24 @@ func prepareTunnelConfig(
|
||||
ProtocolSelector: protocolSelector,
|
||||
EdgeTLSConfigs: edgeTLSConfigs,
|
||||
}
|
||||
dynamicConfig := &orchestration.Config{
|
||||
orchestratorConfig := &orchestration.Config{
|
||||
Ingress: &ingressRules,
|
||||
WarpRoutingEnabled: warpRoutingEnabled,
|
||||
WarpRouting: ingress.NewWarpRoutingConfig(&cfg.WarpRouting),
|
||||
ConfigurationFlags: parseConfigFlags(c),
|
||||
}
|
||||
return tunnelConfig, dynamicConfig, nil
|
||||
return tunnelConfig, orchestratorConfig, nil
|
||||
}
|
||||
|
||||
func parseConfigFlags(c *cli.Context) map[string]string {
|
||||
result := make(map[string]string)
|
||||
|
||||
for _, flag := range configFlags {
|
||||
if v := c.String(flag); c.IsSet(flag) && v != "" {
|
||||
result[flag] = v
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func gracePeriod(c *cli.Context) (time.Duration, error) {
|
||||
@@ -338,3 +410,18 @@ func dedup(slice []string) []string {
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// ParseConfigIPVersion returns the IP version from possible expected values from config
|
||||
func parseConfigIPVersion(version string) (v allregions.ConfigIPVersion, err error) {
|
||||
switch version {
|
||||
case "4":
|
||||
v = allregions.IPv4Only
|
||||
case "6":
|
||||
v = allregions.IPv6Only
|
||||
case "auto":
|
||||
v = allregions.Auto
|
||||
default: // unspecified or invalid
|
||||
err = fmt.Errorf("invalid value for edge-ip-version: %s", version)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/altsrc"
|
||||
"golang.org/x/net/idna"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cfapi"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
@@ -134,7 +134,7 @@ var (
|
||||
}
|
||||
selectProtocolFlag = altsrc.NewStringFlag(&cli.StringFlag{
|
||||
Name: "protocol",
|
||||
Value: "auto",
|
||||
Value: connection.AutoSelectFlag,
|
||||
Aliases: []string{"p"},
|
||||
Usage: fmt.Sprintf("Protocol implementation to connect with Cloudflare's edge network. %s", connection.AvailableProtocolFlagMessage),
|
||||
EnvVars: []string{"TUNNEL_TRANSPORT_PROTOCOL"},
|
||||
|
||||
+26
-22
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/cloudflare/cloudflared/validation"
|
||||
)
|
||||
@@ -177,9 +177,9 @@ func ValidateUrl(c *cli.Context, allowURLFromArgs bool) (*url.URL, error) {
|
||||
}
|
||||
|
||||
type UnvalidatedIngressRule struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Path string `json:"path"`
|
||||
Service string `json:"service"`
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Service string `json:"service,omitempty"`
|
||||
OriginRequest OriginRequestConfig `yaml:"originRequest" json:"originRequest"`
|
||||
}
|
||||
|
||||
@@ -192,41 +192,43 @@ type UnvalidatedIngressRule struct {
|
||||
// - To specify a time.Duration in json, use int64 of the nanoseconds
|
||||
type OriginRequestConfig struct {
|
||||
// HTTP proxy timeout for establishing a new connection
|
||||
ConnectTimeout *CustomDuration `yaml:"connectTimeout" json:"connectTimeout"`
|
||||
ConnectTimeout *CustomDuration `yaml:"connectTimeout" json:"connectTimeout,omitempty"`
|
||||
// HTTP proxy timeout for completing a TLS handshake
|
||||
TLSTimeout *CustomDuration `yaml:"tlsTimeout" json:"tlsTimeout"`
|
||||
TLSTimeout *CustomDuration `yaml:"tlsTimeout" json:"tlsTimeout,omitempty"`
|
||||
// HTTP proxy TCP keepalive duration
|
||||
TCPKeepAlive *CustomDuration `yaml:"tcpKeepAlive" json:"tcpKeepAlive"`
|
||||
TCPKeepAlive *CustomDuration `yaml:"tcpKeepAlive" json:"tcpKeepAlive,omitempty"`
|
||||
// HTTP proxy should disable "happy eyeballs" for IPv4/v6 fallback
|
||||
NoHappyEyeballs *bool `yaml:"noHappyEyeballs" json:"noHappyEyeballs"`
|
||||
NoHappyEyeballs *bool `yaml:"noHappyEyeballs" json:"noHappyEyeballs,omitempty"`
|
||||
// HTTP proxy maximum keepalive connection pool size
|
||||
KeepAliveConnections *int `yaml:"keepAliveConnections" json:"keepAliveConnections"`
|
||||
KeepAliveConnections *int `yaml:"keepAliveConnections" json:"keepAliveConnections,omitempty"`
|
||||
// HTTP proxy timeout for closing an idle connection
|
||||
KeepAliveTimeout *CustomDuration `yaml:"keepAliveTimeout" json:"keepAliveTimeout"`
|
||||
KeepAliveTimeout *CustomDuration `yaml:"keepAliveTimeout" json:"keepAliveTimeout,omitempty"`
|
||||
// Sets the HTTP Host header for the local webserver.
|
||||
HTTPHostHeader *string `yaml:"httpHostHeader" json:"httpHostHeader"`
|
||||
HTTPHostHeader *string `yaml:"httpHostHeader" json:"httpHostHeader,omitempty"`
|
||||
// Hostname on the origin server certificate.
|
||||
OriginServerName *string `yaml:"originServerName" json:"originServerName"`
|
||||
OriginServerName *string `yaml:"originServerName" json:"originServerName,omitempty"`
|
||||
// Path to the CA for the certificate of your origin.
|
||||
// This option should be used only if your certificate is not signed by Cloudflare.
|
||||
CAPool *string `yaml:"caPool" json:"caPool"`
|
||||
CAPool *string `yaml:"caPool" json:"caPool,omitempty"`
|
||||
// Disables TLS verification of the certificate presented by your origin.
|
||||
// Will allow any certificate from the origin to be accepted.
|
||||
// Note: The connection from your machine to Cloudflare's Edge is still encrypted.
|
||||
NoTLSVerify *bool `yaml:"noTLSVerify" json:"noTLSVerify"`
|
||||
NoTLSVerify *bool `yaml:"noTLSVerify" json:"noTLSVerify,omitempty"`
|
||||
// Disables chunked transfer encoding.
|
||||
// Useful if you are running a WSGI server.
|
||||
DisableChunkedEncoding *bool `yaml:"disableChunkedEncoding" json:"disableChunkedEncoding"`
|
||||
DisableChunkedEncoding *bool `yaml:"disableChunkedEncoding" json:"disableChunkedEncoding,omitempty"`
|
||||
// Runs as jump host
|
||||
BastionMode *bool `yaml:"bastionMode" json:"bastionMode"`
|
||||
BastionMode *bool `yaml:"bastionMode" json:"bastionMode,omitempty"`
|
||||
// Listen address for the proxy.
|
||||
ProxyAddress *string `yaml:"proxyAddress" json:"proxyAddress"`
|
||||
ProxyAddress *string `yaml:"proxyAddress" json:"proxyAddress,omitempty"`
|
||||
// Listen port for the proxy.
|
||||
ProxyPort *uint `yaml:"proxyPort" json:"proxyPort"`
|
||||
ProxyPort *uint `yaml:"proxyPort" json:"proxyPort,omitempty"`
|
||||
// Valid options are 'socks' or empty.
|
||||
ProxyType *string `yaml:"proxyType" json:"proxyType"`
|
||||
ProxyType *string `yaml:"proxyType" json:"proxyType,omitempty"`
|
||||
// IP rules for the proxy service
|
||||
IPRules []IngressIPRule `yaml:"ipRules" json:"ipRules"`
|
||||
IPRules []IngressIPRule `yaml:"ipRules" json:"ipRules,omitempty"`
|
||||
// Attempt to connect to origin with HTTP/2
|
||||
Http2Origin *bool `yaml:"http2Origin" json:"http2Origin,omitempty"`
|
||||
}
|
||||
|
||||
type IngressIPRule struct {
|
||||
@@ -244,7 +246,9 @@ type Configuration struct {
|
||||
}
|
||||
|
||||
type WarpRoutingConfig struct {
|
||||
Enabled bool `yaml:"enabled" json:"enabled"`
|
||||
Enabled bool `yaml:"enabled" json:"enabled"`
|
||||
ConnectTimeout *CustomDuration `yaml:"connectTimeout" json:"connectTimeout,omitempty"`
|
||||
TCPKeepAlive *CustomDuration `yaml:"tcpKeepAlive" json:"tcpKeepAlive,omitempty"`
|
||||
}
|
||||
|
||||
type configFileSettings struct {
|
||||
@@ -392,7 +396,7 @@ func ReadConfigFile(c *cli.Context, log *zerolog.Logger) (settings *configFileSe
|
||||
// Parse it again, with strict mode, to find warnings.
|
||||
if file, err := os.Open(configFile); err == nil {
|
||||
decoder := yaml.NewDecoder(file)
|
||||
decoder.SetStrict(true)
|
||||
decoder.KnownFields(true)
|
||||
var unusedConfig configFileSettings
|
||||
if err := decoder.Decode(&unusedConfig); err != nil {
|
||||
warnings = err.Error()
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestConfigFileSettings(t *testing.T) {
|
||||
@@ -23,7 +23,9 @@ func TestConfigFileSettings(t *testing.T) {
|
||||
Service: "https://localhost:8001",
|
||||
}
|
||||
warpRouting = WarpRoutingConfig{
|
||||
Enabled: true,
|
||||
Enabled: true,
|
||||
ConnectTimeout: &CustomDuration{Duration: 2 * time.Second},
|
||||
TCPKeepAlive: &CustomDuration{Duration: 10 * time.Second},
|
||||
}
|
||||
)
|
||||
rawYAML := `
|
||||
@@ -48,6 +50,9 @@ ingress:
|
||||
service: https://localhost:8001
|
||||
warp-routing:
|
||||
enabled: true
|
||||
connectTimeout: 2s
|
||||
tcpKeepAlive: 10s
|
||||
|
||||
retries: 5
|
||||
grace-period: 30s
|
||||
percentage: 3.14
|
||||
@@ -111,7 +116,7 @@ counters:
|
||||
|
||||
}
|
||||
|
||||
var rawConfig = []byte(`
|
||||
var rawJsonConfig = []byte(`
|
||||
{
|
||||
"connectTimeout": 10,
|
||||
"tlsTimeout": 30,
|
||||
@@ -139,7 +144,8 @@ var rawConfig = []byte(`
|
||||
"ports": [443, 4443],
|
||||
"allow": true
|
||||
}
|
||||
]
|
||||
],
|
||||
"http2Origin": true
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -148,15 +154,14 @@ func TestMarshalUnmarshalOriginRequest(t *testing.T) {
|
||||
name string
|
||||
marshalFunc func(in interface{}) (out []byte, err error)
|
||||
unMarshalFunc func(in []byte, out interface{}) (err error)
|
||||
baseUnit time.Duration
|
||||
}{
|
||||
{"json", json.Marshal, json.Unmarshal, time.Second},
|
||||
{"yaml", yaml.Marshal, yaml.Unmarshal, time.Nanosecond},
|
||||
{"json", json.Marshal, json.Unmarshal},
|
||||
{"yaml", yaml.Marshal, yaml.Unmarshal},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assertConfig(t, tc.marshalFunc, tc.unMarshalFunc, tc.baseUnit)
|
||||
assertConfig(t, tc.marshalFunc, tc.unMarshalFunc)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -165,18 +170,17 @@ func assertConfig(
|
||||
t *testing.T,
|
||||
marshalFunc func(in interface{}) (out []byte, err error),
|
||||
unMarshalFunc func(in []byte, out interface{}) (err error),
|
||||
baseUnit time.Duration,
|
||||
) {
|
||||
var config OriginRequestConfig
|
||||
var config2 OriginRequestConfig
|
||||
|
||||
assert.NoError(t, unMarshalFunc(rawConfig, &config))
|
||||
assert.NoError(t, json.Unmarshal(rawJsonConfig, &config))
|
||||
|
||||
assert.Equal(t, baseUnit*10, config.ConnectTimeout.Duration)
|
||||
assert.Equal(t, baseUnit*30, config.TLSTimeout.Duration)
|
||||
assert.Equal(t, baseUnit*30, config.TCPKeepAlive.Duration)
|
||||
assert.Equal(t, time.Second*10, config.ConnectTimeout.Duration)
|
||||
assert.Equal(t, time.Second*30, config.TLSTimeout.Duration)
|
||||
assert.Equal(t, time.Second*30, config.TCPKeepAlive.Duration)
|
||||
assert.Equal(t, true, *config.NoHappyEyeballs)
|
||||
assert.Equal(t, baseUnit*60, config.KeepAliveTimeout.Duration)
|
||||
assert.Equal(t, time.Second*60, config.KeepAliveTimeout.Duration)
|
||||
assert.Equal(t, 10, *config.KeepAliveConnections)
|
||||
assert.Equal(t, "app.tunnel.com", *config.HTTPHostHeader)
|
||||
assert.Equal(t, "app.tunnel.com", *config.OriginServerName)
|
||||
@@ -188,6 +192,7 @@ func assertConfig(
|
||||
assert.Equal(t, true, *config.NoTLSVerify)
|
||||
assert.Equal(t, uint(9000), *config.ProxyPort)
|
||||
assert.Equal(t, "socks", *config.ProxyType)
|
||||
assert.Equal(t, true, *config.Http2Origin)
|
||||
|
||||
privateV4 := "10.0.0.0/8"
|
||||
privateV6 := "fc00::/7"
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/cloudflare/cloudflared/watcher"
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/websocket"
|
||||
)
|
||||
@@ -29,6 +30,7 @@ var switchingProtocolText = fmt.Sprintf("%d %s", http.StatusSwitchingProtocols,
|
||||
|
||||
type Orchestrator interface {
|
||||
UpdateConfig(version int32, config []byte) *pogs.UpdateConfigurationResponse
|
||||
GetConfigJSON() ([]byte, error)
|
||||
GetOriginProxy() (OriginProxy, error)
|
||||
}
|
||||
|
||||
@@ -121,7 +123,7 @@ func (t Type) String() string {
|
||||
|
||||
// OriginProxy is how data flows from cloudflared to the origin services running behind it.
|
||||
type OriginProxy interface {
|
||||
ProxyHTTP(w ResponseWriter, req *http.Request, isWebsocket bool) error
|
||||
ProxyHTTP(w ResponseWriter, tr *tracing.TracedRequest, isWebsocket bool) error
|
||||
ProxyTCP(ctx context.Context, rwa ReadWriteAcker, req *TCPRequest) error
|
||||
}
|
||||
|
||||
@@ -130,6 +132,7 @@ type TCPRequest struct {
|
||||
Dest string
|
||||
CFRay string
|
||||
LBProbe bool
|
||||
FlowID string
|
||||
}
|
||||
|
||||
// ReadWriteAcker is a readwriter with the ability to Acknowledge to the downstream (edge) that the origin has
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/websocket"
|
||||
)
|
||||
@@ -41,6 +42,10 @@ type mockOrchestrator struct {
|
||||
originProxy OriginProxy
|
||||
}
|
||||
|
||||
func (mcr *mockOrchestrator) GetConfigJSON() ([]byte, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (*mockOrchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.UpdateConfigurationResponse {
|
||||
return &tunnelpogs.UpdateConfigurationResponse{
|
||||
LastAppliedVersion: version,
|
||||
@@ -55,9 +60,10 @@ type mockOriginProxy struct{}
|
||||
|
||||
func (moc *mockOriginProxy) ProxyHTTP(
|
||||
w ResponseWriter,
|
||||
req *http.Request,
|
||||
tr *tracing.TracedRequest,
|
||||
isWebsocket bool,
|
||||
) error {
|
||||
req := tr.Request
|
||||
if isWebsocket {
|
||||
switch req.URL.Path {
|
||||
case "/ws/echo":
|
||||
|
||||
+19
-2
@@ -30,11 +30,15 @@ type controlStream struct {
|
||||
// ControlStreamHandler registers connections with origintunneld and initiates graceful shutdown.
|
||||
type ControlStreamHandler interface {
|
||||
// ServeControlStream handles the control plane of the transport in the current goroutine calling this
|
||||
ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions) error
|
||||
ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions, tunnelConfigGetter TunnelConfigJSONGetter) error
|
||||
// IsStopped tells whether the method above has finished
|
||||
IsStopped() bool
|
||||
}
|
||||
|
||||
type TunnelConfigJSONGetter interface {
|
||||
GetConfigJSON() ([]byte, error)
|
||||
}
|
||||
|
||||
// NewControlStream returns a new instance of ControlStreamHandler
|
||||
func NewControlStream(
|
||||
observer *Observer,
|
||||
@@ -63,15 +67,28 @@ func (c *controlStream) ServeControlStream(
|
||||
ctx context.Context,
|
||||
rw io.ReadWriteCloser,
|
||||
connOptions *tunnelpogs.ConnectionOptions,
|
||||
tunnelConfigGetter TunnelConfigJSONGetter,
|
||||
) error {
|
||||
rpcClient := c.newRPCClientFunc(ctx, rw, c.observer.log)
|
||||
|
||||
if err := rpcClient.RegisterConnection(ctx, c.namedTunnelProperties, connOptions, c.connIndex, c.observer); err != nil {
|
||||
registrationDetails, err := rpcClient.RegisterConnection(ctx, c.namedTunnelProperties, connOptions, c.connIndex, c.observer)
|
||||
if err != nil {
|
||||
rpcClient.Close()
|
||||
return err
|
||||
}
|
||||
c.connectedFuse.Connected()
|
||||
|
||||
// if conn index is 0 and tunnel is not remotely managed, then send local ingress rules configuration
|
||||
if c.connIndex == 0 && !registrationDetails.TunnelIsRemotelyManaged {
|
||||
if tunnelConfig, err := tunnelConfigGetter.GetConfigJSON(); err == nil {
|
||||
if err := rpcClient.SendLocalConfiguration(ctx, tunnelConfig, c.observer); err != nil {
|
||||
c.observer.log.Err(err).Msg("unable to send local configuration")
|
||||
}
|
||||
} else {
|
||||
c.observer.log.Err(err).Msg("failed to obtain current configuration")
|
||||
}
|
||||
}
|
||||
|
||||
c.waitForUnregister(ctx, rpcClient)
|
||||
return nil
|
||||
}
|
||||
|
||||
+2
-1
@@ -12,6 +12,7 @@ import (
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/websocket"
|
||||
)
|
||||
@@ -233,7 +234,7 @@ func (h *h2muxConnection) ServeStream(stream *h2mux.MuxedStream) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = originProxy.ProxyHTTP(respWriter, req, sourceConnectionType == TypeWebsocket)
|
||||
err = originProxy.ProxyHTTP(respWriter, tracing.NewTracedRequest(req), sourceConnectionType == TypeWebsocket)
|
||||
if err != nil {
|
||||
respWriter.WriteErrorResponse()
|
||||
}
|
||||
|
||||
+11
-2
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/rs/zerolog"
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
@@ -116,7 +117,7 @@ func (c *HTTP2Connection) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
switch connType {
|
||||
case TypeControlStream:
|
||||
if err := c.controlStreamHandler.ServeControlStream(r.Context(), respWriter, c.connOptions); err != nil {
|
||||
if err := c.controlStreamHandler.ServeControlStream(r.Context(), respWriter, c.connOptions, c.orchestrator); err != nil {
|
||||
c.controlStreamErr = err
|
||||
c.log.Error().Err(err)
|
||||
respWriter.WriteErrorResponse()
|
||||
@@ -130,7 +131,9 @@ func (c *HTTP2Connection) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
case TypeWebsocket, TypeHTTP:
|
||||
stripWebsocketUpgradeHeader(r)
|
||||
if err := originProxy.ProxyHTTP(respWriter, r, connType == TypeWebsocket); err != nil {
|
||||
// Check for tracing on request
|
||||
tr := tracing.NewTracedRequest(r)
|
||||
if err := originProxy.ProxyHTTP(respWriter, tr, connType == TypeWebsocket); err != nil {
|
||||
err := fmt.Errorf("Failed to proxy HTTP: %w", err)
|
||||
c.log.Error().Err(err)
|
||||
respWriter.WriteErrorResponse()
|
||||
@@ -228,6 +231,12 @@ func (rp *http2RespWriter) WriteRespHeaders(status int, header http.Header) erro
|
||||
dest[name] = values
|
||||
}
|
||||
|
||||
if h2name == tracing.IntCloudflaredTracingHeader {
|
||||
// Add cf-int-cloudflared-tracing header outside of serialized userHeaders
|
||||
rp.w.Header()[tracing.CanonicalCloudflaredTracingHeader] = values
|
||||
continue
|
||||
}
|
||||
|
||||
if !IsControlResponseHeader(h2name) || IsWebsocketClientHeader(h2name) {
|
||||
// User headers, on the other hand, must all be serialized so that
|
||||
// HTTP/2 header validation won't be applied to HTTP/1 header values
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -166,18 +167,26 @@ type mockNamedTunnelRPCClient struct {
|
||||
unregistered chan struct{}
|
||||
}
|
||||
|
||||
func (mc mockNamedTunnelRPCClient) SendLocalConfiguration(c context.Context, config []byte, observer *Observer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mc mockNamedTunnelRPCClient) RegisterConnection(
|
||||
c context.Context,
|
||||
properties *NamedTunnelProperties,
|
||||
options *tunnelpogs.ConnectionOptions,
|
||||
connIndex uint8,
|
||||
observer *Observer,
|
||||
) error {
|
||||
) (*tunnelpogs.ConnectionDetails, error) {
|
||||
if mc.shouldFail != nil {
|
||||
return mc.shouldFail
|
||||
return nil, mc.shouldFail
|
||||
}
|
||||
close(mc.registered)
|
||||
return nil
|
||||
return &tunnelpogs.ConnectionDetails{
|
||||
Location: "LIS",
|
||||
UUID: uuid.New(),
|
||||
TunnelIsRemotelyManaged: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (mc mockNamedTunnelRPCClient) GracefulShutdown(ctx context.Context, gracePeriod time.Duration) {
|
||||
@@ -477,7 +486,7 @@ func TestGracefulShutdownHTTP2(t *testing.T) {
|
||||
|
||||
select {
|
||||
case <-rpcClientFactory.registered:
|
||||
break //ok
|
||||
break // ok
|
||||
case <-time.Tick(time.Second):
|
||||
t.Fatal("timeout out waiting for registration")
|
||||
}
|
||||
@@ -487,7 +496,7 @@ func TestGracefulShutdownHTTP2(t *testing.T) {
|
||||
|
||||
select {
|
||||
case <-rpcClientFactory.unregistered:
|
||||
break //ok
|
||||
break // ok
|
||||
case <-time.Tick(time.Second):
|
||||
t.Fatal("timeout out waiting for unregistered signal")
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ const (
|
||||
MetricsNamespace = "cloudflared"
|
||||
TunnelSubsystem = "tunnel"
|
||||
muxerSubsystem = "muxer"
|
||||
configSubsystem = "config"
|
||||
)
|
||||
|
||||
type muxerMetrics struct {
|
||||
@@ -36,6 +37,11 @@ type muxerMetrics struct {
|
||||
compRateAve *prometheus.GaugeVec
|
||||
}
|
||||
|
||||
type localConfigMetrics struct {
|
||||
pushes prometheus.Counter
|
||||
pushesErrors prometheus.Counter
|
||||
}
|
||||
|
||||
type tunnelMetrics struct {
|
||||
timerRetries prometheus.Gauge
|
||||
serverLocations *prometheus.GaugeVec
|
||||
@@ -51,6 +57,39 @@ type tunnelMetrics struct {
|
||||
muxerMetrics *muxerMetrics
|
||||
tunnelsHA tunnelsForHA
|
||||
userHostnamesCounts *prometheus.CounterVec
|
||||
|
||||
localConfigMetrics *localConfigMetrics
|
||||
}
|
||||
|
||||
func newLocalConfigMetrics() *localConfigMetrics {
|
||||
|
||||
pushesMetric := prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: MetricsNamespace,
|
||||
Subsystem: configSubsystem,
|
||||
Name: "local_config_pushes",
|
||||
Help: "Number of local configuration pushes to the edge",
|
||||
},
|
||||
)
|
||||
|
||||
pushesErrorsMetric := prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: MetricsNamespace,
|
||||
Subsystem: configSubsystem,
|
||||
Name: "local_config_pushes_errors",
|
||||
Help: "Number of errors occurred during local configuration pushes",
|
||||
},
|
||||
)
|
||||
|
||||
prometheus.MustRegister(
|
||||
pushesMetric,
|
||||
pushesErrorsMetric,
|
||||
)
|
||||
|
||||
return &localConfigMetrics{
|
||||
pushes: pushesMetric,
|
||||
pushesErrors: pushesErrorsMetric,
|
||||
}
|
||||
}
|
||||
|
||||
func newMuxerMetrics() *muxerMetrics {
|
||||
@@ -386,6 +425,7 @@ func initTunnelMetrics() *tunnelMetrics {
|
||||
regFail: registerFail,
|
||||
rpcFail: rpcFail,
|
||||
userHostnamesCounts: userHostnamesCounts,
|
||||
localConfigMetrics: newLocalConfigMetrics(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package connection
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
|
||||
const (
|
||||
LogFieldLocation = "location"
|
||||
LogFieldIPAddress = "ip"
|
||||
observerChannelBufferSize = 16
|
||||
)
|
||||
|
||||
@@ -41,11 +43,12 @@ func (o *Observer) RegisterSink(sink EventSink) {
|
||||
o.addSinkChan <- sink
|
||||
}
|
||||
|
||||
func (o *Observer) logServerInfo(connIndex uint8, location, msg string) {
|
||||
func (o *Observer) logServerInfo(connIndex uint8, location string, address net.IP, msg string) {
|
||||
o.sendEvent(Event{Index: connIndex, EventType: Connected, Location: location})
|
||||
o.log.Info().
|
||||
Uint8(LogFieldConnIndex, connIndex).
|
||||
Str(LogFieldLocation, location).
|
||||
IPAddr(LogFieldIPAddress, address).
|
||||
Msg(msg)
|
||||
o.metrics.registerServerLocation(uint8ToString(connIndex), location)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ const (
|
||||
edgeH2TLSServerName = "h2.cftunnel.com"
|
||||
// edgeQUICServerName is the server name to establish quic connection with edge.
|
||||
edgeQUICServerName = "quic.cftunnel.com"
|
||||
autoSelectFlag = "auto"
|
||||
AutoSelectFlag = "auto"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -247,7 +247,7 @@ func selectNamedTunnelProtocols(
|
||||
|
||||
// If the user does not pick (hopefully the majority) then we use the one derived from the TXT DNS record and
|
||||
// fallback on failures.
|
||||
if protocolFlag == autoSelectFlag {
|
||||
if protocolFlag == AutoSelectFlag {
|
||||
return newAutoProtocolSelector(protocol, []Protocol{QUIC, HTTP2, H2mux}, threshold, fetchFunc, ttl, log), nil
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ func selectWarpRoutingProtocols(
|
||||
|
||||
// If the user does not pick (hopefully the majority) then we use the one derived from the TXT DNS record and
|
||||
// fallback on failures.
|
||||
if protocolFlag == autoSelectFlag {
|
||||
if protocolFlag == AutoSelectFlag {
|
||||
return newAutoProtocolSelector(protocol, []Protocol{QUICWarp, HTTP2Warp}, threshold, fetchFunc, ttl, log), nil
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -91,14 +91,14 @@ func TestNewProtocolSelector(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "named tunnel quic and http2 disabled",
|
||||
protocol: "auto",
|
||||
protocol: AutoSelectFlag,
|
||||
expectedProtocol: H2mux,
|
||||
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: -1}, edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: -1}),
|
||||
namedTunnelConfig: testNamedTunnelProperties,
|
||||
},
|
||||
{
|
||||
name: "named tunnel quic disabled",
|
||||
protocol: "auto",
|
||||
protocol: AutoSelectFlag,
|
||||
expectedProtocol: HTTP2,
|
||||
// Hasfallback true is because if http2 fails, then we further fallback to h2mux.
|
||||
hasFallback: true,
|
||||
@@ -108,21 +108,21 @@ func TestNewProtocolSelector(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "named tunnel auto all http2 disabled",
|
||||
protocol: "auto",
|
||||
protocol: AutoSelectFlag,
|
||||
expectedProtocol: H2mux,
|
||||
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: -1}),
|
||||
namedTunnelConfig: testNamedTunnelProperties,
|
||||
},
|
||||
{
|
||||
name: "named tunnel auto to h2mux",
|
||||
protocol: "auto",
|
||||
protocol: AutoSelectFlag,
|
||||
expectedProtocol: H2mux,
|
||||
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 0}),
|
||||
namedTunnelConfig: testNamedTunnelProperties,
|
||||
},
|
||||
{
|
||||
name: "named tunnel auto to http2",
|
||||
protocol: "auto",
|
||||
protocol: AutoSelectFlag,
|
||||
expectedProtocol: HTTP2,
|
||||
hasFallback: true,
|
||||
expectedFallback: H2mux,
|
||||
@@ -131,7 +131,7 @@ func TestNewProtocolSelector(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "named tunnel auto to quic",
|
||||
protocol: "auto",
|
||||
protocol: AutoSelectFlag,
|
||||
expectedProtocol: QUIC,
|
||||
hasFallback: true,
|
||||
expectedFallback: HTTP2,
|
||||
@@ -167,7 +167,7 @@ func TestNewProtocolSelector(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "warp routing quic",
|
||||
protocol: "auto",
|
||||
protocol: AutoSelectFlag,
|
||||
expectedProtocol: QUICWarp,
|
||||
hasFallback: true,
|
||||
expectedFallback: HTTP2Warp,
|
||||
@@ -177,7 +177,7 @@ func TestNewProtocolSelector(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "warp routing auto",
|
||||
protocol: "auto",
|
||||
protocol: AutoSelectFlag,
|
||||
expectedProtocol: HTTP2Warp,
|
||||
hasFallback: false,
|
||||
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}),
|
||||
@@ -186,7 +186,7 @@ func TestNewProtocolSelector(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "warp routing auto- quic",
|
||||
protocol: "auto",
|
||||
protocol: AutoSelectFlag,
|
||||
expectedProtocol: QUICWarp,
|
||||
hasFallback: true,
|
||||
expectedFallback: HTTP2Warp,
|
||||
@@ -209,7 +209,7 @@ func TestNewProtocolSelector(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "named tunnel fetch error",
|
||||
protocol: "auto",
|
||||
protocol: AutoSelectFlag,
|
||||
fetchFunc: mockFetcher(true),
|
||||
namedTunnelConfig: testNamedTunnelProperties,
|
||||
expectedProtocol: HTTP2,
|
||||
@@ -237,7 +237,7 @@ func TestNewProtocolSelector(t *testing.T) {
|
||||
|
||||
func TestAutoProtocolSelectorRefresh(t *testing.T) {
|
||||
fetcher := dynamicMockFetcher{}
|
||||
selector, err := NewProtocolSelector("auto", noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), testNoTTL, &log)
|
||||
selector, err := NewProtocolSelector(AutoSelectFlag, noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), testNoTTL, &log)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, H2mux, selector.Current())
|
||||
|
||||
@@ -297,7 +297,7 @@ func TestHTTP2ProtocolSelectorRefresh(t *testing.T) {
|
||||
func TestProtocolSelectorRefreshTTL(t *testing.T) {
|
||||
fetcher := dynamicMockFetcher{}
|
||||
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}}
|
||||
selector, err := NewProtocolSelector("auto", noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), time.Hour, &log)
|
||||
selector, err := NewProtocolSelector(AutoSelectFlag, noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), time.Hour, &log)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, QUIC, selector.Current())
|
||||
|
||||
|
||||
+46
-24
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/cloudflare/cloudflared/datagramsession"
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
quicpogs "github.com/cloudflare/cloudflared/quic"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
@@ -30,11 +31,13 @@ const (
|
||||
HTTPMethodKey = "HttpMethod"
|
||||
// HTTPHostKey is used to get or set http Method in QUIC ALPN if the underlying proxy connection type is HTTP.
|
||||
HTTPHostKey = "HttpHost"
|
||||
|
||||
QUICMetadataFlowID = "FlowID"
|
||||
)
|
||||
|
||||
// QUICConnection represents the type that facilitates Proxying via QUIC streams.
|
||||
type QUICConnection struct {
|
||||
session quic.Session
|
||||
session quic.Connection
|
||||
logger *zerolog.Logger
|
||||
orchestrator Orchestrator
|
||||
sessionManager datagramsession.Manager
|
||||
@@ -57,7 +60,7 @@ func NewQUICConnection(
|
||||
return nil, fmt.Errorf("failed to dial to edge: %w", err)
|
||||
}
|
||||
|
||||
datagramMuxer, err := quicpogs.NewDatagramMuxer(session)
|
||||
datagramMuxer, err := quicpogs.NewDatagramMuxer(session, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -110,7 +113,7 @@ func (q *QUICConnection) Serve(ctx context.Context) error {
|
||||
|
||||
func (q *QUICConnection) serveControlStream(ctx context.Context, controlStream quic.Stream) error {
|
||||
// This blocks until the control plane is done.
|
||||
err := q.controlStreamHandler.ServeControlStream(ctx, controlStream, q.connOptions)
|
||||
err := q.controlStreamHandler.ServeControlStream(ctx, controlStream, q.connOptions, q.orchestrator)
|
||||
if err != nil {
|
||||
// Not wrapping error here to be consistent with the http2 message.
|
||||
return err
|
||||
@@ -119,6 +122,11 @@ func (q *QUICConnection) serveControlStream(ctx context.Context, controlStream q
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the session with no errors specified.
|
||||
func (q *QUICConnection) Close() {
|
||||
q.session.CloseWithError(0, "")
|
||||
}
|
||||
|
||||
func (q *QUICConnection) acceptStream(ctx context.Context) error {
|
||||
defer q.Close()
|
||||
for {
|
||||
@@ -130,20 +138,17 @@ func (q *QUICConnection) acceptStream(ctx context.Context) error {
|
||||
}
|
||||
return fmt.Errorf("failed to accept QUIC stream: %w", err)
|
||||
}
|
||||
go func() {
|
||||
stream := quicpogs.NewSafeStreamCloser(quicStream)
|
||||
defer stream.Close()
|
||||
|
||||
if err = q.handleStream(stream); err != nil {
|
||||
q.logger.Err(err).Msg("Failed to handle QUIC stream")
|
||||
}
|
||||
}()
|
||||
go q.runStream(quicStream)
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the session with no errors specified.
|
||||
func (q *QUICConnection) Close() {
|
||||
q.session.CloseWithError(0, "")
|
||||
func (q *QUICConnection) runStream(quicStream quic.Stream) {
|
||||
stream := quicpogs.NewSafeStreamCloser(quicStream)
|
||||
defer stream.Close()
|
||||
|
||||
if err := q.handleStream(stream); err != nil {
|
||||
q.logger.Err(err).Msg("Failed to handle QUIC stream")
|
||||
}
|
||||
}
|
||||
|
||||
func (q *QUICConnection) handleStream(stream io.ReadWriteCloser) error {
|
||||
@@ -155,7 +160,7 @@ func (q *QUICConnection) handleStream(stream io.ReadWriteCloser) error {
|
||||
case quicpogs.DataStreamProtocolSignature:
|
||||
reqServerStream, err := quicpogs.NewRequestServerStream(stream, signature)
|
||||
if err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
return q.handleDataStream(reqServerStream)
|
||||
case quicpogs.RPCStreamProtocolSignature:
|
||||
@@ -170,27 +175,41 @@ func (q *QUICConnection) handleStream(stream io.ReadWriteCloser) error {
|
||||
}
|
||||
|
||||
func (q *QUICConnection) handleDataStream(stream *quicpogs.RequestServerStream) error {
|
||||
connectRequest, err := stream.ReadConnectRequestData()
|
||||
request, err := stream.ReadConnectRequestData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := q.dispatchRequest(stream, err, request); err != nil {
|
||||
_ = stream.WriteConnectResponseData(err)
|
||||
q.logger.Err(err).Str("type", request.Type.String()).Str("dest", request.Dest).Msg("Request failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *QUICConnection) dispatchRequest(stream *quicpogs.RequestServerStream, err error, request *quicpogs.ConnectRequest) error {
|
||||
originProxy, err := q.orchestrator.GetOriginProxy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch connectRequest.Type {
|
||||
|
||||
switch request.Type {
|
||||
case quicpogs.ConnectionTypeHTTP, quicpogs.ConnectionTypeWebsocket:
|
||||
req, err := buildHTTPRequest(connectRequest, stream)
|
||||
tracedReq, err := buildHTTPRequest(request, stream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := newHTTPResponseAdapter(stream)
|
||||
return originProxy.ProxyHTTP(w, req, connectRequest.Type == quicpogs.ConnectionTypeWebsocket)
|
||||
return originProxy.ProxyHTTP(w, tracedReq, request.Type == quicpogs.ConnectionTypeWebsocket)
|
||||
|
||||
case quicpogs.ConnectionTypeTCP:
|
||||
rwa := &streamReadWriteAcker{stream}
|
||||
return originProxy.ProxyTCP(context.Background(), rwa, &TCPRequest{Dest: connectRequest.Dest})
|
||||
metadata := request.MetadataMap()
|
||||
return originProxy.ProxyTCP(context.Background(), rwa, &TCPRequest{
|
||||
Dest: request.Dest,
|
||||
FlowID: metadata[QUICMetadataFlowID],
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -216,7 +235,7 @@ func (q *QUICConnection) RegisterUdpSession(ctx context.Context, sessionID uuid.
|
||||
|
||||
go q.serveUDPSession(session, closeAfterIdleHint)
|
||||
|
||||
q.logger.Debug().Msgf("Registered session %v, %v, %v", sessionID, dstIP, dstPort)
|
||||
q.logger.Debug().Str("sessionID", sessionID.String()).Str("src", originProxy.LocalAddr().String()).Str("dst", fmt.Sprintf("%s:%d", dstIP, dstPort)).Msgf("Registered session")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -305,7 +324,7 @@ func (hrw httpResponseAdapter) WriteErrorResponse(err error) {
|
||||
hrw.WriteConnectResponseData(err, quicpogs.Metadata{Key: "HttpStatus", Val: strconv.Itoa(http.StatusBadGateway)})
|
||||
}
|
||||
|
||||
func buildHTTPRequest(connectRequest *quicpogs.ConnectRequest, body io.ReadCloser) (*http.Request, error) {
|
||||
func buildHTTPRequest(connectRequest *quicpogs.ConnectRequest, body io.ReadCloser) (*tracing.TracedRequest, error) {
|
||||
metadata := connectRequest.MetadataMap()
|
||||
dest := connectRequest.Dest
|
||||
method := metadata[HTTPMethodKey]
|
||||
@@ -345,7 +364,10 @@ func buildHTTPRequest(connectRequest *quicpogs.ConnectRequest, body io.ReadClose
|
||||
req.Body = http.NoBody
|
||||
}
|
||||
stripWebsocketUpgradeHeader(req)
|
||||
return req, err
|
||||
|
||||
// Check for tracing on request
|
||||
tracedReq := tracing.NewTracedRequest(req)
|
||||
return tracedReq, err
|
||||
}
|
||||
|
||||
func setContentLength(req *http.Request) error {
|
||||
|
||||
@@ -24,13 +24,14 @@ import (
|
||||
|
||||
"github.com/cloudflare/cloudflared/datagramsession"
|
||||
quicpogs "github.com/cloudflare/cloudflared/quic"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
var (
|
||||
testTLSServerConfig = quicpogs.GenerateTLSConfig()
|
||||
testQUICConfig = &quic.Config{
|
||||
KeepAlive: true,
|
||||
KeepAlivePeriod: 5 * time.Second,
|
||||
EnableDatagrams: true,
|
||||
}
|
||||
)
|
||||
@@ -162,7 +163,7 @@ type fakeControlStream struct {
|
||||
ControlStreamHandler
|
||||
}
|
||||
|
||||
func (fakeControlStream) ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions) error {
|
||||
func (fakeControlStream) ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions, tunnelConfigGetter TunnelConfigJSONGetter) error {
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
}
|
||||
@@ -219,9 +220,10 @@ func quicServer(
|
||||
|
||||
type mockOriginProxyWithRequest struct{}
|
||||
|
||||
func (moc *mockOriginProxyWithRequest) ProxyHTTP(w ResponseWriter, r *http.Request, isWebsocket bool) error {
|
||||
func (moc *mockOriginProxyWithRequest) ProxyHTTP(w ResponseWriter, tr *tracing.TracedRequest, isWebsocket bool) error {
|
||||
// These are a series of crude tests to ensure the headers and http related data is transferred from
|
||||
// metadata.
|
||||
r := tr.Request
|
||||
if r.Method == "" {
|
||||
return errors.New("method not sent")
|
||||
}
|
||||
@@ -478,7 +480,7 @@ func TestBuildHTTPRequest(t *testing.T) {
|
||||
req, err := buildHTTPRequest(test.connectRequest, test.body)
|
||||
assert.NoError(t, err)
|
||||
test.req = test.req.WithContext(req.Context())
|
||||
assert.Equal(t, test.req, req)
|
||||
assert.Equal(t, test.req, req.Request)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -500,7 +502,7 @@ func TestServeUDPSession(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// Establish QUIC connection with edge
|
||||
edgeQUICSessionChan := make(chan quic.Session)
|
||||
edgeQUICSessionChan := make(chan quic.Connection)
|
||||
go func() {
|
||||
earlyListener, err := quic.Listen(udpListener, testTLSServerConfig, testQUICConfig)
|
||||
require.NoError(t, err)
|
||||
@@ -520,7 +522,7 @@ func TestServeUDPSession(t *testing.T) {
|
||||
cancel()
|
||||
}
|
||||
|
||||
func serveSession(ctx context.Context, qc *QUICConnection, edgeQUICSession quic.Session, closeType closeReason, expectedReason string, t *testing.T) {
|
||||
func serveSession(ctx context.Context, qc *QUICConnection, edgeQUICSession quic.Connection, closeType closeReason, expectedReason string, t *testing.T) {
|
||||
var (
|
||||
payload = []byte(t.Name())
|
||||
)
|
||||
@@ -581,7 +583,7 @@ const (
|
||||
closedByTimeout
|
||||
)
|
||||
|
||||
func runRPCServer(ctx context.Context, session quic.Session, sessionRPCServer tunnelpogs.SessionManager, configRPCServer tunnelpogs.ConfigurationManager, t *testing.T) {
|
||||
func runRPCServer(ctx context.Context, session quic.Connection, sessionRPCServer tunnelpogs.SessionManager, configRPCServer tunnelpogs.ConfigurationManager, t *testing.T) {
|
||||
stream, err := session.AcceptStream(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
+24
-7
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
@@ -58,6 +59,11 @@ type NamedTunnelRPCClient interface {
|
||||
options *tunnelpogs.ConnectionOptions,
|
||||
connIndex uint8,
|
||||
observer *Observer,
|
||||
) (*tunnelpogs.ConnectionDetails, error)
|
||||
SendLocalConfiguration(
|
||||
c context.Context,
|
||||
config []byte,
|
||||
observer *Observer,
|
||||
) error
|
||||
GracefulShutdown(ctx context.Context, gracePeriod time.Duration)
|
||||
Close()
|
||||
@@ -90,7 +96,7 @@ func (rsc *registrationServerClient) RegisterConnection(
|
||||
options *tunnelpogs.ConnectionOptions,
|
||||
connIndex uint8,
|
||||
observer *Observer,
|
||||
) error {
|
||||
) (*tunnelpogs.ConnectionDetails, error) {
|
||||
conn, err := rsc.client.RegisterConnection(
|
||||
ctx,
|
||||
properties.Credentials.Auth(),
|
||||
@@ -101,18 +107,29 @@ func (rsc *registrationServerClient) RegisterConnection(
|
||||
if err != nil {
|
||||
if err.Error() == DuplicateConnectionError {
|
||||
observer.metrics.regFail.WithLabelValues("dup_edge_conn", "registerConnection").Inc()
|
||||
return errDuplicationConnection
|
||||
return nil, errDuplicationConnection
|
||||
}
|
||||
observer.metrics.regFail.WithLabelValues("server_error", "registerConnection").Inc()
|
||||
return serverRegistrationErrorFromRPC(err)
|
||||
return nil, serverRegistrationErrorFromRPC(err)
|
||||
}
|
||||
|
||||
observer.metrics.regSuccess.WithLabelValues("registerConnection").Inc()
|
||||
|
||||
observer.logServerInfo(connIndex, conn.Location, fmt.Sprintf("Connection %s registered", conn.UUID))
|
||||
observer.logServerInfo(connIndex, conn.Location, options.OriginLocalIP, fmt.Sprintf("Connection %s registered", conn.UUID))
|
||||
observer.sendConnectedEvent(connIndex, conn.Location)
|
||||
|
||||
return nil
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (rsc *registrationServerClient) SendLocalConfiguration(ctx context.Context, config []byte, observer *Observer) (err error) {
|
||||
observer.metrics.localConfigMetrics.pushes.Inc()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
observer.metrics.localConfigMetrics.pushesErrors.Inc()
|
||||
}
|
||||
}()
|
||||
|
||||
return rsc.client.SendLocalConfiguration(ctx, config)
|
||||
}
|
||||
|
||||
func (rsc *registrationServerClient) GracefulShutdown(ctx context.Context, gracePeriod time.Duration) {
|
||||
@@ -258,7 +275,7 @@ func (h *h2muxConnection) logServerInfo(ctx context.Context, rpcClient *tunnelSe
|
||||
h.observer.log.Err(err).Msg("Failed to retrieve server information")
|
||||
return err
|
||||
}
|
||||
h.observer.logServerInfo(h.connIndex, serverInfo.LocationName, "Connection established")
|
||||
h.observer.logServerInfo(h.connIndex, serverInfo.LocationName, net.IP{}, "Connection established")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -274,7 +291,7 @@ func (h *h2muxConnection) registerNamedTunnel(
|
||||
rpcClient := h.newRPCClientFunc(ctx, stream, h.observer.log)
|
||||
defer rpcClient.Close()
|
||||
|
||||
if err = rpcClient.RegisterConnection(ctx, namedTunnel, connOptions, h.connIndex, h.observer); err != nil {
|
||||
if _, err = rpcClient.RegisterConnection(ctx, namedTunnel, connOptions, h.connIndex, h.observer); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -2,7 +2,9 @@ package datagramsession
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
@@ -12,6 +14,11 @@ import (
|
||||
|
||||
const (
|
||||
requestChanCapacity = 16
|
||||
defaultReqTimeout = time.Second * 5
|
||||
)
|
||||
|
||||
var (
|
||||
errSessionManagerClosed = fmt.Errorf("session manager closed")
|
||||
)
|
||||
|
||||
// Manager defines the APIs to manage sessions from the same transport.
|
||||
@@ -22,29 +29,41 @@ type Manager interface {
|
||||
RegisterSession(ctx context.Context, sessionID uuid.UUID, dstConn io.ReadWriteCloser) (*Session, error)
|
||||
// UnregisterSession stops tracking the session and terminates it
|
||||
UnregisterSession(ctx context.Context, sessionID uuid.UUID, message string, byRemote bool) error
|
||||
// UpdateLogger updates the logger used by the Manager
|
||||
UpdateLogger(log *zerolog.Logger)
|
||||
}
|
||||
|
||||
type manager struct {
|
||||
registrationChan chan *registerSessionEvent
|
||||
unregistrationChan chan *unregisterSessionEvent
|
||||
datagramChan chan *newDatagram
|
||||
closedChan chan struct{}
|
||||
transport transport
|
||||
sessions map[uuid.UUID]*Session
|
||||
log *zerolog.Logger
|
||||
// timeout waiting for an API to finish. This can be overriden in test
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func NewManager(transport transport, log *zerolog.Logger) Manager {
|
||||
func NewManager(transport transport, log *zerolog.Logger) *manager {
|
||||
return &manager{
|
||||
registrationChan: make(chan *registerSessionEvent),
|
||||
unregistrationChan: make(chan *unregisterSessionEvent),
|
||||
// datagramChan is buffered, so it can read more datagrams from transport while the event loop is processing other events
|
||||
datagramChan: make(chan *newDatagram, requestChanCapacity),
|
||||
closedChan: make(chan struct{}),
|
||||
transport: transport,
|
||||
sessions: make(map[uuid.UUID]*Session),
|
||||
log: log,
|
||||
timeout: defaultReqTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manager) UpdateLogger(log *zerolog.Logger) {
|
||||
// Benign data race, no problem if the old pointer is read or not concurrently.
|
||||
m.log = log
|
||||
}
|
||||
|
||||
func (m *manager) Serve(ctx context.Context) error {
|
||||
errGroup, ctx := errgroup.WithContext(ctx)
|
||||
errGroup.Go(func() error {
|
||||
@@ -85,27 +104,67 @@ func (m *manager) Serve(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
})
|
||||
return errGroup.Wait()
|
||||
err := errGroup.Wait()
|
||||
close(m.closedChan)
|
||||
m.shutdownSessions(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *manager) shutdownSessions(err error) {
|
||||
if err == nil {
|
||||
err = errSessionManagerClosed
|
||||
}
|
||||
closeSessionErr := &errClosedSession{
|
||||
message: err.Error(),
|
||||
// Usually connection with remote has been closed, so set this to true to skip unregistering from remote
|
||||
byRemote: true,
|
||||
}
|
||||
for _, s := range m.sessions {
|
||||
s.close(closeSessionErr)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manager) RegisterSession(ctx context.Context, sessionID uuid.UUID, originProxy io.ReadWriteCloser) (*Session, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, m.timeout)
|
||||
defer cancel()
|
||||
event := newRegisterSessionEvent(sessionID, originProxy)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
m.log.Error().Msg("Datagram session registration timeout")
|
||||
return nil, ctx.Err()
|
||||
case m.registrationChan <- event:
|
||||
session := <-event.resultChan
|
||||
return session, nil
|
||||
// Once closedChan is closed, manager won't accept more registration because nothing is
|
||||
// reading from registrationChan and it's an unbuffered channel
|
||||
case <-m.closedChan:
|
||||
return nil, errSessionManagerClosed
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manager) registerSession(ctx context.Context, registration *registerSessionEvent) {
|
||||
session := newSession(registration.sessionID, m.transport, registration.originProxy, m.log)
|
||||
session := m.newSession(registration.sessionID, registration.originProxy)
|
||||
m.sessions[registration.sessionID] = session
|
||||
registration.resultChan <- session
|
||||
}
|
||||
|
||||
func (m *manager) newSession(id uuid.UUID, dstConn io.ReadWriteCloser) *Session {
|
||||
return &Session{
|
||||
ID: id,
|
||||
transport: m.transport,
|
||||
dstConn: dstConn,
|
||||
// activeAtChan has low capacity. It can be full when there are many concurrent read/write. markActive() will
|
||||
// drop instead of blocking because last active time only needs to be an approximation
|
||||
activeAtChan: make(chan time.Time, 2),
|
||||
// capacity is 2 because close() and dstToTransport routine in Serve() can write to this channel
|
||||
closeChan: make(chan error, 2),
|
||||
log: m.log,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manager) UnregisterSession(ctx context.Context, sessionID uuid.UUID, message string, byRemote bool) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, m.timeout)
|
||||
defer cancel()
|
||||
event := &unregisterSessionEvent{
|
||||
sessionID: sessionID,
|
||||
err: &errClosedSession{
|
||||
@@ -115,9 +174,12 @@ func (m *manager) UnregisterSession(ctx context.Context, sessionID uuid.UUID, me
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
m.log.Error().Msg("Datagram session unregistration timeout")
|
||||
return ctx.Err()
|
||||
case m.unregistrationChan <- event:
|
||||
return nil
|
||||
case <-m.closedChan:
|
||||
return errSessionManagerClosed
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -21,12 +22,8 @@ func TestManagerServe(t *testing.T) {
|
||||
msgs = 50
|
||||
remoteUnregisterMsg = "eyeball closed connection"
|
||||
)
|
||||
log := zerolog.Nop()
|
||||
transport := &mockQUICTransport{
|
||||
reqChan: newDatagramChannel(1),
|
||||
respChan: newDatagramChannel(1),
|
||||
}
|
||||
mg := NewManager(transport, &log)
|
||||
|
||||
mg, transport := newTestManager(1)
|
||||
|
||||
eyeballTracker := make(map[uuid.UUID]*datagramChannel)
|
||||
for i := 0; i < sessions; i++ {
|
||||
@@ -120,6 +117,65 @@ func TestManagerServe(t *testing.T) {
|
||||
<-serveDone
|
||||
}
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
const (
|
||||
testTimeout = time.Millisecond * 50
|
||||
)
|
||||
|
||||
mg, _ := newTestManager(1)
|
||||
mg.timeout = testTimeout
|
||||
ctx := context.Background()
|
||||
sessionID := uuid.New()
|
||||
// session manager is not running, so event loop is not running and therefore calling the APIs should timeout
|
||||
session, err := mg.RegisterSession(ctx, sessionID, nil)
|
||||
require.ErrorIs(t, err, context.DeadlineExceeded)
|
||||
require.Nil(t, session)
|
||||
|
||||
err = mg.UnregisterSession(ctx, sessionID, "session gone", true)
|
||||
require.ErrorIs(t, err, context.DeadlineExceeded)
|
||||
}
|
||||
|
||||
func TestCloseTransportCloseSessions(t *testing.T) {
|
||||
mg, transport := newTestManager(1)
|
||||
ctx := context.Background()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := mg.Serve(ctx)
|
||||
require.Error(t, err)
|
||||
}()
|
||||
|
||||
cfdConn, eyeballConn := net.Pipe()
|
||||
session, err := mg.RegisterSession(ctx, uuid.New(), cfdConn)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, session)
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_, err := eyeballConn.Write([]byte(t.Name()))
|
||||
require.NoError(t, err)
|
||||
transport.close()
|
||||
}()
|
||||
|
||||
closedByRemote, err := session.Serve(ctx, time.Minute)
|
||||
require.True(t, closedByRemote)
|
||||
require.Error(t, err)
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func newTestManager(capacity uint) (*manager, *mockQUICTransport) {
|
||||
log := zerolog.Nop()
|
||||
transport := &mockQUICTransport{
|
||||
reqChan: newDatagramChannel(capacity),
|
||||
respChan: newDatagramChannel(capacity),
|
||||
}
|
||||
return NewManager(transport, &log), transport
|
||||
}
|
||||
|
||||
type mockOrigin struct {
|
||||
expectMsgCount int
|
||||
expectedMsg []byte
|
||||
@@ -199,7 +255,10 @@ func (rc *datagramChannel) Send(ctx context.Context, sessionID uuid.UUID, payloa
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-rc.closedChan:
|
||||
return fmt.Errorf("datagram channel closed")
|
||||
return &errClosedSession{
|
||||
message: fmt.Errorf("datagram channel closed").Error(),
|
||||
byRemote: true,
|
||||
}
|
||||
case rc.datagramChan <- &newDatagram{sessionID: sessionID, payload: payload}:
|
||||
return nil
|
||||
}
|
||||
@@ -210,7 +269,11 @@ func (rc *datagramChannel) Receive(ctx context.Context) (uuid.UUID, []byte, erro
|
||||
case <-ctx.Done():
|
||||
return uuid.Nil, nil, ctx.Err()
|
||||
case <-rc.closedChan:
|
||||
return uuid.Nil, nil, fmt.Errorf("datagram channel closed")
|
||||
err := &errClosedSession{
|
||||
message: fmt.Errorf("datagram channel closed").Error(),
|
||||
byRemote: true,
|
||||
}
|
||||
return uuid.Nil, nil, err
|
||||
case msg := <-rc.datagramChan:
|
||||
return msg.sessionID, msg.payload, nil
|
||||
}
|
||||
|
||||
@@ -39,20 +39,6 @@ type Session struct {
|
||||
log *zerolog.Logger
|
||||
}
|
||||
|
||||
func newSession(id uuid.UUID, transport transport, dstConn io.ReadWriteCloser, log *zerolog.Logger) *Session {
|
||||
return &Session{
|
||||
ID: id,
|
||||
transport: transport,
|
||||
dstConn: dstConn,
|
||||
// activeAtChan has low capacity. It can be full when there are many concurrent read/write. markActive() will
|
||||
// drop instead of blocking because last active time only needs to be an approximation
|
||||
activeAtChan: make(chan time.Time, 2),
|
||||
// capacity is 2 because close() and dstToTransport routine in Serve() can write to this channel
|
||||
closeChan: make(chan error, 2),
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) Serve(ctx context.Context, closeAfterIdle time.Duration) (closedByRemote bool, err error) {
|
||||
go func() {
|
||||
// QUIC implementation copies data to another buffer before returning https://github.com/lucas-clemente/quic-go/blob/v0.24.0/session.go#L1967-L1975
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
@@ -41,12 +40,9 @@ func testSessionReturns(t *testing.T, closeBy closeMethod, closeAfterIdle time.D
|
||||
sessionID := uuid.New()
|
||||
cfdConn, originConn := net.Pipe()
|
||||
payload := testPayload(sessionID)
|
||||
transport := &mockQUICTransport{
|
||||
reqChan: newDatagramChannel(1),
|
||||
respChan: newDatagramChannel(1),
|
||||
}
|
||||
log := zerolog.Nop()
|
||||
session := newSession(sessionID, transport, cfdConn, &log)
|
||||
|
||||
mg, _ := newTestManager(1)
|
||||
session := mg.newSession(sessionID, cfdConn)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
sessionDone := make(chan struct{})
|
||||
@@ -117,12 +113,9 @@ func testActiveSessionNotClosed(t *testing.T, readFromDst bool, writeToDst bool)
|
||||
sessionID := uuid.New()
|
||||
cfdConn, originConn := net.Pipe()
|
||||
payload := testPayload(sessionID)
|
||||
transport := &mockQUICTransport{
|
||||
reqChan: newDatagramChannel(100),
|
||||
respChan: newDatagramChannel(100),
|
||||
}
|
||||
log := zerolog.Nop()
|
||||
session := newSession(sessionID, transport, cfdConn, &log)
|
||||
|
||||
mg, _ := newTestManager(100)
|
||||
session := mg.newSession(sessionID, cfdConn)
|
||||
|
||||
startTime := time.Now()
|
||||
activeUntil := startTime.Add(activeTime)
|
||||
@@ -184,7 +177,8 @@ func testActiveSessionNotClosed(t *testing.T, readFromDst bool, writeToDst bool)
|
||||
|
||||
func TestMarkActiveNotBlocking(t *testing.T) {
|
||||
const concurrentCalls = 50
|
||||
session := newSession(uuid.New(), nil, nil, nil)
|
||||
mg, _ := newTestManager(1)
|
||||
session := mg.newSession(uuid.New(), nil)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(concurrentCalls)
|
||||
for i := 0; i < concurrentCalls; i++ {
|
||||
@@ -199,12 +193,9 @@ func TestMarkActiveNotBlocking(t *testing.T) {
|
||||
func TestZeroBytePayload(t *testing.T) {
|
||||
sessionID := uuid.New()
|
||||
cfdConn, originConn := net.Pipe()
|
||||
transport := &mockQUICTransport{
|
||||
reqChan: newDatagramChannel(1),
|
||||
respChan: newDatagramChannel(1),
|
||||
}
|
||||
log := zerolog.Nop()
|
||||
session := newSession(sessionID, transport, cfdConn, &log)
|
||||
|
||||
mg, transport := newTestManager(1)
|
||||
session := mg.newSession(sessionID, cfdConn)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
errGroup, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.17.5 as builder
|
||||
FROM golang:1.17.10 as builder
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0
|
||||
WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||
@@ -6,4 +6,4 @@ RUN apt-get update
|
||||
COPY . .
|
||||
# compile cloudflared
|
||||
RUN make cloudflared
|
||||
RUN cp /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
|
||||
RUN cp /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
|
||||
|
||||
@@ -32,10 +32,40 @@ var (
|
||||
netLookupIP = net.LookupIP
|
||||
)
|
||||
|
||||
// ConfigIPVersion is the selection of IP versions from config
|
||||
type ConfigIPVersion int8
|
||||
|
||||
const (
|
||||
Auto ConfigIPVersion = 2
|
||||
IPv4Only ConfigIPVersion = 4
|
||||
IPv6Only ConfigIPVersion = 6
|
||||
)
|
||||
|
||||
// IPVersion is the IP version of an EdgeAddr
|
||||
type EdgeIPVersion int8
|
||||
|
||||
const (
|
||||
V4 EdgeIPVersion = 4
|
||||
V6 EdgeIPVersion = 6
|
||||
)
|
||||
|
||||
// String returns the enum's constant name.
|
||||
func (c EdgeIPVersion) String() string {
|
||||
switch c {
|
||||
case V4:
|
||||
return "4"
|
||||
case V6:
|
||||
return "6"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// EdgeAddr is a representation of possible ways to refer an edge location.
|
||||
type EdgeAddr struct {
|
||||
TCP *net.TCPAddr
|
||||
UDP *net.UDPAddr
|
||||
TCP *net.TCPAddr
|
||||
UDP *net.UDPAddr
|
||||
IPVersion EdgeIPVersion
|
||||
}
|
||||
|
||||
// If the call to net.LookupSRV fails, try to fall back to DoT from Cloudflare directly.
|
||||
@@ -120,9 +150,14 @@ func resolveSRV(srv *net.SRV) ([]*EdgeAddr, error) {
|
||||
}
|
||||
addrs := make([]*EdgeAddr, len(ips))
|
||||
for i, ip := range ips {
|
||||
version := V6
|
||||
if ip.To4() != nil {
|
||||
version = V4
|
||||
}
|
||||
addrs[i] = &EdgeAddr{
|
||||
TCP: &net.TCPAddr{IP: ip, Port: int(srv.Port)},
|
||||
UDP: &net.UDPAddr{IP: ip, Port: int(srv.Port)},
|
||||
TCP: &net.TCPAddr{IP: ip, Port: int(srv.Port)},
|
||||
UDP: &net.UDPAddr{IP: ip, Port: int(srv.Port)},
|
||||
IPVersion: version,
|
||||
}
|
||||
}
|
||||
return addrs, nil
|
||||
@@ -143,9 +178,14 @@ func ResolveAddrs(addrs []string, log *zerolog.Logger) (resolved []*EdgeAddr) {
|
||||
log.Error().Str(logFieldAddress, addr).Err(err).Msg("failed to resolve to UDP address")
|
||||
continue
|
||||
}
|
||||
version := V6
|
||||
if udpAddr.IP.To4() != nil {
|
||||
version = V4
|
||||
}
|
||||
resolved = append(resolved, &EdgeAddr{
|
||||
TCP: tcpAddr,
|
||||
UDP: udpAddr,
|
||||
TCP: tcpAddr,
|
||||
UDP: udpAddr,
|
||||
IPVersion: version,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func newMockAddrs(port uint16, numRegions uint8, numAddrsPerRegion uint8) mockAd
|
||||
IP: net.ParseIP(fmt.Sprintf("10.0.%v.%v", r, a)),
|
||||
Port: int(port),
|
||||
}
|
||||
addrs = append(addrs, &EdgeAddr{tcpAddr, udpAddr})
|
||||
addrs = append(addrs, &EdgeAddr{tcpAddr, udpAddr, V4})
|
||||
}
|
||||
addrMap[srv] = addrs
|
||||
numAddrs += len(addrs)
|
||||
|
||||
@@ -114,7 +114,8 @@ func (ed *Edge) GetDifferentAddr(connIndex int) (*allregions.EdgeAddr, error) {
|
||||
// note: if oldAddr were not nil, it will become available on the next iteration
|
||||
return nil, errNoAddressesLeft
|
||||
}
|
||||
log.Debug().Msg("edgediscovery - GetDifferentAddr: Giving connection its new address")
|
||||
log.Debug().Msgf("edgediscovery - GetDifferentAddr: Giving connection its new address: %v from the address list: %v",
|
||||
addr, ed.regions.AvailableAddrs())
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,80 +3,80 @@ module github.com/cloudflare/cloudflared
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect
|
||||
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93
|
||||
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc
|
||||
github.com/coredns/coredns v1.8.7
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
|
||||
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 // indirect
|
||||
github.com/facebookgo/grace v0.0.0-20180706040059-75cf19382434
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/gdamore/tcell v1.3.0
|
||||
github.com/getsentry/raven-go v0.0.0-20180517221441-ed7bcb39ff10
|
||||
github.com/gobwas/httphead v0.0.0-20200921212729-da3d93bc3c58 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.0.4
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/lucas-clemente/quic-go v0.24.0
|
||||
github.com/lucas-clemente/quic-go v0.27.1
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
github.com/miekg/dns v1.1.45
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/prometheus/client_golang v1.12.1
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/rivo/tview v0.0.0-20200712113419-c65badfc3d92
|
||||
github.com/rs/zerolog v1.20.0
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
go.opentelemetry.io/contrib/propagators v0.22.0
|
||||
go.opentelemetry.io/otel v1.6.3
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.6.3
|
||||
go.opentelemetry.io/otel/sdk v1.6.3
|
||||
go.opentelemetry.io/otel/trace v1.6.3
|
||||
go.opentelemetry.io/proto/otlp v0.15.0
|
||||
go.uber.org/automaxprocs v1.4.0
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
|
||||
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb // indirect
|
||||
google.golang.org/grpc v1.43.0 // indirect
|
||||
google.golang.org/protobuf v1.28.0
|
||||
gopkg.in/coreos/go-oidc.v2 v2.2.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
zombiezen.com/go/capnproto2 v2.18.0+incompatible
|
||||
)
|
||||
|
||||
require gopkg.in/coreos/go-oidc.v2 v2.2.1
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
github.com/apparentlymart/go-cidr v1.1.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/coredns/caddy v1.1.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
|
||||
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 // indirect
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/gobwas/httphead v0.0.0-20200921212729-da3d93bc3c58 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 // indirect
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.8 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
@@ -84,18 +84,28 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/rivo/uniseg v0.1.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb // indirect
|
||||
google.golang.org/grpc v1.45.0 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/urfave/cli/v2 => github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d
|
||||
|
||||
replace github.com/lucas-clemente/quic-go => github.com/chungthuang/quic-go v0.24.1-0.20220110095058-981dc498cb62
|
||||
replace github.com/lucas-clemente/quic-go => github.com/chungthuang/quic-go v0.27.1-0.20220607112311-13144fbde8da
|
||||
|
||||
// Avoid 'CVE-2022-21698'
|
||||
replace github.com/prometheus/golang_client => github.com/prometheus/golang_client v1.12.1
|
||||
|
||||
@@ -100,17 +100,19 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 h1:JLaf/iINcLyjwbtTsCJjc6rtlASgHeIJPrB6QmwURnA=
|
||||
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/chungthuang/quic-go v0.24.1-0.20220110095058-981dc498cb62 h1:PLTB4iA6sOgAItzQY642tYdcGKfG/7i2gu93JQGgUcM=
|
||||
github.com/chungthuang/quic-go v0.24.1-0.20220110095058-981dc498cb62/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg=
|
||||
github.com/chungthuang/quic-go v0.27.1-0.20220607112311-13144fbde8da h1:FmuwbQ8RU/ftTKnfz5diawqvQFH1KDB9wN2Q8S2wqds=
|
||||
github.com/chungthuang/quic-go v0.27.1-0.20220607112311-13144fbde8da/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
@@ -182,7 +184,6 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
@@ -210,6 +211,11 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||
@@ -232,6 +238,8 @@ github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -282,8 +290,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -334,7 +343,10 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
@@ -393,13 +405,12 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.4 h1:xbHbOGGhrenVtII6Co8akhLEdrawwB2iHl5yhJRpnco=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.0 h1:P9ggrs5xtwiqXv/FHNwntmuLMNq3KaSIG93AtAZ48xk=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 h1:EnzzN9fPUkUck/1CuY1FlzBaIYMoiBsdwTNmNGkwUUM=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.1 h1:DQjHPq+aOzUeh9/lixAGunn6rIOQyWChPSI4+hgW7jc=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.1 h1:qp7p7XXUFL7fpBvSS1sWD+uSqPvzNQK43DH+/qEkj0Y=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
@@ -478,8 +489,9 @@ github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@@ -495,8 +507,9 @@ github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rabbitmq/amqp091-go v1.1.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
@@ -558,8 +571,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
|
||||
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
@@ -584,7 +598,22 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opentelemetry.io/contrib/propagators v0.22.0 h1:KGdv58M2//veiYLIhb31mofaI2LgkIPXXAZVeYVyfd8=
|
||||
go.opentelemetry.io/contrib/propagators v0.22.0/go.mod h1:xGOuXr6lLIF9BXipA4pm6UuOSI0M98U6tsI3khbOiwU=
|
||||
go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM=
|
||||
go.opentelemetry.io/otel v1.6.3 h1:FLOfo8f9JzFVFVyU+MSRJc2HdEAXQgm7pIv2uFKRSZE=
|
||||
go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.6.3/go.mod h1:NEu79Xo32iVb+0gVNV8PMd7GoWqnyDXRlj04yFjqz40=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.6.3 h1:4/UjHWMVVc5VwX/KAtqJOHErKigMCH8NexChMuanb/o=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.6.3/go.mod h1:UJmXdiVVBaZ63umRUTwJuCMAV//GCMvDiQwn703/GoY=
|
||||
go.opentelemetry.io/otel/sdk v1.6.3 h1:prSHYdwCQOX5DrsEzxowH3nLhoAzEBdZhvrR79scfLs=
|
||||
go.opentelemetry.io/otel/sdk v1.6.3/go.mod h1:A4iWF7HTXa+GWL/AaqESz28VuSBIcZ+0CV+IzJ5NMiQ=
|
||||
go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4=
|
||||
go.opentelemetry.io/otel/trace v1.6.3 h1:IqN4L+5b0mPNjdXIiZ90Ni4Bl5BRkDQywePLWemd9bc=
|
||||
go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v0.15.0 h1:h0bKrvdrT/9sBwEJ6iWUqT/N/xPcS66bL4u3isneJ6w=
|
||||
go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
|
||||
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
|
||||
@@ -605,8 +634,9 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -692,6 +722,7 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs=
|
||||
@@ -786,6 +817,7 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -798,8 +830,9 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/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-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -1025,8 +1058,10 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
|
||||
google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
@@ -1040,8 +1075,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/DataDog/dd-trace-go.v1 v1.34.0/go.mod h1:HtrC65fyJ6lWazShCC9rlOeiTSZJ0XtZhkwjZM2WpC4=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -1072,8 +1108,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
+167
-16
@@ -12,10 +12,11 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
defaultConnectTimeout = config.CustomDuration{Duration: 30 * time.Second}
|
||||
defaultTLSTimeout = config.CustomDuration{Duration: 10 * time.Second}
|
||||
defaultTCPKeepAlive = config.CustomDuration{Duration: 30 * time.Second}
|
||||
defaultKeepAliveTimeout = config.CustomDuration{Duration: 90 * time.Second}
|
||||
defaultHTTPConnectTimeout = config.CustomDuration{Duration: 30 * time.Second}
|
||||
defaultWarpRoutingConnectTimeout = config.CustomDuration{Duration: 5 * time.Second}
|
||||
defaultTLSTimeout = config.CustomDuration{Duration: 10 * time.Second}
|
||||
defaultTCPKeepAlive = config.CustomDuration{Duration: 30 * time.Second}
|
||||
defaultKeepAliveTimeout = config.CustomDuration{Duration: 90 * time.Second}
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -35,47 +36,90 @@ const (
|
||||
NoChunkedEncodingFlag = "no-chunked-encoding"
|
||||
ProxyAddressFlag = "proxy-address"
|
||||
ProxyPortFlag = "proxy-port"
|
||||
Http2OriginFlag = "http2-origin"
|
||||
)
|
||||
|
||||
const (
|
||||
socksProxy = "socks"
|
||||
)
|
||||
|
||||
type WarpRoutingConfig struct {
|
||||
Enabled bool `yaml:"enabled" json:"enabled"`
|
||||
ConnectTimeout config.CustomDuration `yaml:"connectTimeout" json:"connectTimeout,omitempty"`
|
||||
TCPKeepAlive config.CustomDuration `yaml:"tcpKeepAlive" json:"tcpKeepAlive,omitempty"`
|
||||
}
|
||||
|
||||
func NewWarpRoutingConfig(raw *config.WarpRoutingConfig) WarpRoutingConfig {
|
||||
cfg := WarpRoutingConfig{
|
||||
Enabled: raw.Enabled,
|
||||
ConnectTimeout: defaultWarpRoutingConnectTimeout,
|
||||
TCPKeepAlive: defaultTCPKeepAlive,
|
||||
}
|
||||
if raw.ConnectTimeout != nil {
|
||||
cfg.ConnectTimeout = *raw.ConnectTimeout
|
||||
}
|
||||
if raw.TCPKeepAlive != nil {
|
||||
cfg.TCPKeepAlive = *raw.TCPKeepAlive
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (c *WarpRoutingConfig) RawConfig() config.WarpRoutingConfig {
|
||||
raw := config.WarpRoutingConfig{
|
||||
Enabled: c.Enabled,
|
||||
}
|
||||
if c.ConnectTimeout.Duration != defaultWarpRoutingConnectTimeout.Duration {
|
||||
raw.ConnectTimeout = &c.ConnectTimeout
|
||||
}
|
||||
if c.TCPKeepAlive.Duration != defaultTCPKeepAlive.Duration {
|
||||
raw.TCPKeepAlive = &c.TCPKeepAlive
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
// RemoteConfig models ingress settings that can be managed remotely, for example through the dashboard.
|
||||
type RemoteConfig struct {
|
||||
Ingress Ingress
|
||||
WarpRouting config.WarpRoutingConfig
|
||||
WarpRouting WarpRoutingConfig
|
||||
}
|
||||
|
||||
type remoteConfigJSON struct {
|
||||
GlobalOriginRequest config.OriginRequestConfig `json:"originRequest"`
|
||||
type RemoteConfigJSON struct {
|
||||
GlobalOriginRequest *config.OriginRequestConfig `json:"originRequest,omitempty"`
|
||||
IngressRules []config.UnvalidatedIngressRule `json:"ingress"`
|
||||
WarpRouting config.WarpRoutingConfig `json:"warp-routing"`
|
||||
}
|
||||
|
||||
func (rc *RemoteConfig) UnmarshalJSON(b []byte) error {
|
||||
var rawConfig remoteConfigJSON
|
||||
var rawConfig RemoteConfigJSON
|
||||
|
||||
if err := json.Unmarshal(b, &rawConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
ingress, err := validateIngress(rawConfig.IngressRules, originRequestFromConfig(rawConfig.GlobalOriginRequest))
|
||||
|
||||
// if nil, just assume the default values.
|
||||
globalOriginRequestConfig := rawConfig.GlobalOriginRequest
|
||||
if globalOriginRequestConfig == nil {
|
||||
globalOriginRequestConfig = &config.OriginRequestConfig{}
|
||||
}
|
||||
|
||||
ingress, err := validateIngress(rawConfig.IngressRules, originRequestFromConfig(*globalOriginRequestConfig))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc.Ingress = ingress
|
||||
rc.WarpRouting = rawConfig.WarpRouting
|
||||
rc.WarpRouting = NewWarpRoutingConfig(&rawConfig.WarpRouting)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
|
||||
var connectTimeout config.CustomDuration = defaultConnectTimeout
|
||||
var tlsTimeout config.CustomDuration = defaultTLSTimeout
|
||||
var tcpKeepAlive config.CustomDuration = defaultTCPKeepAlive
|
||||
var connectTimeout = defaultHTTPConnectTimeout
|
||||
var tlsTimeout = defaultTLSTimeout
|
||||
var tcpKeepAlive = defaultTCPKeepAlive
|
||||
var noHappyEyeballs bool
|
||||
var keepAliveConnections int = defaultKeepAliveConnections
|
||||
var keepAliveTimeout config.CustomDuration = defaultKeepAliveTimeout
|
||||
var keepAliveConnections = defaultKeepAliveConnections
|
||||
var keepAliveTimeout = defaultKeepAliveTimeout
|
||||
var httpHostHeader string
|
||||
var originServerName string
|
||||
var caPool string
|
||||
@@ -85,6 +129,7 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
|
||||
var proxyAddress = defaultProxyAddress
|
||||
var proxyPort uint
|
||||
var proxyType string
|
||||
var http2Origin bool
|
||||
if flag := ProxyConnectTimeoutFlag; c.IsSet(flag) {
|
||||
connectTimeout = config.CustomDuration{Duration: c.Duration(flag)}
|
||||
}
|
||||
@@ -128,9 +173,13 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
|
||||
// Note TUN-3758 , we use Int because UInt is not supported with altsrc
|
||||
proxyPort = uint(c.Int(flag))
|
||||
}
|
||||
if flag := Http2OriginFlag; c.IsSet(flag) {
|
||||
http2Origin = c.Bool(flag)
|
||||
}
|
||||
if c.IsSet(Socks5Flag) {
|
||||
proxyType = socksProxy
|
||||
}
|
||||
|
||||
return OriginRequestConfig{
|
||||
ConnectTimeout: connectTimeout,
|
||||
TLSTimeout: tlsTimeout,
|
||||
@@ -147,12 +196,13 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
|
||||
ProxyAddress: proxyAddress,
|
||||
ProxyPort: proxyPort,
|
||||
ProxyType: proxyType,
|
||||
Http2Origin: http2Origin,
|
||||
}
|
||||
}
|
||||
|
||||
func originRequestFromConfig(c config.OriginRequestConfig) OriginRequestConfig {
|
||||
out := OriginRequestConfig{
|
||||
ConnectTimeout: defaultConnectTimeout,
|
||||
ConnectTimeout: defaultHTTPConnectTimeout,
|
||||
TLSTimeout: defaultTLSTimeout,
|
||||
TCPKeepAlive: defaultTCPKeepAlive,
|
||||
KeepAliveConnections: defaultKeepAliveConnections,
|
||||
@@ -212,6 +262,9 @@ func originRequestFromConfig(c config.OriginRequestConfig) OriginRequestConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.Http2Origin != nil {
|
||||
out.Http2Origin = *c.Http2Origin
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -255,6 +308,8 @@ type OriginRequestConfig struct {
|
||||
ProxyType string `yaml:"proxyType" json:"proxyType"`
|
||||
// IP rules for the proxy service
|
||||
IPRules []ipaccess.Rule `yaml:"ipRules" json:"ipRules"`
|
||||
// Attempt to connect to origin with HTTP/2
|
||||
Http2Origin bool `yaml:"http2Origin" json:"http2Origin"`
|
||||
}
|
||||
|
||||
func (defaults *OriginRequestConfig) setConnectTimeout(overrides config.OriginRequestConfig) {
|
||||
@@ -360,6 +415,12 @@ func (defaults *OriginRequestConfig) setIPRules(overrides config.OriginRequestCo
|
||||
}
|
||||
}
|
||||
|
||||
func (defaults *OriginRequestConfig) setHttp2Origin(overrides config.OriginRequestConfig) {
|
||||
if val := overrides.Http2Origin; val != nil {
|
||||
defaults.Http2Origin = *val
|
||||
}
|
||||
}
|
||||
|
||||
// SetConfig gets config for the requests that cloudflared sends to origins.
|
||||
// Each field has a setter method which sets a value for the field by trying to find:
|
||||
// 1. The user config for this rule
|
||||
@@ -385,5 +446,95 @@ func setConfig(defaults OriginRequestConfig, overrides config.OriginRequestConfi
|
||||
cfg.setProxyAddress(overrides)
|
||||
cfg.setProxyType(overrides)
|
||||
cfg.setIPRules(overrides)
|
||||
cfg.setHttp2Origin(overrides)
|
||||
return cfg
|
||||
}
|
||||
|
||||
func ConvertToRawOriginConfig(c OriginRequestConfig) config.OriginRequestConfig {
|
||||
var connectTimeout *config.CustomDuration
|
||||
var tlsTimeout *config.CustomDuration
|
||||
var tcpKeepAlive *config.CustomDuration
|
||||
var keepAliveConnections *int
|
||||
var keepAliveTimeout *config.CustomDuration
|
||||
var proxyAddress *string
|
||||
|
||||
if c.ConnectTimeout != defaultHTTPConnectTimeout {
|
||||
connectTimeout = &c.ConnectTimeout
|
||||
}
|
||||
if c.TLSTimeout != defaultTLSTimeout {
|
||||
tlsTimeout = &c.TLSTimeout
|
||||
}
|
||||
if c.TCPKeepAlive != defaultTCPKeepAlive {
|
||||
tcpKeepAlive = &c.TCPKeepAlive
|
||||
}
|
||||
if c.KeepAliveConnections != defaultKeepAliveConnections {
|
||||
keepAliveConnections = &c.KeepAliveConnections
|
||||
}
|
||||
if c.KeepAliveTimeout != defaultKeepAliveTimeout {
|
||||
keepAliveTimeout = &c.KeepAliveTimeout
|
||||
}
|
||||
if c.ProxyAddress != defaultProxyAddress {
|
||||
proxyAddress = &c.ProxyAddress
|
||||
}
|
||||
|
||||
return config.OriginRequestConfig{
|
||||
ConnectTimeout: connectTimeout,
|
||||
TLSTimeout: tlsTimeout,
|
||||
TCPKeepAlive: tcpKeepAlive,
|
||||
NoHappyEyeballs: defaultBoolToNil(c.NoHappyEyeballs),
|
||||
KeepAliveConnections: keepAliveConnections,
|
||||
KeepAliveTimeout: keepAliveTimeout,
|
||||
HTTPHostHeader: emptyStringToNil(c.HTTPHostHeader),
|
||||
OriginServerName: emptyStringToNil(c.OriginServerName),
|
||||
CAPool: emptyStringToNil(c.CAPool),
|
||||
NoTLSVerify: defaultBoolToNil(c.NoTLSVerify),
|
||||
DisableChunkedEncoding: defaultBoolToNil(c.DisableChunkedEncoding),
|
||||
BastionMode: defaultBoolToNil(c.BastionMode),
|
||||
ProxyAddress: proxyAddress,
|
||||
ProxyPort: zeroUIntToNil(c.ProxyPort),
|
||||
ProxyType: emptyStringToNil(c.ProxyType),
|
||||
IPRules: convertToRawIPRules(c.IPRules),
|
||||
Http2Origin: defaultBoolToNil(c.Http2Origin),
|
||||
}
|
||||
}
|
||||
|
||||
func convertToRawIPRules(ipRules []ipaccess.Rule) []config.IngressIPRule {
|
||||
result := make([]config.IngressIPRule, 0)
|
||||
for _, r := range ipRules {
|
||||
cidr := r.StringCIDR()
|
||||
|
||||
newRule := config.IngressIPRule{
|
||||
Prefix: &cidr,
|
||||
Ports: r.Ports(),
|
||||
Allow: r.RulePolicy(),
|
||||
}
|
||||
|
||||
result = append(result, newRule)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func defaultBoolToNil(b bool) *bool {
|
||||
if b == false {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &b
|
||||
}
|
||||
|
||||
func emptyStringToNil(s string) *string {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
func zeroUIntToNil(v uint) *uint {
|
||||
if v == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &v
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
@@ -274,7 +274,7 @@ func TestOriginRequestConfigDefaults(t *testing.T) {
|
||||
// Rule 0 didn't override anything, so it inherits the cloudflared defaults
|
||||
actual0 := ing.Rules[0].Config
|
||||
expected0 := OriginRequestConfig{
|
||||
ConnectTimeout: defaultConnectTimeout,
|
||||
ConnectTimeout: defaultHTTPConnectTimeout,
|
||||
TLSTimeout: defaultTLSTimeout,
|
||||
TCPKeepAlive: defaultTCPKeepAlive,
|
||||
KeepAliveConnections: defaultKeepAliveConnections,
|
||||
@@ -404,7 +404,7 @@ func TestDefaultConfigFromCLI(t *testing.T) {
|
||||
c := cli.NewContext(nil, set, nil)
|
||||
|
||||
expected := OriginRequestConfig{
|
||||
ConnectTimeout: defaultConnectTimeout,
|
||||
ConnectTimeout: defaultHTTPConnectTimeout,
|
||||
TLSTimeout: defaultTLSTimeout,
|
||||
TCPKeepAlive: defaultTCPKeepAlive,
|
||||
KeepAliveConnections: defaultKeepAliveConnections,
|
||||
|
||||
+10
-2
@@ -97,8 +97,16 @@ type WarpRoutingService struct {
|
||||
Proxy StreamBasedOriginProxy
|
||||
}
|
||||
|
||||
func NewWarpRoutingService() *WarpRoutingService {
|
||||
return &WarpRoutingService{Proxy: &rawTCPService{name: ServiceWarpRouting}}
|
||||
func NewWarpRoutingService(config WarpRoutingConfig) *WarpRoutingService {
|
||||
svc := &rawTCPService{
|
||||
name: ServiceWarpRouting,
|
||||
dialer: net.Dialer{
|
||||
Timeout: config.ConnectTimeout.Duration,
|
||||
KeepAlive: config.TCPKeepAlive.Duration,
|
||||
},
|
||||
}
|
||||
|
||||
return &WarpRoutingService{Proxy: svc}
|
||||
}
|
||||
|
||||
// Get a single origin service from the CLI/config.
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
|
||||
+16
-15
@@ -1,25 +1,20 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"net"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
errUnsupportedConnectionType = errors.New("internal error: unsupported connection type")
|
||||
)
|
||||
|
||||
// HTTPOriginProxy can be implemented by origin services that want to proxy http requests.
|
||||
type HTTPOriginProxy interface {
|
||||
// RoundTrip is how cloudflared proxies eyeball requests to the actual origin services
|
||||
// RoundTripper is how cloudflared proxies eyeball requests to the actual origin services
|
||||
http.RoundTripper
|
||||
}
|
||||
|
||||
// StreamBasedOriginProxy can be implemented by origin services that want to proxy ws/TCP.
|
||||
type StreamBasedOriginProxy interface {
|
||||
EstablishConnection(dest string) (OriginConnection, error)
|
||||
EstablishConnection(ctx context.Context, dest string) (OriginConnection, error)
|
||||
}
|
||||
|
||||
func (o *unixSocketPath) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
@@ -49,11 +44,17 @@ func (o *httpService) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
}
|
||||
|
||||
func (o *statusCode) RoundTrip(_ *http.Request) (*http.Response, error) {
|
||||
return o.resp, nil
|
||||
resp := &http.Response{
|
||||
StatusCode: o.code,
|
||||
Status: fmt.Sprintf("%d %s", o.code, http.StatusText(o.code)),
|
||||
Body: new(NopReadCloser),
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (o *rawTCPService) EstablishConnection(dest string) (OriginConnection, error) {
|
||||
conn, err := net.Dial("tcp", dest)
|
||||
func (o *rawTCPService) EstablishConnection(ctx context.Context, dest string) (OriginConnection, error) {
|
||||
conn, err := o.dialer.DialContext(ctx, "tcp", dest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -64,13 +65,13 @@ func (o *rawTCPService) EstablishConnection(dest string) (OriginConnection, erro
|
||||
return originConn, nil
|
||||
}
|
||||
|
||||
func (o *tcpOverWSService) EstablishConnection(dest string) (OriginConnection, error) {
|
||||
func (o *tcpOverWSService) EstablishConnection(ctx context.Context, dest string) (OriginConnection, error) {
|
||||
var err error
|
||||
if !o.isBastion {
|
||||
dest = o.dest
|
||||
}
|
||||
|
||||
conn, err := net.Dial("tcp", dest)
|
||||
conn, err := o.dialer.DialContext(ctx, "tcp", dest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -82,6 +83,6 @@ func (o *tcpOverWSService) EstablishConnection(dest string) (OriginConnection, e
|
||||
|
||||
}
|
||||
|
||||
func (o *socksProxyOverWSService) EstablishConnection(dest string) (OriginConnection, error) {
|
||||
func (o *socksProxyOverWSService) EstablishConnection(_ctx context.Context, _dest string) (OriginConnection, error) {
|
||||
return o.conn, nil
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestRawTCPServiceEstablishConnection(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Origin not listening for new connection, should return an error
|
||||
_, err = rawTCPService.EstablishConnection(req.URL.String())
|
||||
_, err = rawTCPService.EstablishConnection(context.Background(), req.URL.String())
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func TestTCPOverWSServiceEstablishConnection(t *testing.T) {
|
||||
t.Run(test.testCase, func(t *testing.T) {
|
||||
if test.expectErr {
|
||||
bastionHost, _ := carrier.ResolveBastionDest(test.req)
|
||||
_, err := test.service.EstablishConnection(bastionHost)
|
||||
_, err := test.service.EstablishConnection(context.Background(), bastionHost)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
})
|
||||
@@ -99,7 +99,7 @@ func TestTCPOverWSServiceEstablishConnection(t *testing.T) {
|
||||
for _, service := range []*tcpOverWSService{newTCPOverWSService(originURL), newBastionService()} {
|
||||
// Origin not listening for new connection, should return an error
|
||||
bastionHost, _ := carrier.ResolveBastionDest(bastionReq)
|
||||
_, err := service.EstablishConnection(bastionHost)
|
||||
_, err := service.EstablishConnection(context.Background(), bastionHost)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
+18
-11
@@ -91,7 +91,8 @@ func (o httpService) MarshalJSON() ([]byte, error) {
|
||||
// rawTCPService dials TCP to the destination specified by the client
|
||||
// It's used by warp routing
|
||||
type rawTCPService struct {
|
||||
name string
|
||||
name string
|
||||
dialer net.Dialer
|
||||
}
|
||||
|
||||
func (o *rawTCPService) String() string {
|
||||
@@ -109,9 +110,11 @@ func (o rawTCPService) MarshalJSON() ([]byte, error) {
|
||||
// tcpOverWSService models TCP origins serving eyeballs connecting over websocket, such as
|
||||
// cloudflared access commands.
|
||||
type tcpOverWSService struct {
|
||||
scheme string
|
||||
dest string
|
||||
isBastion bool
|
||||
streamHandler streamHandlerFunc
|
||||
dialer net.Dialer
|
||||
}
|
||||
|
||||
type socksProxyOverWSService struct {
|
||||
@@ -130,7 +133,8 @@ func newTCPOverWSService(url *url.URL) *tcpOverWSService {
|
||||
addPortIfMissing(url, 7864) // just a random port since there isn't a default in this case
|
||||
}
|
||||
return &tcpOverWSService{
|
||||
dest: url.Host,
|
||||
scheme: url.Scheme,
|
||||
dest: url.Host,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +164,12 @@ func (o *tcpOverWSService) String() string {
|
||||
if o.isBastion {
|
||||
return ServiceBastion
|
||||
}
|
||||
return o.dest
|
||||
|
||||
if o.scheme != "" {
|
||||
return fmt.Sprintf("%s://%s", o.scheme, o.dest)
|
||||
} else {
|
||||
return o.dest
|
||||
}
|
||||
}
|
||||
|
||||
func (o *tcpOverWSService) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRequestConfig) error {
|
||||
@@ -169,6 +178,8 @@ func (o *tcpOverWSService) start(log *zerolog.Logger, _ <-chan struct{}, cfg Ori
|
||||
} else {
|
||||
o.streamHandler = DefaultStreamHandler
|
||||
}
|
||||
o.dialer.Timeout = cfg.ConnectTimeout.Duration
|
||||
o.dialer.KeepAlive = cfg.TCPKeepAlive.Duration
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -231,20 +242,15 @@ func (o helloWorld) MarshalJSON() ([]byte, error) {
|
||||
// statusCode is an OriginService that just responds with a given HTTP status.
|
||||
// Typical use-case is "user wants the catch-all rule to just respond 404".
|
||||
type statusCode struct {
|
||||
resp *http.Response
|
||||
code int
|
||||
}
|
||||
|
||||
func newStatusCode(status int) statusCode {
|
||||
resp := &http.Response{
|
||||
StatusCode: status,
|
||||
Status: fmt.Sprintf("%d %s", status, http.StatusText(status)),
|
||||
Body: new(NopReadCloser),
|
||||
}
|
||||
return statusCode{resp: resp}
|
||||
return statusCode{code: status}
|
||||
}
|
||||
|
||||
func (o *statusCode) String() string {
|
||||
return fmt.Sprintf("http_status:%d", o.resp.StatusCode)
|
||||
return fmt.Sprintf("http_status:%d", o.code)
|
||||
}
|
||||
|
||||
func (o *statusCode) start(
|
||||
@@ -284,6 +290,7 @@ func newHTTPTransport(service OriginService, cfg OriginRequestConfig, log *zerol
|
||||
TLSHandshakeTimeout: cfg.TLSTimeout.Duration,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: &tls.Config{RootCAs: originCertPool, InsecureSkipVerify: cfg.NoTLSVerify},
|
||||
ForceAttemptHTTP2: cfg.Http2Origin,
|
||||
}
|
||||
if _, isHelloWorld := service.(*helloWorld); !isHelloWorld && cfg.OriginServerName != "" {
|
||||
httpTransport.TLSClientConfig.ServerName = cfg.OriginServerName
|
||||
|
||||
@@ -6,11 +6,16 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type UDPProxy struct {
|
||||
type UDPProxy interface {
|
||||
io.ReadWriteCloser
|
||||
LocalAddr() net.Addr
|
||||
}
|
||||
|
||||
func DialUDP(dstIP net.IP, dstPort uint16) (*UDPProxy, error) {
|
||||
type udpProxy struct {
|
||||
*net.UDPConn
|
||||
}
|
||||
|
||||
func DialUDP(dstIP net.IP, dstPort uint16) (UDPProxy, error) {
|
||||
dstAddr := &net.UDPAddr{
|
||||
IP: dstIP,
|
||||
Port: int(dstPort),
|
||||
@@ -23,5 +28,5 @@ func DialUDP(dstIP net.IP, dstPort uint16) (*UDPProxy, error) {
|
||||
return nil, fmt.Errorf("unable to create UDP proxy to origin (%v:%v): %w", dstIP, dstPort, err)
|
||||
}
|
||||
|
||||
return &UDPProxy{udpConn}, nil
|
||||
return &udpProxy{udpConn}, nil
|
||||
}
|
||||
|
||||
@@ -182,25 +182,25 @@ func TestMarshalJSON(t *testing.T) {
|
||||
{
|
||||
name: "Nil",
|
||||
path: nil,
|
||||
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`,
|
||||
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false}}`,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Nil regex",
|
||||
path: &Regexp{Regexp: nil},
|
||||
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`,
|
||||
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false}}`,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Empty",
|
||||
path: &Regexp{Regexp: regexp.MustCompile("")},
|
||||
expected: `{"hostname":"example.com","path":"","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`,
|
||||
expected: `{"hostname":"example.com","path":"","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false}}`,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Basic",
|
||||
path: &Regexp{Regexp: regexp.MustCompile("/echo")},
|
||||
expected: `{"hostname":"example.com","path":"/echo","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`,
|
||||
expected: `{"hostname":"example.com","path":"/echo","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false}}`,
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -99,3 +99,15 @@ func (ipr *Rule) PortsString() string {
|
||||
}
|
||||
return "all"
|
||||
}
|
||||
|
||||
func (ipr *Rule) Ports() []int {
|
||||
return ipr.ports
|
||||
}
|
||||
|
||||
func (ipr *Rule) RulePolicy() bool {
|
||||
return ipr.allow
|
||||
}
|
||||
|
||||
func (ipr *Rule) StringCIDR() string {
|
||||
return ipr.ipNet.String()
|
||||
}
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ const (
|
||||
)
|
||||
|
||||
type orchestrator interface {
|
||||
GetConfigJSON() ([]byte, error)
|
||||
GetVersionedConfigJSON() ([]byte, error)
|
||||
}
|
||||
|
||||
func newMetricsHandler(
|
||||
@@ -47,7 +47,7 @@ func newMetricsHandler(
|
||||
})
|
||||
if orchestrator != nil {
|
||||
router.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) {
|
||||
json, err := orchestrator.GetConfigJSON()
|
||||
json, err := orchestrator.GetVersionedConfigJSON()
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
_, _ = fmt.Fprintf(w, "ERR: %v", err)
|
||||
|
||||
+12
-7
@@ -5,21 +5,24 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
conn "github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/cloudflare/cloudflared/tunnelstate"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// ReadyServer serves HTTP 200 if the tunnel can serve traffic. Intended for k8s readiness checks.
|
||||
type ReadyServer struct {
|
||||
tracker *tunnelstate.ConnTracker
|
||||
clientID uuid.UUID
|
||||
tracker *tunnelstate.ConnTracker
|
||||
}
|
||||
|
||||
// NewReadyServer initializes a ReadyServer and starts listening for dis/connection events.
|
||||
func NewReadyServer(log *zerolog.Logger) *ReadyServer {
|
||||
func NewReadyServer(log *zerolog.Logger, clientID uuid.UUID) *ReadyServer {
|
||||
return &ReadyServer{
|
||||
tracker: tunnelstate.NewConnTracker(log),
|
||||
clientID: clientID,
|
||||
tracker: tunnelstate.NewConnTracker(log),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +31,9 @@ func (rs *ReadyServer) OnTunnelEvent(c conn.Event) {
|
||||
}
|
||||
|
||||
type body struct {
|
||||
Status int `json:"status"`
|
||||
ReadyConnections uint `json:"readyConnections"`
|
||||
Status int `json:"status"`
|
||||
ReadyConnections uint `json:"readyConnections"`
|
||||
ConnectorID uuid.UUID `json:"connectorId"`
|
||||
}
|
||||
|
||||
// ServeHTTP responds with HTTP 200 if the tunnel is connected to the edge.
|
||||
@@ -39,6 +43,7 @@ func (rs *ReadyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
body := body{
|
||||
Status: statusCode,
|
||||
ReadyConnections: readyConnections,
|
||||
ConnectorID: rs.clientID,
|
||||
}
|
||||
msg, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelstate"
|
||||
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/cloudflare/cloudflared/tunnelstate"
|
||||
)
|
||||
|
||||
func TestReadyServer_makeResponse(t *testing.T) {
|
||||
@@ -66,7 +66,7 @@ func TestReadyServer_makeResponse(t *testing.T) {
|
||||
|
||||
func TestReadinessEventHandling(t *testing.T) {
|
||||
nopLogger := zerolog.Nop()
|
||||
rs := NewReadyServer(&nopLogger)
|
||||
rs := NewReadyServer(&nopLogger, uuid.Nil)
|
||||
|
||||
// start not ok
|
||||
code, ready := rs.makeResponse()
|
||||
|
||||
+55
-4
@@ -1,15 +1,66 @@
|
||||
package orchestration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
)
|
||||
|
||||
type newConfig struct {
|
||||
type newRemoteConfig struct {
|
||||
ingress.RemoteConfig
|
||||
// Add more fields when we support other settings in tunnel orchestration
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Ingress *ingress.Ingress
|
||||
WarpRoutingEnabled bool
|
||||
type newLocalConfig struct {
|
||||
RemoteConfig ingress.RemoteConfig
|
||||
ConfigurationFlags map[string]string `json:"__configuration_flags,omitempty"`
|
||||
}
|
||||
|
||||
// Config is the original config as read and parsed by cloudflared.
|
||||
type Config struct {
|
||||
Ingress *ingress.Ingress
|
||||
WarpRouting ingress.WarpRoutingConfig
|
||||
|
||||
// Extra settings used to configure this instance but that are not eligible for remotely management
|
||||
// ie. (--protocol, --loglevel, ...)
|
||||
ConfigurationFlags map[string]string
|
||||
}
|
||||
|
||||
func (rc *newLocalConfig) MarshalJSON() ([]byte, error) {
|
||||
var r = struct {
|
||||
ConfigurationFlags map[string]string `json:"__configuration_flags,omitempty"`
|
||||
ingress.RemoteConfigJSON
|
||||
}{
|
||||
ConfigurationFlags: rc.ConfigurationFlags,
|
||||
RemoteConfigJSON: ingress.RemoteConfigJSON{
|
||||
// UI doesn't support top level configs, so we reconcile to individual ingress configs.
|
||||
GlobalOriginRequest: nil,
|
||||
IngressRules: convertToUnvalidatedIngressRules(rc.RemoteConfig.Ingress),
|
||||
WarpRouting: rc.RemoteConfig.WarpRouting.RawConfig(),
|
||||
},
|
||||
}
|
||||
|
||||
return json.Marshal(r)
|
||||
}
|
||||
|
||||
func convertToUnvalidatedIngressRules(i ingress.Ingress) []config.UnvalidatedIngressRule {
|
||||
result := make([]config.UnvalidatedIngressRule, 0)
|
||||
for _, rule := range i.Rules {
|
||||
var path string
|
||||
if rule.Path != nil {
|
||||
path = rule.Path.String()
|
||||
}
|
||||
|
||||
newRule := config.UnvalidatedIngressRule{
|
||||
Hostname: rule.Hostname,
|
||||
Path: path,
|
||||
Service: rule.Service.String(),
|
||||
OriginRequest: ingress.ConvertToRawOriginConfig(rule.Config),
|
||||
}
|
||||
|
||||
result = append(result, newRule)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package orchestration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
)
|
||||
|
||||
// TestNewLocalConfig_MarshalJSON tests that we are able to converte a compiled and validated config back
|
||||
// into an "unvalidated" format which is compatible with Remote Managed configurations.
|
||||
func TestNewLocalConfig_MarshalJSON(t *testing.T) {
|
||||
rawConfig := []byte(`
|
||||
{
|
||||
"originRequest": {
|
||||
"connectTimeout": 160,
|
||||
"httpHostHeader": "default"
|
||||
},
|
||||
"ingress": [
|
||||
{
|
||||
"hostname": "tun.example.com",
|
||||
"service": "https://localhost:8000"
|
||||
},
|
||||
{
|
||||
"hostname": "*",
|
||||
"service": "https://localhost:8001",
|
||||
"originRequest": {
|
||||
"connectTimeout": 121,
|
||||
"tlsTimeout": 2,
|
||||
"noHappyEyeballs": false,
|
||||
"tcpKeepAlive": 2,
|
||||
"keepAliveConnections": 2,
|
||||
"keepAliveTimeout": 2,
|
||||
"httpHostHeader": "def",
|
||||
"originServerName": "b2",
|
||||
"caPool": "/tmp/path1",
|
||||
"noTLSVerify": false,
|
||||
"disableChunkedEncoding": false,
|
||||
"bastionMode": false,
|
||||
"proxyAddress": "interface",
|
||||
"proxyPort": 200,
|
||||
"proxyType": "",
|
||||
"ipRules": [
|
||||
{
|
||||
"prefix": "10.0.0.0/16",
|
||||
"ports": [3000, 3030],
|
||||
"allow": false
|
||||
},
|
||||
{
|
||||
"prefix": "192.16.0.0/24",
|
||||
"ports": [5000, 5050],
|
||||
"allow": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"warp-routing": {
|
||||
"enabled": true,
|
||||
"connectTimeout": 1
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
var expectedConfig ingress.RemoteConfig
|
||||
err := json.Unmarshal(rawConfig, &expectedConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
c := &newLocalConfig{
|
||||
RemoteConfig: expectedConfig,
|
||||
ConfigurationFlags: nil,
|
||||
}
|
||||
|
||||
jsonSerde, err := json.Marshal(c)
|
||||
require.NoError(t, err)
|
||||
|
||||
var remoteConfig ingress.RemoteConfig
|
||||
err = json.Unmarshal(jsonSerde, &remoteConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, remoteConfig.WarpRouting, ingress.WarpRoutingConfig{
|
||||
Enabled: true,
|
||||
ConnectTimeout: config.CustomDuration{
|
||||
Duration: time.Second,
|
||||
},
|
||||
TCPKeepAlive: config.CustomDuration{
|
||||
Duration: 30 * time.Second, // default value is 30 seconds
|
||||
},
|
||||
})
|
||||
require.Equal(t, remoteConfig.Ingress.Rules, expectedConfig.Ingress.Rules)
|
||||
}
|
||||
@@ -47,14 +47,14 @@ func NewOrchestrator(ctx context.Context, config *Config, tags []tunnelpogs.Tag,
|
||||
log: log,
|
||||
shutdownC: ctx.Done(),
|
||||
}
|
||||
if err := o.updateIngress(*config.Ingress, config.WarpRoutingEnabled); err != nil {
|
||||
if err := o.updateIngress(*config.Ingress, config.WarpRouting); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go o.waitToCloseLastProxy()
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// Update creates a new proxy with the new ingress rules
|
||||
// UpdateConfig creates a new proxy with the new ingress rules
|
||||
func (o *Orchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.UpdateConfigurationResponse {
|
||||
o.lock.Lock()
|
||||
defer o.lock.Unlock()
|
||||
@@ -63,12 +63,12 @@ func (o *Orchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.Up
|
||||
o.log.Debug().
|
||||
Int32("current_version", o.currentVersion).
|
||||
Int32("received_version", version).
|
||||
Msg("Current version is equal or newer than receivied version")
|
||||
Msg("Current version is equal or newer than received version")
|
||||
return &tunnelpogs.UpdateConfigurationResponse{
|
||||
LastAppliedVersion: o.currentVersion,
|
||||
}
|
||||
}
|
||||
var newConf newConfig
|
||||
var newConf newRemoteConfig
|
||||
if err := json.Unmarshal(config, &newConf); err != nil {
|
||||
o.log.Err(err).
|
||||
Int32("version", version).
|
||||
@@ -80,7 +80,7 @@ func (o *Orchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.Up
|
||||
}
|
||||
}
|
||||
|
||||
if err := o.updateIngress(newConf.Ingress, newConf.WarpRouting.Enabled); err != nil {
|
||||
if err := o.updateIngress(newConf.Ingress, newConf.WarpRouting); err != nil {
|
||||
o.log.Err(err).
|
||||
Int32("version", version).
|
||||
Str("config", string(config)).
|
||||
@@ -103,7 +103,7 @@ func (o *Orchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.Up
|
||||
}
|
||||
|
||||
// The caller is responsible to make sure there is no concurrent access
|
||||
func (o *Orchestrator) updateIngress(ingressRules ingress.Ingress, warpRoutingEnabled bool) error {
|
||||
func (o *Orchestrator) updateIngress(ingressRules ingress.Ingress, warpRouting ingress.WarpRoutingConfig) error {
|
||||
select {
|
||||
case <-o.shutdownC:
|
||||
return fmt.Errorf("cloudflared already shutdown")
|
||||
@@ -118,10 +118,10 @@ func (o *Orchestrator) updateIngress(ingressRules ingress.Ingress, warpRoutingEn
|
||||
if err := ingressRules.StartOrigins(o.log, proxyShutdownC); err != nil {
|
||||
return errors.Wrap(err, "failed to start origin")
|
||||
}
|
||||
newProxy := proxy.NewOriginProxy(ingressRules, warpRoutingEnabled, o.tags, o.log)
|
||||
newProxy := proxy.NewOriginProxy(ingressRules, warpRouting, o.tags, o.log)
|
||||
o.proxy.Store(newProxy)
|
||||
o.config.Ingress = &ingressRules
|
||||
o.config.WarpRoutingEnabled = warpRoutingEnabled
|
||||
o.config.WarpRouting = warpRouting
|
||||
|
||||
// If proxyShutdownC is nil, there is no previous running proxy
|
||||
if o.proxyShutdownC != nil {
|
||||
@@ -131,10 +131,26 @@ func (o *Orchestrator) updateIngress(ingressRules ingress.Ingress, warpRoutingEn
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfigJSON returns the current version and configuration as JSON
|
||||
// GetConfigJSON returns the current json serialization of the config as the edge understands it
|
||||
func (o *Orchestrator) GetConfigJSON() ([]byte, error) {
|
||||
o.lock.RLock()
|
||||
defer o.lock.RUnlock()
|
||||
|
||||
c := &newLocalConfig{
|
||||
RemoteConfig: ingress.RemoteConfig{
|
||||
Ingress: *o.config.Ingress,
|
||||
WarpRouting: o.config.WarpRouting,
|
||||
},
|
||||
ConfigurationFlags: o.config.ConfigurationFlags,
|
||||
}
|
||||
|
||||
return json.Marshal(c)
|
||||
}
|
||||
|
||||
// GetVersionedConfigJSON returns the current version and configuration as JSON
|
||||
func (o *Orchestrator) GetVersionedConfigJSON() ([]byte, error) {
|
||||
o.lock.RLock()
|
||||
defer o.lock.RUnlock()
|
||||
var currentConfiguration = struct {
|
||||
Version int32 `json:"version"`
|
||||
Config struct {
|
||||
@@ -150,7 +166,7 @@ func (o *Orchestrator) GetConfigJSON() ([]byte, error) {
|
||||
OriginRequest ingress.OriginRequestConfig `json:"originRequest"`
|
||||
}{
|
||||
Ingress: o.config.Ingress.Rules,
|
||||
WarpRouting: config.WarpRoutingConfig{Enabled: o.config.WarpRoutingEnabled},
|
||||
WarpRouting: o.config.WarpRouting.RawConfig(),
|
||||
OriginRequest: o.config.Ingress.Defaults,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package orchestration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
"github.com/cloudflare/cloudflared/proxy"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
@@ -46,8 +48,7 @@ var (
|
||||
// - receiving an old version is noop
|
||||
func TestUpdateConfiguration(t *testing.T) {
|
||||
initConfig := &Config{
|
||||
Ingress: &ingress.Ingress{},
|
||||
WarpRoutingEnabled: false,
|
||||
Ingress: &ingress.Ingress{},
|
||||
}
|
||||
orchestrator, err := NewOrchestrator(context.Background(), initConfig, testTags, &testLogger)
|
||||
require.NoError(t, err)
|
||||
@@ -85,7 +86,8 @@ func TestUpdateConfiguration(t *testing.T) {
|
||||
}
|
||||
],
|
||||
"warp-routing": {
|
||||
"enabled": true
|
||||
"enabled": true,
|
||||
"connectTimeout": 10
|
||||
}
|
||||
}
|
||||
`)
|
||||
@@ -119,7 +121,8 @@ func TestUpdateConfiguration(t *testing.T) {
|
||||
require.Equal(t, config.CustomDuration{Duration: time.Second * 90}, configV2.Ingress.Rules[2].Config.ConnectTimeout)
|
||||
require.Equal(t, false, configV2.Ingress.Rules[2].Config.NoTLSVerify)
|
||||
require.Equal(t, true, configV2.Ingress.Rules[2].Config.NoHappyEyeballs)
|
||||
require.True(t, configV2.WarpRoutingEnabled)
|
||||
require.True(t, configV2.WarpRouting.Enabled)
|
||||
require.Equal(t, configV2.WarpRouting.ConnectTimeout.Duration, 10*time.Second)
|
||||
|
||||
originProxyV2, err := orchestrator.GetOriginProxy()
|
||||
require.NoError(t, err)
|
||||
@@ -162,7 +165,7 @@ func TestUpdateConfiguration(t *testing.T) {
|
||||
require.Len(t, configV10.Ingress.Rules, 1)
|
||||
require.True(t, configV10.Ingress.Rules[0].Matches("blogs.tunnel.io", "/2022/02/10"))
|
||||
require.Equal(t, ingress.HelloWorldService, configV10.Ingress.Rules[0].Service.String())
|
||||
require.False(t, configV10.WarpRoutingEnabled)
|
||||
require.False(t, configV10.WarpRouting.Enabled)
|
||||
|
||||
originProxyV10, err := orchestrator.GetOriginProxy()
|
||||
require.NoError(t, err)
|
||||
@@ -244,12 +247,14 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
|
||||
appliedV2 = make(chan struct{})
|
||||
|
||||
initConfig = &Config{
|
||||
Ingress: &ingress.Ingress{},
|
||||
WarpRoutingEnabled: false,
|
||||
Ingress: &ingress.Ingress{},
|
||||
}
|
||||
)
|
||||
|
||||
orchestrator, err := NewOrchestrator(context.Background(), initConfig, testTags, &testLogger)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
orchestrator, err := NewOrchestrator(ctx, initConfig, testTags, &testLogger)
|
||||
require.NoError(t, err)
|
||||
|
||||
updateWithValidation(t, orchestrator, 1, configJSONV1)
|
||||
@@ -265,8 +270,9 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func(i int, originProxy connection.OriginProxy) {
|
||||
defer wg.Done()
|
||||
resp, err := proxyHTTP(t, originProxy, hostname)
|
||||
require.NoError(t, err)
|
||||
resp, err := proxyHTTP(originProxy, hostname)
|
||||
require.NoError(t, err, "proxyHTTP %d failed %v", i, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
var warpRoutingDisabled bool
|
||||
// The response can be from initOrigin, http_status:204 or http_status:418
|
||||
@@ -290,7 +296,7 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
|
||||
// Once we have originProxy, it won't be changed by configuration updates.
|
||||
// We can infer the version by the ProxyHTTP response code
|
||||
pr, pw := io.Pipe()
|
||||
// concurrentRespWriter makes sure ResponseRecorder is not read/write concurrently, and read waits for the first write
|
||||
|
||||
w := newRespReadWriteFlusher()
|
||||
|
||||
// Write TCP message and make sure it's echo back. This has to be done in a go routune since ProxyTCP doesn't
|
||||
@@ -303,7 +309,14 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
|
||||
tcpEyeball(t, pw, tcpBody, w)
|
||||
}()
|
||||
}
|
||||
proxyTCP(t, originProxy, tcpOrigin.Addr().String(), w, pr, warpRoutingDisabled)
|
||||
|
||||
err = proxyTCP(ctx, originProxy, tcpOrigin.Addr().String(), w, pr)
|
||||
if warpRoutingDisabled {
|
||||
require.Error(t, err, "expect proxyTCP %d to return error", i)
|
||||
} else {
|
||||
require.NoError(t, err, "proxyTCP %d failed %v", i, err)
|
||||
}
|
||||
|
||||
}(i, originProxy)
|
||||
|
||||
if i == concurrentRequests/4 {
|
||||
@@ -319,6 +332,7 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
// Makes sure v2 is applied before v3
|
||||
<-appliedV2
|
||||
updateWithValidation(t, orchestrator, 3, configJSONV3)
|
||||
}()
|
||||
@@ -328,16 +342,20 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func proxyHTTP(t *testing.T, originProxy connection.OriginProxy, hostname string) (*http.Response, error) {
|
||||
func proxyHTTP(originProxy connection.OriginProxy, hostname string) (*http.Response, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s", hostname), nil)
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
log := zerolog.Nop()
|
||||
respWriter, err := connection.NewHTTP2RespWriter(req, w, connection.TypeHTTP, &log)
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = originProxy.ProxyHTTP(respWriter, req, false)
|
||||
err = originProxy.ProxyHTTP(respWriter, tracing.NewTracedRequest(req), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -356,13 +374,17 @@ func tcpEyeball(t *testing.T, reqWriter io.WriteCloser, body string, respReadWri
|
||||
require.Equal(t, writeN, n)
|
||||
}
|
||||
|
||||
func proxyTCP(t *testing.T, originProxy connection.OriginProxy, originAddr string, w http.ResponseWriter, reqBody io.ReadCloser, expectErr bool) {
|
||||
func proxyTCP(ctx context.Context, originProxy connection.OriginProxy, originAddr string, w http.ResponseWriter, reqBody io.ReadCloser) error {
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s", originAddr), reqBody)
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log := zerolog.Nop()
|
||||
respWriter, err := connection.NewHTTP2RespWriter(req, w, connection.TypeTCP, &log)
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tcpReq := &connection.TCPRequest{
|
||||
Dest: originAddr,
|
||||
@@ -370,12 +392,8 @@ func proxyTCP(t *testing.T, originProxy connection.OriginProxy, originAddr strin
|
||||
LBProbe: false,
|
||||
}
|
||||
rws := connection.NewHTTPResponseReadWriterAcker(respWriter, req)
|
||||
if expectErr {
|
||||
require.Error(t, originProxy.ProxyTCP(context.Background(), rws, tcpReq))
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, originProxy.ProxyTCP(context.Background(), rws, tcpReq))
|
||||
return originProxy.ProxyTCP(ctx, rws, tcpReq)
|
||||
}
|
||||
|
||||
func serveTCPOrigin(t *testing.T, tcpOrigin net.Listener, wg *sync.WaitGroup) {
|
||||
@@ -458,8 +476,7 @@ func TestClosePreviousProxies(t *testing.T) {
|
||||
}
|
||||
`)
|
||||
initConfig = &Config{
|
||||
Ingress: &ingress.Ingress{},
|
||||
WarpRoutingEnabled: false,
|
||||
Ingress: &ingress.Ingress{},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -471,7 +488,7 @@ func TestClosePreviousProxies(t *testing.T) {
|
||||
|
||||
originProxyV1, err := orchestrator.GetOriginProxy()
|
||||
require.NoError(t, err)
|
||||
resp, err := proxyHTTP(t, originProxyV1, hostname)
|
||||
resp, err := proxyHTTP(originProxyV1, hostname)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
@@ -479,12 +496,12 @@ func TestClosePreviousProxies(t *testing.T) {
|
||||
|
||||
originProxyV2, err := orchestrator.GetOriginProxy()
|
||||
require.NoError(t, err)
|
||||
resp, err = proxyHTTP(t, originProxyV2, hostname)
|
||||
resp, err = proxyHTTP(originProxyV2, hostname)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusTeapot, resp.StatusCode)
|
||||
|
||||
// The hello-world server in config v1 should have been stopped
|
||||
resp, err = proxyHTTP(t, originProxyV1, hostname)
|
||||
resp, err = proxyHTTP(originProxyV1, hostname)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, resp)
|
||||
|
||||
@@ -495,7 +512,7 @@ func TestClosePreviousProxies(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, originProxyV1, originProxyV3)
|
||||
|
||||
resp, err = proxyHTTP(t, originProxyV3, hostname)
|
||||
resp, err = proxyHTTP(originProxyV3, hostname)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
@@ -504,7 +521,7 @@ func TestClosePreviousProxies(t *testing.T) {
|
||||
// Wait for proxies to shutdown
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
|
||||
resp, err = proxyHTTP(t, originProxyV3, hostname)
|
||||
resp, err = proxyHTTP(originProxyV3, hostname)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, resp)
|
||||
}
|
||||
@@ -516,8 +533,7 @@ func TestPersistentConnection(t *testing.T) {
|
||||
)
|
||||
msg := t.Name()
|
||||
initConfig := &Config{
|
||||
Ingress: &ingress.Ingress{},
|
||||
WarpRoutingEnabled: false,
|
||||
Ingress: &ingress.Ingress{},
|
||||
}
|
||||
orchestrator, err := NewOrchestrator(context.Background(), initConfig, testTags, &testLogger)
|
||||
require.NoError(t, err)
|
||||
@@ -553,6 +569,9 @@ func TestPersistentConnection(t *testing.T) {
|
||||
tcpReqReader, tcpReqWriter := io.Pipe()
|
||||
tcpRespReadWriter := newRespReadWriteFlusher()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(3)
|
||||
// Start TCP origin
|
||||
@@ -570,7 +589,7 @@ func TestPersistentConnection(t *testing.T) {
|
||||
// Simulate cloudflared recieving a TCP connection
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
proxyTCP(t, originProxy, tcpOrigin.Addr().String(), tcpRespReadWriter, tcpReqReader, false)
|
||||
require.NoError(t, proxyTCP(ctx, originProxy, tcpOrigin.Addr().String(), tcpRespReadWriter, tcpReqReader))
|
||||
}()
|
||||
// Simulate cloudflared recieving a WS connection
|
||||
go func() {
|
||||
@@ -585,7 +604,7 @@ func TestPersistentConnection(t *testing.T) {
|
||||
respWriter, err := connection.NewHTTP2RespWriter(req, wsRespReadWriter, connection.TypeWebsocket, &log)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = originProxy.ProxyHTTP(respWriter, req, true)
|
||||
err = originProxy.ProxyHTTP(respWriter, tracing.NewTracedRequest(req), true)
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
@@ -621,6 +640,18 @@ func TestPersistentConnection(t *testing.T) {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSerializeLocalConfig(t *testing.T) {
|
||||
c := &newLocalConfig{
|
||||
RemoteConfig: ingress.RemoteConfig{
|
||||
Ingress: ingress.Ingress{},
|
||||
},
|
||||
ConfigurationFlags: map[string]string{"a": "b"},
|
||||
}
|
||||
|
||||
result, _ := json.Marshal(c)
|
||||
fmt.Println(string(result))
|
||||
}
|
||||
|
||||
func wsEcho(w http.ResponseWriter, r *http.Request) {
|
||||
upgrader := gows.Upgrader{}
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type bufferPool struct {
|
||||
// A bufferPool must not be copied after first use.
|
||||
// https://golang.org/pkg/sync/#Pool
|
||||
buffers sync.Pool
|
||||
}
|
||||
|
||||
func newBufferPool(bufferSize int) *bufferPool {
|
||||
return &bufferPool{
|
||||
buffers: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, bufferSize)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *bufferPool) Get() []byte {
|
||||
return p.buffers.Get().([]byte)
|
||||
}
|
||||
|
||||
func (p *bufferPool) Put(buf []byte) {
|
||||
p.buffers.Put(buf)
|
||||
}
|
||||
+47
-31
@@ -10,10 +10,14 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/cloudflare/cloudflared/carrier"
|
||||
"github.com/cloudflare/cloudflared/cfio"
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/websocket"
|
||||
)
|
||||
@@ -24,6 +28,7 @@ const (
|
||||
LogFieldCFRay = "cfRay"
|
||||
LogFieldRule = "ingressRule"
|
||||
LogFieldOriginService = "originService"
|
||||
LogFieldFlowID = "flowID"
|
||||
)
|
||||
|
||||
// Proxy represents a means to Proxy between cloudflared and the origin services.
|
||||
@@ -32,13 +37,12 @@ type Proxy struct {
|
||||
warpRouting *ingress.WarpRoutingService
|
||||
tags []tunnelpogs.Tag
|
||||
log *zerolog.Logger
|
||||
bufferPool *bufferPool
|
||||
}
|
||||
|
||||
// NewOriginProxy returns a new instance of the Proxy struct.
|
||||
func NewOriginProxy(
|
||||
ingressRules ingress.Ingress,
|
||||
warpRoutingEnabled bool,
|
||||
warpRouting ingress.WarpRoutingConfig,
|
||||
tags []tunnelpogs.Tag,
|
||||
log *zerolog.Logger,
|
||||
) *Proxy {
|
||||
@@ -46,10 +50,9 @@ func NewOriginProxy(
|
||||
ingressRules: ingressRules,
|
||||
tags: tags,
|
||||
log: log,
|
||||
bufferPool: newBufferPool(512 * 1024),
|
||||
}
|
||||
if warpRoutingEnabled {
|
||||
proxy.warpRouting = ingress.NewWarpRoutingService()
|
||||
if warpRouting.Enabled {
|
||||
proxy.warpRouting = ingress.NewWarpRoutingService(warpRouting)
|
||||
log.Info().Msgf("Warp-routing is enabled")
|
||||
}
|
||||
|
||||
@@ -60,16 +63,19 @@ func NewOriginProxy(
|
||||
// a simple roundtrip or a tcp/websocket dial depending on ingres rule setup.
|
||||
func (p *Proxy) ProxyHTTP(
|
||||
w connection.ResponseWriter,
|
||||
req *http.Request,
|
||||
tr *tracing.TracedRequest,
|
||||
isWebsocket bool,
|
||||
) error {
|
||||
incrementRequests()
|
||||
defer decrementConcurrentRequests()
|
||||
|
||||
req := tr.Request
|
||||
cfRay := connection.FindCfRayHeader(req)
|
||||
lbProbe := connection.IsLBProbeRequest(req)
|
||||
p.appendTagHeaders(req)
|
||||
|
||||
_, ruleSpan := tr.Tracer().Start(req.Context(), "ingress_match",
|
||||
trace.WithAttributes(attribute.String("req-host", req.Host)))
|
||||
rule, ruleNum := p.ingressRules.FindMatchingRule(req.Host, req.URL.Path)
|
||||
logFields := logFields{
|
||||
cfRay: cfRay,
|
||||
@@ -77,23 +83,24 @@ func (p *Proxy) ProxyHTTP(
|
||||
rule: ruleNum,
|
||||
}
|
||||
p.logRequest(req, logFields)
|
||||
ruleSpan.SetAttributes(attribute.Int("rule-num", ruleNum))
|
||||
ruleSpan.End()
|
||||
|
||||
switch originProxy := rule.Service.(type) {
|
||||
case ingress.HTTPOriginProxy:
|
||||
if err := p.proxyHTTPRequest(
|
||||
w,
|
||||
req,
|
||||
tr,
|
||||
originProxy,
|
||||
isWebsocket,
|
||||
rule.Config.DisableChunkedEncoding,
|
||||
logFields,
|
||||
); err != nil {
|
||||
rule, srv := ruleField(p.ingressRules, ruleNum)
|
||||
p.logRequestError(err, cfRay, rule, srv)
|
||||
p.logRequestError(err, cfRay, "", rule, srv)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
case ingress.StreamBasedOriginProxy:
|
||||
dest, err := getDestFromRule(rule, req)
|
||||
if err != nil {
|
||||
@@ -101,9 +108,9 @@ func (p *Proxy) ProxyHTTP(
|
||||
}
|
||||
|
||||
rws := connection.NewHTTPResponseReadWriterAcker(w, req)
|
||||
if err := p.proxyStream(req.Context(), rws, dest, originProxy, logFields); err != nil {
|
||||
if err := p.proxyStream(req.Context(), rws, dest, originProxy); err != nil {
|
||||
rule, srv := ruleField(p.ingressRules, ruleNum)
|
||||
p.logRequestError(err, cfRay, rule, srv)
|
||||
p.logRequestError(err, cfRay, "", rule, srv)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -130,17 +137,15 @@ func (p *Proxy) ProxyTCP(
|
||||
serveCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
logFields := logFields{
|
||||
cfRay: req.CFRay,
|
||||
lbProbe: req.LBProbe,
|
||||
rule: ingress.ServiceWarpRouting,
|
||||
}
|
||||
p.log.Debug().Str(LogFieldFlowID, req.FlowID).Msg("tcp proxy stream started")
|
||||
|
||||
if err := p.proxyStream(serveCtx, rwa, req.Dest, p.warpRouting.Proxy, logFields); err != nil {
|
||||
p.logRequestError(err, req.CFRay, "", ingress.ServiceWarpRouting)
|
||||
if err := p.proxyStream(serveCtx, rwa, req.Dest, p.warpRouting.Proxy); err != nil {
|
||||
p.logRequestError(err, req.CFRay, req.FlowID, "", ingress.ServiceWarpRouting)
|
||||
return err
|
||||
}
|
||||
|
||||
p.log.Debug().Str(LogFieldFlowID, req.FlowID).Msg("tcp proxy stream finished successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -155,15 +160,15 @@ func ruleField(ing ingress.Ingress, ruleNum int) (ruleID string, srv string) {
|
||||
// ProxyHTTPRequest proxies requests of underlying type http and websocket to the origin service.
|
||||
func (p *Proxy) proxyHTTPRequest(
|
||||
w connection.ResponseWriter,
|
||||
req *http.Request,
|
||||
tr *tracing.TracedRequest,
|
||||
httpService ingress.HTTPOriginProxy,
|
||||
isWebsocket bool,
|
||||
disableChunkedEncoding bool,
|
||||
fields logFields,
|
||||
) error {
|
||||
roundTripReq := req
|
||||
roundTripReq := tr.Request
|
||||
if isWebsocket {
|
||||
roundTripReq = req.Clone(req.Context())
|
||||
roundTripReq = tr.Clone(tr.Request.Context())
|
||||
roundTripReq.Header.Set("Connection", "Upgrade")
|
||||
roundTripReq.Header.Set("Upgrade", "websocket")
|
||||
roundTripReq.Header.Set("Sec-Websocket-Version", "13")
|
||||
@@ -173,7 +178,7 @@ func (p *Proxy) proxyHTTPRequest(
|
||||
// Support for WSGI Servers by switching transfer encoding from chunked to gzip/deflate
|
||||
if disableChunkedEncoding {
|
||||
roundTripReq.TransferEncoding = []string{"gzip", "deflate"}
|
||||
cLength, err := strconv.Atoi(req.Header.Get("Content-Length"))
|
||||
cLength, err := strconv.Atoi(tr.Request.Header.Get("Content-Length"))
|
||||
if err == nil {
|
||||
roundTripReq.ContentLength = int64(cLength)
|
||||
}
|
||||
@@ -187,12 +192,24 @@ func (p *Proxy) proxyHTTPRequest(
|
||||
roundTripReq.Header.Set("User-Agent", "")
|
||||
}
|
||||
|
||||
_, ttfbSpan := tr.Tracer().Start(tr.Context(), "ttfb_origin")
|
||||
resp, err := httpService.RoundTrip(roundTripReq)
|
||||
if err != nil {
|
||||
tracing.EndWithErrorStatus(ttfbSpan, err)
|
||||
return errors.Wrap(err, "Unable to reach the origin service. The service may be down or it may not be responding to traffic from cloudflared")
|
||||
}
|
||||
|
||||
tracing.EndWithStatusCode(ttfbSpan, resp.StatusCode)
|
||||
defer resp.Body.Close()
|
||||
|
||||
// resp headers can be nil
|
||||
if resp.Header == nil {
|
||||
resp.Header = make(http.Header)
|
||||
}
|
||||
|
||||
// Add spans to response header (if available)
|
||||
tr.AddSpans(resp.Header, p.log)
|
||||
|
||||
err = w.WriteRespHeaders(resp.StatusCode, resp.Header)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error writing response header")
|
||||
@@ -207,7 +224,7 @@ func (p *Proxy) proxyHTTPRequest(
|
||||
|
||||
eyeballStream := &bidirectionalStream{
|
||||
writer: w,
|
||||
reader: req.Body,
|
||||
reader: tr.Request.Body,
|
||||
}
|
||||
|
||||
websocket.Stream(eyeballStream, rwc, p.log)
|
||||
@@ -218,11 +235,7 @@ func (p *Proxy) proxyHTTPRequest(
|
||||
p.log.Debug().Msg("Detected Server-Side Events from Origin")
|
||||
p.writeEventStream(w, resp.Body)
|
||||
} else {
|
||||
// Use CopyBuffer, because Copy only allocates a 32KiB buffer, and cross-stream
|
||||
// compression generates dictionary on first write
|
||||
buf := p.bufferPool.Get()
|
||||
defer p.bufferPool.Put(buf)
|
||||
_, _ = io.CopyBuffer(w, resp.Body, buf)
|
||||
_, _ = cfio.Copy(w, resp.Body)
|
||||
}
|
||||
|
||||
p.logOriginResponse(resp, fields)
|
||||
@@ -236,9 +249,8 @@ func (p *Proxy) proxyStream(
|
||||
rwa connection.ReadWriteAcker,
|
||||
dest string,
|
||||
connectionProxy ingress.StreamBasedOriginProxy,
|
||||
fields logFields,
|
||||
) error {
|
||||
originConn, err := connectionProxy.EstablishConnection(dest)
|
||||
originConn, err := connectionProxy.EstablishConnection(ctx, dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -303,6 +315,7 @@ type logFields struct {
|
||||
cfRay string
|
||||
lbProbe bool
|
||||
rule interface{}
|
||||
flowID string
|
||||
}
|
||||
|
||||
func (p *Proxy) logRequest(r *http.Request, fields logFields) {
|
||||
@@ -346,12 +359,15 @@ func (p *Proxy) logOriginResponse(resp *http.Response, fields logFields) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Proxy) logRequestError(err error, cfRay string, rule, service string) {
|
||||
func (p *Proxy) logRequestError(err error, cfRay string, flowID string, rule, service string) {
|
||||
requestErrors.Inc()
|
||||
log := p.log.Error().Err(err)
|
||||
if cfRay != "" {
|
||||
log = log.Str(LogFieldCFRay, cfRay)
|
||||
}
|
||||
if flowID != "" {
|
||||
log = log.Str(LogFieldFlowID, flowID)
|
||||
}
|
||||
if rule != "" {
|
||||
log = log.Str(LogFieldRule, rule)
|
||||
}
|
||||
|
||||
+19
-13
@@ -27,11 +27,17 @@ import (
|
||||
"github.com/cloudflare/cloudflared/hello"
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/tracing"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
var (
|
||||
testTags = []tunnelpogs.Tag{tunnelpogs.Tag{Name: "Name", Value: "value"}}
|
||||
testTags = []tunnelpogs.Tag{tunnelpogs.Tag{Name: "Name", Value: "value"}}
|
||||
noWarpRouting = ingress.WarpRoutingConfig{}
|
||||
testWarpRouting = ingress.WarpRoutingConfig{
|
||||
Enabled: true,
|
||||
ConnectTimeout: config.CustomDuration{Duration: time.Second},
|
||||
}
|
||||
)
|
||||
|
||||
type mockHTTPRespWriter struct {
|
||||
@@ -137,7 +143,7 @@ func TestProxySingleOrigin(t *testing.T) {
|
||||
|
||||
require.NoError(t, ingressRule.StartOrigins(&log, ctx.Done()))
|
||||
|
||||
proxy := NewOriginProxy(ingressRule, false, testTags, &log)
|
||||
proxy := NewOriginProxy(ingressRule, noWarpRouting, testTags, &log)
|
||||
t.Run("testProxyHTTP", testProxyHTTP(proxy))
|
||||
t.Run("testProxyWebsocket", testProxyWebsocket(proxy))
|
||||
t.Run("testProxySSE", testProxySSE(proxy))
|
||||
@@ -151,7 +157,7 @@ func testProxyHTTP(proxy connection.OriginProxy) func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:8080", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = proxy.ProxyHTTP(responseWriter, req, false)
|
||||
err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedRequest(req), false)
|
||||
require.NoError(t, err)
|
||||
for _, tag := range testTags {
|
||||
assert.Equal(t, tag.Value, req.Header.Get(TagHeaderNamePrefix+tag.Name))
|
||||
@@ -178,7 +184,7 @@ func testProxyWebsocket(proxy connection.OriginProxy) func(t *testing.T) {
|
||||
|
||||
errGroup, ctx := errgroup.WithContext(ctx)
|
||||
errGroup.Go(func() error {
|
||||
err = proxy.ProxyHTTP(responseWriter, req, true)
|
||||
err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedRequest(req), true)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, http.StatusSwitchingProtocols, responseWriter.Code)
|
||||
@@ -239,7 +245,7 @@ func testProxySSE(proxy connection.OriginProxy) func(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err = proxy.ProxyHTTP(responseWriter, req, false)
|
||||
err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedRequest(req), false)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, http.StatusOK, responseWriter.Code)
|
||||
@@ -344,14 +350,14 @@ func runIngressTestScenarios(t *testing.T, unvalidatedIngress []config.Unvalidat
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
require.NoError(t, ingress.StartOrigins(&log, ctx.Done()))
|
||||
|
||||
proxy := NewOriginProxy(ingress, false, testTags, &log)
|
||||
proxy := NewOriginProxy(ingress, noWarpRouting, testTags, &log)
|
||||
|
||||
for _, test := range tests {
|
||||
responseWriter := newMockHTTPRespWriter()
|
||||
req, err := http.NewRequest(http.MethodGet, test.url, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = proxy.ProxyHTTP(responseWriter, req, false)
|
||||
err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedRequest(req), false)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expectedStatus, responseWriter.Code)
|
||||
@@ -392,13 +398,13 @@ func TestProxyError(t *testing.T) {
|
||||
|
||||
log := zerolog.Nop()
|
||||
|
||||
proxy := NewOriginProxy(ing, false, testTags, &log)
|
||||
proxy := NewOriginProxy(ing, noWarpRouting, testTags, &log)
|
||||
|
||||
responseWriter := newMockHTTPRespWriter()
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Error(t, proxy.ProxyHTTP(responseWriter, req, false))
|
||||
assert.Error(t, proxy.ProxyHTTP(responseWriter, tracing.NewTracedRequest(req), false))
|
||||
}
|
||||
|
||||
type replayer struct {
|
||||
@@ -508,7 +514,7 @@ func TestConnections(t *testing.T) {
|
||||
originService: runEchoTCPService,
|
||||
eyeballResponseWriter: newTCPRespWriter(replayer),
|
||||
eyeballRequestBody: newTCPRequestBody([]byte("test2")),
|
||||
warpRoutingService: ingress.NewWarpRoutingService(),
|
||||
warpRoutingService: ingress.NewWarpRoutingService(testWarpRouting),
|
||||
connectionType: connection.TypeTCP,
|
||||
requestHeaders: map[string][]string{
|
||||
"Cf-Cloudflared-Proxy-Src": {"non-blank-value"},
|
||||
@@ -525,7 +531,7 @@ func TestConnections(t *testing.T) {
|
||||
originService: runEchoWSService,
|
||||
// eyeballResponseWriter gets set after roundtrip dial.
|
||||
eyeballRequestBody: newPipedWSRequestBody([]byte("test3")),
|
||||
warpRoutingService: ingress.NewWarpRoutingService(),
|
||||
warpRoutingService: ingress.NewWarpRoutingService(testWarpRouting),
|
||||
requestHeaders: map[string][]string{
|
||||
"Cf-Cloudflared-Proxy-Src": {"non-blank-value"},
|
||||
},
|
||||
@@ -651,7 +657,7 @@ func TestConnections(t *testing.T) {
|
||||
|
||||
ingressRule := createSingleIngressConfig(t, test.args.ingressServiceScheme+ln.Addr().String())
|
||||
ingressRule.StartOrigins(logger, ctx.Done())
|
||||
proxy := NewOriginProxy(ingressRule, true, testTags, logger)
|
||||
proxy := NewOriginProxy(ingressRule, testWarpRouting, testTags, logger)
|
||||
proxy.warpRouting = test.args.warpRoutingService
|
||||
|
||||
dest := ln.Addr().String()
|
||||
@@ -676,7 +682,7 @@ func TestConnections(t *testing.T) {
|
||||
rwa := connection.NewHTTPResponseReadWriterAcker(respWriter, req)
|
||||
err = proxy.ProxyTCP(ctx, rwa, &connection.TCPRequest{Dest: dest})
|
||||
} else {
|
||||
err = proxy.ProxyHTTP(respWriter, req, test.args.connectionType == connection.TypeWebsocket)
|
||||
err = proxy.ProxyHTTP(respWriter, tracing.NewTracedRequest(req), test.args.connectionType == connection.TypeWebsocket)
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
+12
-9
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -13,18 +14,14 @@ const (
|
||||
)
|
||||
|
||||
type DatagramMuxer struct {
|
||||
ID uuid.UUID
|
||||
session quic.Session
|
||||
session quic.Connection
|
||||
logger *zerolog.Logger
|
||||
}
|
||||
|
||||
func NewDatagramMuxer(quicSession quic.Session) (*DatagramMuxer, error) {
|
||||
muxerID, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func NewDatagramMuxer(quicSession quic.Connection, logger *zerolog.Logger) (*DatagramMuxer, error) {
|
||||
return &DatagramMuxer{
|
||||
ID: muxerID,
|
||||
session: quicSession,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -42,6 +39,7 @@ func (dm *DatagramMuxer) SendTo(sessionID uuid.UUID, payload []byte) error {
|
||||
if err := dm.session.SendMessage(msgWithID); err != nil {
|
||||
return errors.Wrap(err, "Failed to send datagram back to edge")
|
||||
}
|
||||
dm.logger.Debug().Str("sessionID", sessionID.String()).Int("bytes", len(payload)).Msg("Send datagram back to edge")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -53,7 +51,12 @@ func (dm *DatagramMuxer) ReceiveFrom() (uuid.UUID, []byte, error) {
|
||||
if err != nil {
|
||||
return uuid.Nil, nil, err
|
||||
}
|
||||
return extractSessionID(msg)
|
||||
sessionID, payload, err := extractSessionID(msg)
|
||||
if err != nil {
|
||||
return uuid.Nil, nil, err
|
||||
}
|
||||
dm.logger.Debug().Str("sessionID", sessionID.String()).Int("bytes", len(payload)).Msg("Received datagram from edge")
|
||||
return sessionID, payload, nil
|
||||
}
|
||||
|
||||
// Maximum application payload to send to / receive from QUIC datagram frame
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
@@ -55,7 +56,7 @@ func TestMaxDatagramPayload(t *testing.T) {
|
||||
payload := make([]byte, maxDatagramPayloadSize)
|
||||
|
||||
quicConfig := &quic.Config{
|
||||
KeepAlive: true,
|
||||
KeepAlivePeriod: 5 * time.Millisecond,
|
||||
EnableDatagrams: true,
|
||||
MaxDatagramFrameSize: MaxDatagramFrameSize,
|
||||
}
|
||||
@@ -71,7 +72,8 @@ func TestMaxDatagramPayload(t *testing.T) {
|
||||
return err
|
||||
}
|
||||
|
||||
muxer, err := NewDatagramMuxer(quicSession)
|
||||
logger := zerolog.Nop()
|
||||
muxer, err := NewDatagramMuxer(quicSession, &logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -96,7 +98,8 @@ func TestMaxDatagramPayload(t *testing.T) {
|
||||
quicSession, err := quic.DialAddrEarly(quicListener.Addr().String(), tlsClientConfig, quicConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
muxer, err := NewDatagramMuxer(quicSession)
|
||||
logger := zerolog.Nop()
|
||||
muxer, err := NewDatagramMuxer(quicSession, &logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
"zombiezen.com/go/capnproto2/pogs"
|
||||
|
||||
@@ -16,6 +18,18 @@ const (
|
||||
ConnectionTypeTCP
|
||||
)
|
||||
|
||||
func (c ConnectionType) String() string {
|
||||
switch c {
|
||||
case ConnectionTypeHTTP:
|
||||
return "http"
|
||||
case ConnectionTypeWebsocket:
|
||||
return "ws"
|
||||
case ConnectionTypeTCP:
|
||||
return "tcp"
|
||||
}
|
||||
panic(fmt.Sprintf("invalid ConnectionType: %d", c))
|
||||
}
|
||||
|
||||
// ConnectRequest is the representation of metadata sent at the start of a QUIC application handshake.
|
||||
type ConnectRequest struct {
|
||||
Dest string `capnp:"dest"`
|
||||
|
||||
@@ -37,7 +37,8 @@ const (
|
||||
protocolVersionLength = 2
|
||||
|
||||
HandshakeIdleTimeout = 5 * time.Second
|
||||
MaxIdleTimeout = 15 * time.Second
|
||||
MaxIdleTimeout = 5 * time.Second
|
||||
MaxIdlePingPeriod = 1 * time.Second
|
||||
)
|
||||
|
||||
// RequestServerStream is a stream to serve requests
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
var (
|
||||
testTLSServerConfig = GenerateTLSConfig()
|
||||
testQUICConfig = &quic.Config{
|
||||
KeepAlive: true,
|
||||
KeepAlivePeriod: 5 * time.Second,
|
||||
EnableDatagrams: true,
|
||||
}
|
||||
exchanges = 1000
|
||||
|
||||
+65
-24
@@ -3,9 +3,9 @@ package quic
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go/logging"
|
||||
"github.com/lucas-clemente/quic-go/qlog"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
@@ -40,75 +40,116 @@ func NewServerTracer(logger *zerolog.Logger) logging.Tracer {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tracer) TracerForConnection(_ context.Context, p logging.Perspective, odcid logging.ConnectionID) logging.ConnectionTracer {
|
||||
connID := logging.ConnectionID(odcid).String()
|
||||
ql := &quicLogger{
|
||||
logger: t.logger,
|
||||
connectionID: connID,
|
||||
}
|
||||
func (t *tracer) TracerForConnection(_ctx context.Context, _p logging.Perspective, _odcid logging.ConnectionID) logging.ConnectionTracer {
|
||||
if t.config.isClient {
|
||||
return newConnTracer(ql, p, odcid, newClientCollector(t.config.index))
|
||||
return newConnTracer(newClientCollector(t.config.index))
|
||||
}
|
||||
return newConnTracer(ql, p, odcid, newServiceCollector())
|
||||
return newConnTracer(newServiceCollector())
|
||||
}
|
||||
|
||||
func (*tracer) SentPacket(net.Addr, *logging.Header, logging.ByteCount, []logging.Frame) {}
|
||||
func (*tracer) DroppedPacket(net.Addr, logging.PacketType, logging.ByteCount, logging.PacketDropReason) {
|
||||
}
|
||||
|
||||
// connTracer is a wrapper around https://pkg.go.dev/github.com/lucas-clemente/quic-go@v0.23.0/qlog#NewConnectionTracer to collect metrics
|
||||
var _ logging.Tracer = (*tracer)(nil)
|
||||
|
||||
// connTracer collects connection level metrics
|
||||
type connTracer struct {
|
||||
logging.ConnectionTracer
|
||||
metricsCollector MetricsCollector
|
||||
connectionID string
|
||||
}
|
||||
|
||||
func newConnTracer(ql *quicLogger, p logging.Perspective, odcid logging.ConnectionID, metricsCollector MetricsCollector) logging.ConnectionTracer {
|
||||
var _ logging.ConnectionTracer = (*connTracer)(nil)
|
||||
|
||||
func newConnTracer(metricsCollector MetricsCollector) logging.ConnectionTracer {
|
||||
return &connTracer{
|
||||
qlog.NewConnectionTracer(ql, p, odcid),
|
||||
metricsCollector,
|
||||
logging.ConnectionID(odcid).String(),
|
||||
metricsCollector: metricsCollector,
|
||||
}
|
||||
}
|
||||
|
||||
func (ct *connTracer) StartedConnection(local, remote net.Addr, srcConnID, destConnID logging.ConnectionID) {
|
||||
ct.metricsCollector.startedConnection()
|
||||
ct.ConnectionTracer.StartedConnection(local, remote, srcConnID, destConnID)
|
||||
}
|
||||
|
||||
func (ct *connTracer) ClosedConnection(err error) {
|
||||
ct.metricsCollector.closedConnection(err)
|
||||
ct.ConnectionTracer.ClosedConnection(err)
|
||||
}
|
||||
|
||||
func (ct *connTracer) SentPacket(hdr *logging.ExtendedHeader, packetSize logging.ByteCount, ack *logging.AckFrame, frames []logging.Frame) {
|
||||
ct.metricsCollector.sentPackets(packetSize)
|
||||
ct.ConnectionTracer.SentPacket(hdr, packetSize, ack, frames)
|
||||
}
|
||||
|
||||
func (ct *connTracer) ReceivedPacket(hdr *logging.ExtendedHeader, size logging.ByteCount, frames []logging.Frame) {
|
||||
ct.metricsCollector.receivedPackets(size)
|
||||
ct.ConnectionTracer.ReceivedPacket(hdr, size, frames)
|
||||
}
|
||||
|
||||
func (ct *connTracer) BufferedPacket(pt logging.PacketType) {
|
||||
ct.metricsCollector.bufferedPackets(pt)
|
||||
ct.ConnectionTracer.BufferedPacket(pt)
|
||||
}
|
||||
|
||||
func (ct *connTracer) DroppedPacket(pt logging.PacketType, size logging.ByteCount, reason logging.PacketDropReason) {
|
||||
ct.metricsCollector.droppedPackets(pt, size, reason)
|
||||
ct.ConnectionTracer.DroppedPacket(pt, size, reason)
|
||||
}
|
||||
|
||||
func (ct *connTracer) LostPacket(level logging.EncryptionLevel, number logging.PacketNumber, reason logging.PacketLossReason) {
|
||||
ct.metricsCollector.lostPackets(reason)
|
||||
ct.ConnectionTracer.LostPacket(level, number, reason)
|
||||
}
|
||||
|
||||
func (ct *connTracer) UpdatedMetrics(rttStats *logging.RTTStats, cwnd, bytesInFlight logging.ByteCount, packetsInFlight int) {
|
||||
ct.metricsCollector.updatedRTT(rttStats)
|
||||
ct.ConnectionTracer.UpdatedMetrics(rttStats, cwnd, bytesInFlight, packetsInFlight)
|
||||
}
|
||||
|
||||
func (ct *connTracer) NegotiatedVersion(chosen logging.VersionNumber, clientVersions, serverVersions []logging.VersionNumber) {
|
||||
}
|
||||
|
||||
func (ct *connTracer) SentTransportParameters(parameters *logging.TransportParameters) {
|
||||
}
|
||||
|
||||
func (ct *connTracer) ReceivedTransportParameters(parameters *logging.TransportParameters) {
|
||||
}
|
||||
|
||||
func (ct *connTracer) RestoredTransportParameters(parameters *logging.TransportParameters) {
|
||||
}
|
||||
|
||||
func (ct *connTracer) ReceivedVersionNegotiationPacket(header *logging.Header, numbers []logging.VersionNumber) {
|
||||
}
|
||||
|
||||
func (ct *connTracer) ReceivedRetry(header *logging.Header) {
|
||||
}
|
||||
|
||||
func (ct *connTracer) AcknowledgedPacket(level logging.EncryptionLevel, number logging.PacketNumber) {
|
||||
}
|
||||
|
||||
func (ct *connTracer) UpdatedCongestionState(state logging.CongestionState) {
|
||||
}
|
||||
|
||||
func (ct *connTracer) UpdatedPTOCount(value uint32) {
|
||||
}
|
||||
|
||||
func (ct *connTracer) UpdatedKeyFromTLS(level logging.EncryptionLevel, perspective logging.Perspective) {
|
||||
}
|
||||
|
||||
func (ct *connTracer) UpdatedKey(generation logging.KeyPhase, remote bool) {
|
||||
}
|
||||
|
||||
func (ct *connTracer) DroppedEncryptionLevel(level logging.EncryptionLevel) {
|
||||
}
|
||||
|
||||
func (ct *connTracer) DroppedKey(generation logging.KeyPhase) {
|
||||
}
|
||||
|
||||
func (ct *connTracer) SetLossTimer(timerType logging.TimerType, level logging.EncryptionLevel, time time.Time) {
|
||||
}
|
||||
|
||||
func (ct *connTracer) LossTimerExpired(timerType logging.TimerType, level logging.EncryptionLevel) {
|
||||
}
|
||||
|
||||
func (ct *connTracer) LossTimerCanceled() {
|
||||
}
|
||||
|
||||
func (ct *connTracer) Close() {
|
||||
}
|
||||
|
||||
func (ct *connTracer) Debug(name, msg string) {
|
||||
}
|
||||
|
||||
type quicLogger struct {
|
||||
|
||||
+391
@@ -0,0 +1,391 @@
|
||||
"""
|
||||
This is a utility for creating deb and rpm packages, signing them
|
||||
and uploading them to a storage and adding metadata to workers KV.
|
||||
|
||||
It has two over-arching responsiblities:
|
||||
1. Create deb and yum repositories from .deb and .rpm files.
|
||||
This is also responsible for signing the packages and generally preparing
|
||||
them to be in an uploadable state.
|
||||
2. Upload these packages to a storage in a format that apt and yum expect.
|
||||
"""
|
||||
import argparse
|
||||
import base64
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
from hashlib import sha256
|
||||
from pathlib import Path
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
import boto3
|
||||
import gnupg
|
||||
from botocore.client import Config
|
||||
from botocore.exceptions import ClientError
|
||||
|
||||
# The front facing R2 URL to access assets from.
|
||||
R2_ASSET_URL = 'https://demo-r2-worker.cloudflare-tunnel.workers.dev/'
|
||||
|
||||
|
||||
class PkgUploader:
|
||||
def __init__(self, account_id, bucket_name, client_id, client_secret):
|
||||
self.account_id = account_id
|
||||
self.bucket_name = bucket_name
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
|
||||
def upload_pkg_to_r2(self, filename, upload_file_path):
|
||||
endpoint_url = f"https://{self.account_id}.r2.cloudflarestorage.com"
|
||||
token_secret_hash = sha256(self.client_secret.encode()).hexdigest()
|
||||
|
||||
config = Config(
|
||||
region_name='auto',
|
||||
s3={
|
||||
"addressing_style": "path",
|
||||
}
|
||||
)
|
||||
|
||||
r2 = boto3.client(
|
||||
"s3",
|
||||
endpoint_url=endpoint_url,
|
||||
aws_access_key_id=self.client_id,
|
||||
aws_secret_access_key=token_secret_hash,
|
||||
config=config,
|
||||
)
|
||||
|
||||
print(f"uploading asset: {filename} to {upload_file_path} in bucket{self.bucket_name}...")
|
||||
try:
|
||||
r2.upload_file(filename, self.bucket_name, upload_file_path)
|
||||
except ClientError as e:
|
||||
raise e
|
||||
|
||||
|
||||
class PkgCreator:
|
||||
"""
|
||||
The distribution conf is what dictates to reprepro, the debian packaging building
|
||||
and signing tool we use, what distros to support, what GPG key to use for signing
|
||||
and what to call the debian binary etc. This function creates it "./conf/distributions".
|
||||
|
||||
origin - name of your package (String)
|
||||
label - label of your package (could be same as the name) (String)
|
||||
release - release you want this to be distributed for (List of Strings)
|
||||
components - could be a channel like main/stable/beta
|
||||
archs - Architecture (List of Strings)
|
||||
description - (String)
|
||||
gpg_key_id - gpg key id of what you want to use to sign the packages.(String)
|
||||
"""
|
||||
|
||||
def create_distribution_conf(self,
|
||||
file_path,
|
||||
origin,
|
||||
label,
|
||||
releases,
|
||||
archs,
|
||||
components,
|
||||
description,
|
||||
gpg_key_id):
|
||||
with open(file_path, "w+") as distributions_file:
|
||||
for release in releases:
|
||||
distributions_file.write(f"Origin: {origin}\n")
|
||||
distributions_file.write(f"Label: {label}\n")
|
||||
distributions_file.write(f"Codename: {release}\n")
|
||||
archs_list = " ".join(archs)
|
||||
distributions_file.write(f"Architectures: {archs_list}\n")
|
||||
distributions_file.write(f"Components: {components}\n")
|
||||
distributions_file.write(f"Description: {description} - {release}\n")
|
||||
distributions_file.write(f"SignWith: {gpg_key_id}\n")
|
||||
distributions_file.write("\n")
|
||||
return distributions_file
|
||||
|
||||
"""
|
||||
Uses the reprepro tool to generate packages, sign them and create the InRelease as specified
|
||||
by the distribution_conf file.
|
||||
|
||||
This function creates three folders db, pool and dist.
|
||||
db and pool contain information and metadata about builds. We can ignore these.
|
||||
dist: contains all the pkgs and signed releases that are necessary for an apt download.
|
||||
"""
|
||||
|
||||
def create_deb_pkgs(self, release, deb_file):
|
||||
print(f"creating deb pkgs: {release} : {deb_file}")
|
||||
p = Popen(["reprepro", "includedeb", release, deb_file], stdout=PIPE, stderr=PIPE)
|
||||
out, err = p.communicate()
|
||||
if p.returncode != 0:
|
||||
print(f"create deb_pkgs result => {out}, {err}")
|
||||
raise
|
||||
|
||||
def create_rpm_pkgs(self, artifacts_path, gpg_key_name):
|
||||
self._setup_rpm_pkg_directories(artifacts_path, gpg_key_name)
|
||||
p = Popen(["createrepo", "./rpm"], stdout=PIPE, stderr=PIPE)
|
||||
out, err = p.communicate()
|
||||
if p.returncode != 0:
|
||||
print(f"create rpm_pkgs result => {out}, {err}")
|
||||
raise
|
||||
|
||||
self._sign_repomd()
|
||||
|
||||
"""
|
||||
creates a <binary>.repo file with details like so
|
||||
[cloudflared-stable]
|
||||
name=cloudflared-stable
|
||||
baseurl=https://pkg.cloudflare.com/cloudflared/rpm
|
||||
enabled=1
|
||||
type=rpm
|
||||
gpgcheck=1
|
||||
gpgkey=https://pkg.cloudflare.com/cloudflare-main.gpg
|
||||
"""
|
||||
|
||||
def create_repo_file(self, file_path, binary_name, baseurl, gpgkey_url):
|
||||
with open(os.path.join(file_path, binary_name + '.repo'), "w+") as repo_file:
|
||||
repo_file.write(f"[{binary_name}-stable]")
|
||||
repo_file.write(f"{binary_name}-stable")
|
||||
repo_file.write(f"baseurl={baseurl}/rpm")
|
||||
repo_file.write("enabled=1")
|
||||
repo_file.write("type=rpm")
|
||||
repo_file.write("gpgcheck=1")
|
||||
repo_file.write(f"gpgkey={gpgkey_url}")
|
||||
|
||||
def _sign_rpms(self, file_path):
|
||||
p = Popen(["rpm", "--define", f"_gpg_name {gpg_key_name}", "--addsign", file_path], stdout=PIPE, stderr=PIPE)
|
||||
out, err = p.communicate()
|
||||
if p.returncode != 0:
|
||||
print(f"rpm sign result result => {out}, {err}")
|
||||
raise
|
||||
|
||||
def _sign_repomd(self):
|
||||
p = Popen(["gpg", "--batch", "--detach-sign", "--armor", "./rpm/repodata/repomd.xml"], stdout=PIPE, stderr=PIPE)
|
||||
out, err = p.communicate()
|
||||
if p.returncode != 0:
|
||||
print(f"sign repomd result => {out}, {err}")
|
||||
raise
|
||||
|
||||
"""
|
||||
sets up and signs the RPM directories in the following format:
|
||||
- rpm
|
||||
- aarch64
|
||||
- x86_64
|
||||
- 386
|
||||
|
||||
this assumes the assets are in the format <prefix>-<aarch64/x86_64/386>.rpm
|
||||
"""
|
||||
|
||||
def _setup_rpm_pkg_directories(self, artifacts_path, gpg_key_name, archs=["aarch64", "x86_64", "386"]):
|
||||
for arch in archs:
|
||||
for root, _, files in os.walk(artifacts_path):
|
||||
for file in files:
|
||||
if file.endswith(f"{arch}.rpm"):
|
||||
new_dir = f"./rpm/{arch}"
|
||||
os.makedirs(new_dir, exist_ok=True)
|
||||
old_path = os.path.join(root, file)
|
||||
new_path = os.path.join(new_dir, file)
|
||||
shutil.copyfile(old_path, new_path)
|
||||
self._sign_rpms(new_path)
|
||||
|
||||
"""
|
||||
imports gpg keys into the system so reprepro and createrepo can use it to sign packages.
|
||||
it returns the GPG ID after a successful import
|
||||
"""
|
||||
|
||||
def import_gpg_keys(self, private_key, public_key):
|
||||
gpg = gnupg.GPG()
|
||||
private_key = base64.b64decode(private_key)
|
||||
gpg.import_keys(private_key)
|
||||
public_key = base64.b64decode(public_key)
|
||||
gpg.import_keys(public_key)
|
||||
data = gpg.list_keys(secret=True)
|
||||
return (data[0]["fingerprint"], data[0]["uids"][0])
|
||||
|
||||
"""
|
||||
basically rpm --import <key_file>
|
||||
This enables us to sign rpms.
|
||||
"""
|
||||
|
||||
def import_rpm_key(self, public_key):
|
||||
file_name = "pb.key"
|
||||
with open(file_name, "wb") as f:
|
||||
public_key = base64.b64decode(public_key)
|
||||
f.write(public_key)
|
||||
|
||||
p = Popen(["rpm", "--import", file_name], stdout=PIPE, stderr=PIPE)
|
||||
out, err = p.communicate()
|
||||
if p.returncode != 0:
|
||||
print(f"create rpm import result => {out}, {err}")
|
||||
raise
|
||||
|
||||
|
||||
"""
|
||||
Walks through a directory and uploads it's assets to R2.
|
||||
directory : root directory to walk through (String).
|
||||
release: release string. If this value is none, a specific release path will not be created
|
||||
and the release will be uploaded to the default path.
|
||||
binary: name of the binary to upload
|
||||
"""
|
||||
|
||||
|
||||
def upload_from_directories(pkg_uploader, directory, release, binary):
|
||||
for root, _, files in os.walk(directory):
|
||||
for file in files:
|
||||
upload_file_name = os.path.join(binary, root, file)
|
||||
if release:
|
||||
upload_file_name = os.path.join(release, upload_file_name)
|
||||
filename = os.path.join(root, file)
|
||||
try:
|
||||
pkg_uploader.upload_pkg_to_r2(filename, upload_file_name)
|
||||
except ClientError as e:
|
||||
logging.error(e)
|
||||
return
|
||||
|
||||
|
||||
"""
|
||||
1. looks into a built_artifacts folder for cloudflared debs
|
||||
2. creates Packages.gz, InRelease (signed) files
|
||||
3. uploads them to Cloudflare R2
|
||||
|
||||
pkg_creator, pkg_uploader: are instantiations of the two classes above.
|
||||
|
||||
gpg_key_id: is an id indicating the key the package should be signed with. The public key of this id will be
|
||||
uploaded to R2 so it can be presented to apt downloaders.
|
||||
|
||||
release_version: is the cloudflared release version. Only provide this if you want a permanent backup.
|
||||
"""
|
||||
|
||||
|
||||
def create_deb_packaging(pkg_creator, pkg_uploader, releases, gpg_key_id, binary_name, archs, package_component,
|
||||
release_version):
|
||||
# set configuration for package creation.
|
||||
print(f"initialising configuration for {binary_name} , {archs}")
|
||||
Path("./conf").mkdir(parents=True, exist_ok=True)
|
||||
pkg_creator.create_distribution_conf(
|
||||
"./conf/distributions",
|
||||
binary_name,
|
||||
binary_name,
|
||||
releases,
|
||||
archs,
|
||||
package_component,
|
||||
f"apt repository for {binary_name}",
|
||||
gpg_key_id)
|
||||
|
||||
# create deb pkgs
|
||||
for release in releases:
|
||||
for arch in archs:
|
||||
print(f"creating deb pkgs for {release} and {arch}...")
|
||||
pkg_creator.create_deb_pkgs(release, f"./built_artifacts/cloudflared-linux-{arch}.deb")
|
||||
|
||||
print("uploading latest to r2...")
|
||||
upload_from_directories(pkg_uploader, "dists", None, binary_name)
|
||||
upload_from_directories(pkg_uploader, "pool", None, binary_name)
|
||||
|
||||
if release_version:
|
||||
print(f"uploading versioned release {release_version} to r2...")
|
||||
upload_from_directories(pkg_uploader, "dists", release_version, binary_name)
|
||||
upload_from_directories(pkg_uploader, "pool", release_version, binary_name)
|
||||
|
||||
|
||||
def create_rpm_packaging(
|
||||
pkg_creator,
|
||||
pkg_uploader,
|
||||
artifacts_path,
|
||||
release_version,
|
||||
binary_name,
|
||||
gpg_key_name,
|
||||
base_url,
|
||||
gpg_key_url,
|
||||
):
|
||||
print(f"creating rpm pkgs...")
|
||||
pkg_creator.create_rpm_pkgs(artifacts_path, gpg_key_name)
|
||||
pkg_creator.create_repo_file(artifacts_path, binary_name, base_url, gpg_key_url)
|
||||
|
||||
print("uploading latest to r2...")
|
||||
upload_from_directories(pkg_uploader, "rpm", None, binary_name)
|
||||
|
||||
if release_version:
|
||||
print(f"uploading versioned release {release_version} to r2...")
|
||||
upload_from_directories(pkg_uploader, "rpm", release_version, binary_name)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Creates linux releases and uploads them in a packaged format"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--bucket", default=os.environ.get("R2_BUCKET"), help="R2 Bucket name"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--id", default=os.environ.get("R2_CLIENT_ID"), help="R2 Client ID"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--secret", default=os.environ.get("R2_CLIENT_SECRET"), help="R2 Client Secret"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--account", default=os.environ.get("R2_ACCOUNT_ID"), help="R2 Account Tag"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--release-tag", default=os.environ.get("RELEASE_VERSION"), help="Release version you want your pkgs to be\
|
||||
prefixed with. Leave empty if you don't want tagged release versions backed up to R2."
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--binary", default=os.environ.get("BINARY_NAME"), help="The name of the binary the packages are for"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--gpg-private-key", default=os.environ.get("LINUX_SIGNING_PRIVATE_KEY"), help="GPG private key to sign the\
|
||||
packages"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--gpg-public-key", default=os.environ.get("LINUX_SIGNING_PUBLIC_KEY"), help="GPG public key used for\
|
||||
signing packages"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--gpg-public-key-url", default=os.environ.get("GPG_PUBLIC_KEY_URL"), help="GPG public key url that\
|
||||
downloaders can use to verify signing"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--pkg-upload-url", default=os.environ.get("PKG_URL"), help="URL to be used by downloaders"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--deb-based-releases", default=["bookworm", "bullseye", "buster", "jammy", "impish", "focal", "bionic",
|
||||
"xenial", "trusty"],
|
||||
help="list of debian based releases that need to be packaged for"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--archs", default=["amd64", "386", "arm64", "arm"], help="list of architectures we want to package for. Note that\
|
||||
it is the caller's responsiblity to ensure that these debs are already present in a directory. This script\
|
||||
will not build binaries or create their debs."
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
return args
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
args = parse_args()
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
exit(1)
|
||||
|
||||
pkg_creator = PkgCreator()
|
||||
(gpg_key_id, gpg_key_name) = pkg_creator.import_gpg_keys(args.gpg_private_key, args.gpg_public_key)
|
||||
pkg_creator.import_rpm_key(args.gpg_public_key)
|
||||
|
||||
pkg_uploader = PkgUploader(args.account, args.bucket, args.id, args.secret)
|
||||
print(f"signing with gpg_key: {gpg_key_id}")
|
||||
create_deb_packaging(pkg_creator, pkg_uploader, args.deb_based_releases, gpg_key_id, args.binary, args.archs,
|
||||
"main", args.release_tag)
|
||||
|
||||
create_rpm_packaging(
|
||||
pkg_creator,
|
||||
pkg_uploader,
|
||||
"./built_artifacts",
|
||||
args.release_tag,
|
||||
args.binary,
|
||||
gpg_key_name,
|
||||
args.gpg_public_key_url,
|
||||
args.pkg_upload_url,
|
||||
)
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
@@ -62,7 +63,6 @@ var errEarlyShutdown = errors.New("shutdown started")
|
||||
|
||||
type tunnelError struct {
|
||||
index int
|
||||
addr *allregions.EdgeAddr
|
||||
err error
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ func (s *Supervisor) startFirstTunnel(
|
||||
)
|
||||
const firstConnIndex = 0
|
||||
defer func() {
|
||||
s.tunnelErrors <- tunnelError{index: firstConnIndex, addr: addr, err: err}
|
||||
s.tunnelErrors <- tunnelError{index: firstConnIndex, err: err}
|
||||
}()
|
||||
|
||||
addr, err = s.edgeIPs.GetAddr(firstConnIndex)
|
||||
@@ -264,9 +264,9 @@ func (s *Supervisor) startFirstTunnel(
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
return
|
||||
// try the next address if it was a dialError(network problem) or
|
||||
// try the next address if it was a quic.IdleTimeoutError, dialError(network problem) or
|
||||
// dupConnRegisterTunnelError
|
||||
case edgediscovery.DialError, connection.DupConnRegisterTunnelError:
|
||||
case *quic.IdleTimeoutError, edgediscovery.DialError, connection.DupConnRegisterTunnelError:
|
||||
edgeErrors++
|
||||
default:
|
||||
return
|
||||
@@ -305,7 +305,7 @@ func (s *Supervisor) startTunnel(
|
||||
err error
|
||||
)
|
||||
defer func() {
|
||||
s.tunnelErrors <- tunnelError{index: index, addr: addr, err: err}
|
||||
s.tunnelErrors <- tunnelError{index: index, err: err}
|
||||
}()
|
||||
|
||||
addr, err = s.edgeIPs.GetDifferentAddr(index)
|
||||
|
||||
+22
-10
@@ -43,6 +43,7 @@ type TunnelConfig struct {
|
||||
CloseConnOnce *sync.Once // Used to close connectedSignal no more than once
|
||||
EdgeAddrs []string
|
||||
Region string
|
||||
EdgeIPVersion allregions.ConfigIPVersion
|
||||
HAConnections int
|
||||
IncidentLookup IncidentLookup
|
||||
IsAutoupdated bool
|
||||
@@ -171,17 +172,15 @@ func ServeTunnelLoop(
|
||||
protocolFallback.protocol,
|
||||
gracefulShutdownC,
|
||||
)
|
||||
if !recoverable {
|
||||
return err
|
||||
}
|
||||
|
||||
config.Observer.SendReconnect(connIndex)
|
||||
|
||||
duration, ok := protocolFallback.GetMaxBackoffDuration(ctx)
|
||||
if !ok {
|
||||
return err
|
||||
if recoverable {
|
||||
duration, ok := protocolFallback.GetMaxBackoffDuration(ctx)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
config.Observer.SendReconnect(connIndex)
|
||||
connLog.Logger().Info().Msgf("Retrying connection in up to %s seconds", duration)
|
||||
}
|
||||
connLog.Logger().Info().Msgf("Retrying connection in up to %s seconds", duration)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -189,6 +188,10 @@ func ServeTunnelLoop(
|
||||
case <-gracefulShutdownC:
|
||||
return nil
|
||||
case <-protocolFallback.BackoffTimer():
|
||||
if !recoverable {
|
||||
return err
|
||||
}
|
||||
|
||||
if !selectNextProtocol(
|
||||
connLog.Logger(),
|
||||
protocolFallback,
|
||||
@@ -233,6 +236,15 @@ func selectNextProtocol(
|
||||
_, hasFallback := selector.Fallback()
|
||||
|
||||
if protocolBackoff.ReachedMaxRetries() || (hasFallback && isNetworkActivityTimeout) {
|
||||
if isNetworkActivityTimeout {
|
||||
connLog.Warn().Msg("If this log occurs persistently, and cloudflared is unable to connect to " +
|
||||
"Cloudflare Network with `quic` protocol, then most likely your machine/network is getting its egress " +
|
||||
"UDP to port 7844 (or others) blocked or dropped. Make sure to allow egress connectivity as per " +
|
||||
"https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/ports-and-ips/\n" +
|
||||
"If you are using private routing to this Tunnel, then UDP (and Private DNS Resolution) will not work" +
|
||||
"unless your cloudflared can connect with Cloudflare Network with `quic`.")
|
||||
}
|
||||
|
||||
fallback, hasFallback := selector.Fallback()
|
||||
if !hasFallback {
|
||||
return false
|
||||
@@ -538,9 +550,9 @@ func ServeQUIC(
|
||||
quicConfig := &quic.Config{
|
||||
HandshakeIdleTimeout: quicpogs.HandshakeIdleTimeout,
|
||||
MaxIdleTimeout: quicpogs.MaxIdleTimeout,
|
||||
KeepAlivePeriod: quicpogs.MaxIdlePingPeriod,
|
||||
MaxIncomingStreams: connection.MaxConcurrentStreams,
|
||||
MaxIncomingUniStreams: connection.MaxConcurrentStreams,
|
||||
KeepAlive: true,
|
||||
EnableDatagrams: true,
|
||||
MaxDatagramFrameSize: quicpogs.MaxDatagramFrameSize,
|
||||
Tracer: quicpogs.NewClientTracer(connLogger.Logger(), connIndex),
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxTraceAmount = 20
|
||||
)
|
||||
|
||||
var (
|
||||
errNoTraces = errors.New("no traces recorded to be exported")
|
||||
errNoopTracer = errors.New("noop tracer has no traces")
|
||||
)
|
||||
|
||||
type InMemoryClient interface {
|
||||
// Spans returns a copy of the list of in-memory stored spans as a base64
|
||||
// encoded otlp protobuf string.
|
||||
Spans() (string, error)
|
||||
}
|
||||
|
||||
// InMemoryOtlpClient is a client implementation for otlptrace.Client
|
||||
type InMemoryOtlpClient struct {
|
||||
mu sync.Mutex
|
||||
spans []*tracepb.ResourceSpans
|
||||
}
|
||||
|
||||
func (mc *InMemoryOtlpClient) Start(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mc *InMemoryOtlpClient) Stop(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UploadTraces adds the provided list of spans to the in-memory list.
|
||||
func (mc *InMemoryOtlpClient) UploadTraces(_ context.Context, protoSpans []*tracepb.ResourceSpans) error {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
// Catch to make sure too many traces aren't being added to response header.
|
||||
// Returning nil makes sure we don't fail to send the traces we already recorded.
|
||||
if len(mc.spans)+len(protoSpans) > MaxTraceAmount {
|
||||
return nil
|
||||
}
|
||||
mc.spans = append(mc.spans, protoSpans...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Spans returns the list of in-memory stored spans as a base64 encoded otlp protobuf string.
|
||||
func (mc *InMemoryOtlpClient) Spans() (string, error) {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
if len(mc.spans) <= 0 {
|
||||
return "", errNoTraces
|
||||
}
|
||||
pbRequest := &coltracepb.ExportTraceServiceRequest{
|
||||
ResourceSpans: mc.spans,
|
||||
}
|
||||
data, err := proto.Marshal(pbRequest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(data), nil
|
||||
}
|
||||
|
||||
// NoopOtlpClient is a client implementation for otlptrace.Client that does nothing
|
||||
type NoopOtlpClient struct{}
|
||||
|
||||
func (mc *NoopOtlpClient) Start(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mc *NoopOtlpClient) Stop(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mc *NoopOtlpClient) UploadTraces(_ context.Context, _ []*tracepb.ResourceSpans) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Spans always returns no traces error
|
||||
func (mc *NoopOtlpClient) Spans() (string, error) {
|
||||
return "", errNoopTracer
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
resourcepb "go.opentelemetry.io/proto/otlp/resource/v1"
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
resourceSchemaUrl = "http://example.com/custom-resource-schema"
|
||||
instrumentSchemaUrl = semconv.SchemaURL
|
||||
)
|
||||
|
||||
var (
|
||||
traceId = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}
|
||||
spanId = []byte{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8}
|
||||
parentSpanId = []byte{0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08}
|
||||
startTime = time.Date(2022, 4, 4, 0, 0, 0, 0, time.UTC)
|
||||
endTime = startTime.Add(5 * time.Second)
|
||||
|
||||
traceState, _ = trace.ParseTraceState("key1=val1,key2=val2")
|
||||
instrScope = &commonpb.InstrumentationScope{Name: "go.opentelemetry.io/test/otel", Version: "v1.6.0"}
|
||||
otlpKeyValues = []*commonpb.KeyValue{
|
||||
{
|
||||
Key: "string_key",
|
||||
Value: &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_StringValue{
|
||||
StringValue: "string value",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: "bool_key",
|
||||
Value: &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_BoolValue{
|
||||
BoolValue: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
otlpResource = &resourcepb.Resource{
|
||||
Attributes: []*commonpb.KeyValue{
|
||||
{
|
||||
Key: "service.name",
|
||||
Value: &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_StringValue{
|
||||
StringValue: "service-name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
var _ otlptrace.Client = (*InMemoryOtlpClient)(nil)
|
||||
var _ InMemoryClient = (*InMemoryOtlpClient)(nil)
|
||||
var _ otlptrace.Client = (*NoopOtlpClient)(nil)
|
||||
var _ InMemoryClient = (*NoopOtlpClient)(nil)
|
||||
|
||||
func TestUploadTraces(t *testing.T) {
|
||||
client := &InMemoryOtlpClient{}
|
||||
spans := createResourceSpans([]*tracepb.Span{createOtlpSpan(traceId)})
|
||||
spans2 := createResourceSpans([]*tracepb.Span{createOtlpSpan(traceId)})
|
||||
err := client.UploadTraces(context.Background(), spans)
|
||||
assert.NoError(t, err)
|
||||
err = client.UploadTraces(context.Background(), spans2)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, client.spans, 2)
|
||||
}
|
||||
|
||||
func TestSpans(t *testing.T) {
|
||||
client := &InMemoryOtlpClient{}
|
||||
spans := createResourceSpans([]*tracepb.Span{createOtlpSpan(traceId)})
|
||||
err := client.UploadTraces(context.Background(), spans)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, client.spans, 1)
|
||||
enc, err := client.Spans()
|
||||
assert.NoError(t, err)
|
||||
expected := "CsECCiAKHgoMc2VydmljZS5uYW1lEg4KDHNlcnZpY2UtbmFtZRLxAQonCh1nby5vcGVudGVsZW1ldHJ5LmlvL3Rlc3Qvb3RlbBIGdjEuNi4wEp0BChAAAQIDBAUGBwgJCgsMDQ4PEgj//v38+/r5+BoTa2V5MT12YWwxLGtleTI9dmFsMiIIDw4NDAsKCQgqCnRyYWNlX25hbWUwATkAANJvaYjiFkEA8teZaojiFkocCgpzdHJpbmdfa2V5Eg4KDHN0cmluZyB2YWx1ZUoOCghib29sX2tleRICEAF6EhIOc3RhdHVzIG1lc3NhZ2UYARomaHR0cHM6Ly9vcGVudGVsZW1ldHJ5LmlvL3NjaGVtYXMvMS43LjAaKWh0dHA6Ly9leGFtcGxlLmNvbS9jdXN0b20tcmVzb3VyY2Utc2NoZW1h"
|
||||
assert.Equal(t, expected, enc)
|
||||
}
|
||||
|
||||
func TestSpansEmpty(t *testing.T) {
|
||||
client := &InMemoryOtlpClient{}
|
||||
err := client.UploadTraces(context.Background(), []*tracepb.ResourceSpans{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, client.spans, 0)
|
||||
_, err = client.Spans()
|
||||
assert.ErrorIs(t, err, errNoTraces)
|
||||
}
|
||||
|
||||
func TestSpansNil(t *testing.T) {
|
||||
client := &InMemoryOtlpClient{}
|
||||
err := client.UploadTraces(context.Background(), nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, client.spans, 0)
|
||||
_, err = client.Spans()
|
||||
assert.ErrorIs(t, err, errNoTraces)
|
||||
}
|
||||
|
||||
func TestSpansTooManySpans(t *testing.T) {
|
||||
client := &InMemoryOtlpClient{}
|
||||
for i := 0; i < MaxTraceAmount+1; i++ {
|
||||
spans := createResourceSpans([]*tracepb.Span{createOtlpSpan(traceId)})
|
||||
err := client.UploadTraces(context.Background(), spans)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Len(t, client.spans, MaxTraceAmount)
|
||||
_, err := client.Spans()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func createResourceSpans(spans []*tracepb.Span) []*tracepb.ResourceSpans {
|
||||
return []*tracepb.ResourceSpans{createResourceSpan(spans)}
|
||||
}
|
||||
|
||||
func createResourceSpan(spans []*tracepb.Span) *tracepb.ResourceSpans {
|
||||
return &tracepb.ResourceSpans{
|
||||
Resource: otlpResource,
|
||||
ScopeSpans: []*tracepb.ScopeSpans{
|
||||
{
|
||||
Scope: instrScope,
|
||||
Spans: spans,
|
||||
SchemaUrl: instrumentSchemaUrl,
|
||||
},
|
||||
},
|
||||
InstrumentationLibrarySpans: nil,
|
||||
SchemaUrl: resourceSchemaUrl,
|
||||
}
|
||||
}
|
||||
|
||||
func createOtlpSpan(tid []byte) *tracepb.Span {
|
||||
return &tracepb.Span{
|
||||
TraceId: tid,
|
||||
SpanId: spanId,
|
||||
TraceState: traceState.String(),
|
||||
ParentSpanId: parentSpanId,
|
||||
Name: "trace_name",
|
||||
Kind: tracepb.Span_SPAN_KIND_INTERNAL,
|
||||
StartTimeUnixNano: uint64(startTime.UnixNano()),
|
||||
EndTimeUnixNano: uint64(endTime.UnixNano()),
|
||||
Attributes: otlpKeyValues,
|
||||
DroppedAttributesCount: 0,
|
||||
Events: nil,
|
||||
DroppedEventsCount: 0,
|
||||
Links: nil,
|
||||
DroppedLinksCount: 0,
|
||||
Status: &tracepb.Status{
|
||||
Message: "status message",
|
||||
Code: tracepb.Status_STATUS_CODE_OK,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
otelContrib "go.opentelemetry.io/contrib/propagators/jaeger"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
service = "cloudflared"
|
||||
tracerInstrumentName = "origin"
|
||||
|
||||
TracerContextName = "cf-trace-id"
|
||||
TracerContextNameOverride = "uber-trace-id"
|
||||
|
||||
IntCloudflaredTracingHeader = "cf-int-cloudflared-tracing"
|
||||
|
||||
MaxErrorDescriptionLen = 100
|
||||
traceHttpStatusCodeKey = "upstreamStatusCode"
|
||||
)
|
||||
|
||||
var (
|
||||
CanonicalCloudflaredTracingHeader = http.CanonicalHeaderKey(IntCloudflaredTracingHeader)
|
||||
Http2TransportAttribute = trace.WithAttributes(transportAttributeKey.String("http2"))
|
||||
QuicTransportAttribute = trace.WithAttributes(transportAttributeKey.String("quic"))
|
||||
HostOSAttribute = semconv.HostTypeKey.String(runtime.GOOS)
|
||||
HostArchAttribute = semconv.HostArchKey.String(runtime.GOARCH)
|
||||
|
||||
otelVersionAttribute attribute.KeyValue
|
||||
hostnameAttribute attribute.KeyValue
|
||||
cloudflaredVersionAttribute attribute.KeyValue
|
||||
serviceAttribute = semconv.ServiceNameKey.String(service)
|
||||
|
||||
transportAttributeKey = attribute.Key("transport")
|
||||
otelVersionAttributeKey = attribute.Key("jaeger.version")
|
||||
|
||||
errNoopTracerProvider = errors.New("noop tracer provider records no spans")
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register the jaeger propagator globally.
|
||||
otel.SetTextMapPropagator(otelContrib.Jaeger{})
|
||||
otelVersionAttribute = otelVersionAttributeKey.String(fmt.Sprintf("go-otel-%s", otel.Version()))
|
||||
if hostname, err := os.Hostname(); err == nil {
|
||||
hostnameAttribute = attribute.String("hostname", hostname)
|
||||
}
|
||||
}
|
||||
|
||||
func Init(version string) {
|
||||
cloudflaredVersionAttribute = semconv.ProcessRuntimeVersionKey.String(version)
|
||||
}
|
||||
|
||||
type TracedRequest struct {
|
||||
*http.Request
|
||||
trace.TracerProvider
|
||||
exporter InMemoryClient
|
||||
}
|
||||
|
||||
// NewTracedRequest creates a new tracer for the current request context.
|
||||
func NewTracedRequest(req *http.Request) *TracedRequest {
|
||||
ctx, exists := extractTrace(req)
|
||||
if !exists {
|
||||
return &TracedRequest{req, trace.NewNoopTracerProvider(), &NoopOtlpClient{}}
|
||||
}
|
||||
mc := new(InMemoryOtlpClient)
|
||||
exp, err := otlptrace.New(req.Context(), mc)
|
||||
if err != nil {
|
||||
return &TracedRequest{req, trace.NewNoopTracerProvider(), &NoopOtlpClient{}}
|
||||
}
|
||||
tp := tracesdk.NewTracerProvider(
|
||||
// We want to dump to in-memory exporter immediately
|
||||
tracesdk.WithSyncer(exp),
|
||||
// Record information about this application in a Resource.
|
||||
tracesdk.WithResource(resource.NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
serviceAttribute,
|
||||
otelVersionAttribute,
|
||||
hostnameAttribute,
|
||||
cloudflaredVersionAttribute,
|
||||
HostOSAttribute,
|
||||
HostArchAttribute,
|
||||
)),
|
||||
)
|
||||
|
||||
return &TracedRequest{req.WithContext(ctx), tp, mc}
|
||||
}
|
||||
|
||||
func (cft *TracedRequest) Tracer() trace.Tracer {
|
||||
return cft.TracerProvider.Tracer(tracerInstrumentName)
|
||||
}
|
||||
|
||||
// Spans returns the spans as base64 encoded protobuf otlp traces.
|
||||
func (cft *TracedRequest) AddSpans(headers http.Header, log *zerolog.Logger) {
|
||||
if headers == nil {
|
||||
log.Error().Msgf("provided headers map is nil")
|
||||
return
|
||||
}
|
||||
|
||||
enc, err := cft.exporter.Spans()
|
||||
switch err {
|
||||
case nil:
|
||||
break
|
||||
case errNoTraces:
|
||||
log.Error().Err(err).Msgf("expected traces to be available")
|
||||
return
|
||||
case errNoopTracer:
|
||||
return // noop tracer has no traces
|
||||
default:
|
||||
log.Error().Err(err)
|
||||
return
|
||||
}
|
||||
// No need to add header if no traces
|
||||
if enc == "" {
|
||||
log.Error().Msgf("no traces provided and no error from exporter")
|
||||
return
|
||||
}
|
||||
|
||||
headers[CanonicalCloudflaredTracingHeader] = []string{enc}
|
||||
}
|
||||
|
||||
// EndWithErrorStatus will set a status for the span and then end it.
|
||||
func EndWithErrorStatus(span trace.Span, err error) {
|
||||
endSpan(span, -1, codes.Error, err)
|
||||
}
|
||||
|
||||
// EndWithStatusCode will set a status for the span and then end it.
|
||||
func EndWithStatusCode(span trace.Span, statusCode int) {
|
||||
endSpan(span, statusCode, codes.Ok, nil)
|
||||
}
|
||||
|
||||
// EndWithErrorStatus will set a status for the span and then end it.
|
||||
func endSpan(span trace.Span, upstreamStatusCode int, spanStatusCode codes.Code, err error) {
|
||||
if span == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if upstreamStatusCode > 0 {
|
||||
span.SetAttributes(attribute.Int(traceHttpStatusCodeKey, upstreamStatusCode))
|
||||
}
|
||||
|
||||
// add error to status buf cap description
|
||||
errDescription := ""
|
||||
if err != nil {
|
||||
errDescription = err.Error()
|
||||
l := int(math.Min(float64(len(errDescription)), MaxErrorDescriptionLen))
|
||||
errDescription = errDescription[:l]
|
||||
}
|
||||
|
||||
span.SetStatus(spanStatusCode, errDescription)
|
||||
span.End()
|
||||
}
|
||||
|
||||
// extractTrace attempts to check for a cf-trace-id from a request and return the
|
||||
// trace context with the provided http.Request.
|
||||
func extractTrace(req *http.Request) (context.Context, bool) {
|
||||
// Only add tracing for requests with appropriately tagged headers
|
||||
remoteTraces := req.Header.Values(TracerContextName)
|
||||
if len(remoteTraces) <= 0 {
|
||||
// Strip the cf-trace-id header
|
||||
req.Header.Del(TracerContextName)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
traceHeader := map[string]string{}
|
||||
for _, t := range remoteTraces {
|
||||
// Override the 'cf-trace-id' as 'uber-trace-id' so the jaeger propagator can extract it.
|
||||
// Last entry wins if multiple provided
|
||||
traceHeader[TracerContextNameOverride] = t
|
||||
}
|
||||
|
||||
// Strip the cf-trace-id header
|
||||
req.Header.Del(TracerContextName)
|
||||
|
||||
if traceHeader[TracerContextNameOverride] == "" {
|
||||
return nil, false
|
||||
}
|
||||
remoteCtx := otel.GetTextMapPropagator().Extract(req.Context(), propagation.MapCarrier(traceHeader))
|
||||
return remoteCtx, true
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
)
|
||||
|
||||
func TestNewCfTracer(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "http://localhost", nil)
|
||||
req.Header.Add(TracerContextName, "14cb070dde8e51fc5ae8514e69ba42ca:b38f1bf5eae406f3:0:1")
|
||||
tr := NewTracedRequest(req)
|
||||
assert.NotNil(t, tr)
|
||||
assert.IsType(t, tracesdk.NewTracerProvider(), tr.TracerProvider)
|
||||
assert.IsType(t, &InMemoryOtlpClient{}, tr.exporter)
|
||||
}
|
||||
|
||||
func TestNewCfTracerMultiple(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "http://localhost", nil)
|
||||
req.Header.Add(TracerContextName, "1241ce3ecdefc68854e8514e69ba42ca:b38f1bf5eae406f3:0:1")
|
||||
req.Header.Add(TracerContextName, "14cb070dde8e51fc5ae8514e69ba42ca:b38f1bf5eae406f3:0:1")
|
||||
tr := NewTracedRequest(req)
|
||||
assert.NotNil(t, tr)
|
||||
assert.IsType(t, tracesdk.NewTracerProvider(), tr.TracerProvider)
|
||||
assert.IsType(t, &InMemoryOtlpClient{}, tr.exporter)
|
||||
}
|
||||
|
||||
func TestNewCfTracerNilHeader(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "http://localhost", nil)
|
||||
req.Header[http.CanonicalHeaderKey(TracerContextName)] = nil
|
||||
tr := NewTracedRequest(req)
|
||||
assert.NotNil(t, tr)
|
||||
assert.IsType(t, trace.NewNoopTracerProvider(), tr.TracerProvider)
|
||||
assert.IsType(t, &NoopOtlpClient{}, tr.exporter)
|
||||
}
|
||||
|
||||
func TestNewCfTracerInvalidHeaders(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "http://localhost", nil)
|
||||
for _, test := range [][]string{nil, {""}} {
|
||||
req.Header[http.CanonicalHeaderKey(TracerContextName)] = test
|
||||
tr := NewTracedRequest(req)
|
||||
assert.NotNil(t, tr)
|
||||
assert.IsType(t, trace.NewNoopTracerProvider(), tr.TracerProvider)
|
||||
assert.IsType(t, &NoopOtlpClient{}, tr.exporter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddingSpansWithNilMap(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "http://localhost", nil)
|
||||
req.Header.Add(TracerContextName, "14cb070dde8e51fc5ae8514e69ba42ca:b38f1bf5eae406f3:0:1")
|
||||
tr := NewTracedRequest(req)
|
||||
|
||||
exporter := tr.exporter.(*InMemoryOtlpClient)
|
||||
|
||||
// add fake spans
|
||||
spans := createResourceSpans([]*tracepb.Span{createOtlpSpan(traceId)})
|
||||
err := exporter.UploadTraces(context.Background(), spans)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// a panic shouldn't occur
|
||||
tr.AddSpans(nil, &zerolog.Logger{})
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
type RegistrationServer interface {
|
||||
RegisterConnection(ctx context.Context, auth TunnelAuth, tunnelID uuid.UUID, connIndex byte, options *ConnectionOptions) (*ConnectionDetails, error)
|
||||
UnregisterConnection(ctx context.Context)
|
||||
UpdateLocalConfiguration(ctx context.Context, config []byte) error
|
||||
}
|
||||
|
||||
type RegistrationServer_PogsImpl struct {
|
||||
@@ -88,6 +89,17 @@ func (i RegistrationServer_PogsImpl) UnregisterConnection(p tunnelrpc.Registrati
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i RegistrationServer_PogsImpl) UpdateLocalConfiguration(c tunnelrpc.RegistrationServer_updateLocalConfiguration) error {
|
||||
server.Ack(c.Options)
|
||||
|
||||
configBytes, err := c.Params.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return i.impl.UpdateLocalConfiguration(c.Ctx, configBytes)
|
||||
}
|
||||
|
||||
type RegistrationServer_PogsClient struct {
|
||||
Client capnp.Client
|
||||
Conn *rpc.Conn
|
||||
@@ -163,6 +175,24 @@ func (c RegistrationServer_PogsClient) RegisterConnection(ctx context.Context, a
|
||||
return nil, newRPCError("unknown result which %d", result.Which())
|
||||
}
|
||||
|
||||
func (c RegistrationServer_PogsClient) SendLocalConfiguration(ctx context.Context, config []byte) error {
|
||||
client := tunnelrpc.TunnelServer{Client: c.Client}
|
||||
promise := client.UpdateLocalConfiguration(ctx, func(p tunnelrpc.RegistrationServer_updateLocalConfiguration_Params) error {
|
||||
if err := p.SetConfig(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
_, err := promise.Struct()
|
||||
if err != nil {
|
||||
return wrapRPCError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c RegistrationServer_PogsClient) UnregisterConnection(ctx context.Context) error {
|
||||
client := tunnelrpc.TunnelServer{Client: c.Client}
|
||||
promise := client.UnregisterConnection(ctx, func(p tunnelrpc.RegistrationServer_unregisterConnection_Params) error {
|
||||
@@ -212,8 +242,9 @@ func (a *TunnelAuth) UnmarshalCapnproto(s tunnelrpc.TunnelAuth) error {
|
||||
}
|
||||
|
||||
type ConnectionDetails struct {
|
||||
UUID uuid.UUID
|
||||
Location string
|
||||
UUID uuid.UUID
|
||||
Location string
|
||||
TunnelIsRemotelyManaged bool
|
||||
}
|
||||
|
||||
func (details *ConnectionDetails) MarshalCapnproto(s tunnelrpc.ConnectionDetails) error {
|
||||
@@ -223,6 +254,7 @@ func (details *ConnectionDetails) MarshalCapnproto(s tunnelrpc.ConnectionDetails
|
||||
if err := s.SetLocationName(details.Location); err != nil {
|
||||
return err
|
||||
}
|
||||
s.SetTunnelIsRemotelyManaged(details.TunnelIsRemotelyManaged)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -240,6 +272,7 @@ func (details *ConnectionDetails) UnmarshalCapnproto(s tunnelrpc.ConnectionDetai
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
details.TunnelIsRemotelyManaged = s.TunnelIsRemotelyManaged()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -129,6 +129,11 @@ type testConnectionRegistrationServer struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (t *testConnectionRegistrationServer) UpdateLocalConfiguration(ctx context.Context, config []byte) error {
|
||||
// do nothing at this point
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *testConnectionRegistrationServer) RegisterConnection(ctx context.Context, auth TunnelAuth, tunnelID uuid.UUID, connIndex byte, options *ConnectionOptions) (*ConnectionDetails, error) {
|
||||
if auth.AccountTag != testAccountTag {
|
||||
panic("bad account tag: " + auth.AccountTag)
|
||||
|
||||
@@ -121,6 +121,8 @@ struct ConnectionDetails {
|
||||
uuid @0 :Data;
|
||||
# airport code of the colo where this connection landed
|
||||
locationName @1 :Text;
|
||||
# tells if the tunnel is remotely managed
|
||||
tunnelIsRemotelyManaged @2: Bool;
|
||||
}
|
||||
|
||||
struct TunnelAuth {
|
||||
@@ -131,6 +133,7 @@ struct TunnelAuth {
|
||||
interface RegistrationServer {
|
||||
registerConnection @0 (auth :TunnelAuth, tunnelId :Data, connIndex :UInt8, options :ConnectionOptions) -> (result :ConnectionResponse);
|
||||
unregisterConnection @1 () -> ();
|
||||
updateLocalConfiguration @2 (config :Data) -> ();
|
||||
}
|
||||
|
||||
interface TunnelServer extends (RegistrationServer) {
|
||||
|
||||
+448
-223
@@ -1410,12 +1410,12 @@ type ConnectionDetails struct{ capnp.Struct }
|
||||
const ConnectionDetails_TypeID = 0xb5f39f082b9ac18a
|
||||
|
||||
func NewConnectionDetails(s *capnp.Segment) (ConnectionDetails, error) {
|
||||
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
|
||||
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 2})
|
||||
return ConnectionDetails{st}, err
|
||||
}
|
||||
|
||||
func NewRootConnectionDetails(s *capnp.Segment) (ConnectionDetails, error) {
|
||||
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
|
||||
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 2})
|
||||
return ConnectionDetails{st}, err
|
||||
}
|
||||
|
||||
@@ -1462,12 +1462,20 @@ func (s ConnectionDetails) SetLocationName(v string) error {
|
||||
return s.Struct.SetText(1, v)
|
||||
}
|
||||
|
||||
func (s ConnectionDetails) TunnelIsRemotelyManaged() bool {
|
||||
return s.Struct.Bit(0)
|
||||
}
|
||||
|
||||
func (s ConnectionDetails) SetTunnelIsRemotelyManaged(v bool) {
|
||||
s.Struct.SetBit(0, v)
|
||||
}
|
||||
|
||||
// ConnectionDetails_List is a list of ConnectionDetails.
|
||||
type ConnectionDetails_List struct{ capnp.List }
|
||||
|
||||
// NewConnectionDetails creates a new list of ConnectionDetails.
|
||||
func NewConnectionDetails_List(s *capnp.Segment, sz int32) (ConnectionDetails_List, error) {
|
||||
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2}, sz)
|
||||
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 2}, sz)
|
||||
return ConnectionDetails_List{l}, err
|
||||
}
|
||||
|
||||
@@ -1621,11 +1629,35 @@ func (c RegistrationServer) UnregisterConnection(ctx context.Context, params fun
|
||||
}
|
||||
return RegistrationServer_unregisterConnection_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
|
||||
}
|
||||
func (c RegistrationServer) UpdateLocalConfiguration(ctx context.Context, params func(RegistrationServer_updateLocalConfiguration_Params) error, opts ...capnp.CallOption) RegistrationServer_updateLocalConfiguration_Results_Promise {
|
||||
if c.Client == nil {
|
||||
return RegistrationServer_updateLocalConfiguration_Results_Promise{Pipeline: capnp.NewPipeline(capnp.ErrorAnswer(capnp.ErrNullClient))}
|
||||
}
|
||||
call := &capnp.Call{
|
||||
Ctx: ctx,
|
||||
Method: capnp.Method{
|
||||
InterfaceID: 0xf71695ec7fe85497,
|
||||
MethodID: 2,
|
||||
InterfaceName: "tunnelrpc/tunnelrpc.capnp:RegistrationServer",
|
||||
MethodName: "updateLocalConfiguration",
|
||||
},
|
||||
Options: capnp.NewCallOptions(opts),
|
||||
}
|
||||
if params != nil {
|
||||
call.ParamsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 1}
|
||||
call.ParamsFunc = func(s capnp.Struct) error {
|
||||
return params(RegistrationServer_updateLocalConfiguration_Params{Struct: s})
|
||||
}
|
||||
}
|
||||
return RegistrationServer_updateLocalConfiguration_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
|
||||
}
|
||||
|
||||
type RegistrationServer_Server interface {
|
||||
RegisterConnection(RegistrationServer_registerConnection) error
|
||||
|
||||
UnregisterConnection(RegistrationServer_unregisterConnection) error
|
||||
|
||||
UpdateLocalConfiguration(RegistrationServer_updateLocalConfiguration) error
|
||||
}
|
||||
|
||||
func RegistrationServer_ServerToClient(s RegistrationServer_Server) RegistrationServer {
|
||||
@@ -1635,7 +1667,7 @@ func RegistrationServer_ServerToClient(s RegistrationServer_Server) Registration
|
||||
|
||||
func RegistrationServer_Methods(methods []server.Method, s RegistrationServer_Server) []server.Method {
|
||||
if cap(methods) == 0 {
|
||||
methods = make([]server.Method, 0, 2)
|
||||
methods = make([]server.Method, 0, 3)
|
||||
}
|
||||
|
||||
methods = append(methods, server.Method{
|
||||
@@ -1666,6 +1698,20 @@ func RegistrationServer_Methods(methods []server.Method, s RegistrationServer_Se
|
||||
ResultsSize: capnp.ObjectSize{DataSize: 0, PointerCount: 0},
|
||||
})
|
||||
|
||||
methods = append(methods, server.Method{
|
||||
Method: capnp.Method{
|
||||
InterfaceID: 0xf71695ec7fe85497,
|
||||
MethodID: 2,
|
||||
InterfaceName: "tunnelrpc/tunnelrpc.capnp:RegistrationServer",
|
||||
MethodName: "updateLocalConfiguration",
|
||||
},
|
||||
Impl: func(c context.Context, opts capnp.CallOptions, p, r capnp.Struct) error {
|
||||
call := RegistrationServer_updateLocalConfiguration{c, opts, RegistrationServer_updateLocalConfiguration_Params{Struct: p}, RegistrationServer_updateLocalConfiguration_Results{Struct: r}}
|
||||
return s.UpdateLocalConfiguration(call)
|
||||
},
|
||||
ResultsSize: capnp.ObjectSize{DataSize: 0, PointerCount: 0},
|
||||
})
|
||||
|
||||
return methods
|
||||
}
|
||||
|
||||
@@ -1685,6 +1731,14 @@ type RegistrationServer_unregisterConnection struct {
|
||||
Results RegistrationServer_unregisterConnection_Results
|
||||
}
|
||||
|
||||
// RegistrationServer_updateLocalConfiguration holds the arguments for a server call to RegistrationServer.updateLocalConfiguration.
|
||||
type RegistrationServer_updateLocalConfiguration struct {
|
||||
Ctx context.Context
|
||||
Options capnp.CallOptions
|
||||
Params RegistrationServer_updateLocalConfiguration_Params
|
||||
Results RegistrationServer_updateLocalConfiguration_Results
|
||||
}
|
||||
|
||||
type RegistrationServer_registerConnection_Params struct{ capnp.Struct }
|
||||
|
||||
// RegistrationServer_registerConnection_Params_TypeID is the unique identifier for the type RegistrationServer_registerConnection_Params.
|
||||
@@ -2014,6 +2068,130 @@ func (p RegistrationServer_unregisterConnection_Results_Promise) Struct() (Regis
|
||||
return RegistrationServer_unregisterConnection_Results{s}, err
|
||||
}
|
||||
|
||||
type RegistrationServer_updateLocalConfiguration_Params struct{ capnp.Struct }
|
||||
|
||||
// RegistrationServer_updateLocalConfiguration_Params_TypeID is the unique identifier for the type RegistrationServer_updateLocalConfiguration_Params.
|
||||
const RegistrationServer_updateLocalConfiguration_Params_TypeID = 0xc5d6e311876a3604
|
||||
|
||||
func NewRegistrationServer_updateLocalConfiguration_Params(s *capnp.Segment) (RegistrationServer_updateLocalConfiguration_Params, error) {
|
||||
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
|
||||
return RegistrationServer_updateLocalConfiguration_Params{st}, err
|
||||
}
|
||||
|
||||
func NewRootRegistrationServer_updateLocalConfiguration_Params(s *capnp.Segment) (RegistrationServer_updateLocalConfiguration_Params, error) {
|
||||
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
|
||||
return RegistrationServer_updateLocalConfiguration_Params{st}, err
|
||||
}
|
||||
|
||||
func ReadRootRegistrationServer_updateLocalConfiguration_Params(msg *capnp.Message) (RegistrationServer_updateLocalConfiguration_Params, error) {
|
||||
root, err := msg.RootPtr()
|
||||
return RegistrationServer_updateLocalConfiguration_Params{root.Struct()}, err
|
||||
}
|
||||
|
||||
func (s RegistrationServer_updateLocalConfiguration_Params) String() string {
|
||||
str, _ := text.Marshal(0xc5d6e311876a3604, s.Struct)
|
||||
return str
|
||||
}
|
||||
|
||||
func (s RegistrationServer_updateLocalConfiguration_Params) Config() ([]byte, error) {
|
||||
p, err := s.Struct.Ptr(0)
|
||||
return []byte(p.Data()), err
|
||||
}
|
||||
|
||||
func (s RegistrationServer_updateLocalConfiguration_Params) HasConfig() bool {
|
||||
p, err := s.Struct.Ptr(0)
|
||||
return p.IsValid() || err != nil
|
||||
}
|
||||
|
||||
func (s RegistrationServer_updateLocalConfiguration_Params) SetConfig(v []byte) error {
|
||||
return s.Struct.SetData(0, v)
|
||||
}
|
||||
|
||||
// RegistrationServer_updateLocalConfiguration_Params_List is a list of RegistrationServer_updateLocalConfiguration_Params.
|
||||
type RegistrationServer_updateLocalConfiguration_Params_List struct{ capnp.List }
|
||||
|
||||
// NewRegistrationServer_updateLocalConfiguration_Params creates a new list of RegistrationServer_updateLocalConfiguration_Params.
|
||||
func NewRegistrationServer_updateLocalConfiguration_Params_List(s *capnp.Segment, sz int32) (RegistrationServer_updateLocalConfiguration_Params_List, error) {
|
||||
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz)
|
||||
return RegistrationServer_updateLocalConfiguration_Params_List{l}, err
|
||||
}
|
||||
|
||||
func (s RegistrationServer_updateLocalConfiguration_Params_List) At(i int) RegistrationServer_updateLocalConfiguration_Params {
|
||||
return RegistrationServer_updateLocalConfiguration_Params{s.List.Struct(i)}
|
||||
}
|
||||
|
||||
func (s RegistrationServer_updateLocalConfiguration_Params_List) Set(i int, v RegistrationServer_updateLocalConfiguration_Params) error {
|
||||
return s.List.SetStruct(i, v.Struct)
|
||||
}
|
||||
|
||||
func (s RegistrationServer_updateLocalConfiguration_Params_List) String() string {
|
||||
str, _ := text.MarshalList(0xc5d6e311876a3604, s.List)
|
||||
return str
|
||||
}
|
||||
|
||||
// RegistrationServer_updateLocalConfiguration_Params_Promise is a wrapper for a RegistrationServer_updateLocalConfiguration_Params promised by a client call.
|
||||
type RegistrationServer_updateLocalConfiguration_Params_Promise struct{ *capnp.Pipeline }
|
||||
|
||||
func (p RegistrationServer_updateLocalConfiguration_Params_Promise) Struct() (RegistrationServer_updateLocalConfiguration_Params, error) {
|
||||
s, err := p.Pipeline.Struct()
|
||||
return RegistrationServer_updateLocalConfiguration_Params{s}, err
|
||||
}
|
||||
|
||||
type RegistrationServer_updateLocalConfiguration_Results struct{ capnp.Struct }
|
||||
|
||||
// RegistrationServer_updateLocalConfiguration_Results_TypeID is the unique identifier for the type RegistrationServer_updateLocalConfiguration_Results.
|
||||
const RegistrationServer_updateLocalConfiguration_Results_TypeID = 0xe5ceae5d6897d7be
|
||||
|
||||
func NewRegistrationServer_updateLocalConfiguration_Results(s *capnp.Segment) (RegistrationServer_updateLocalConfiguration_Results, error) {
|
||||
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0})
|
||||
return RegistrationServer_updateLocalConfiguration_Results{st}, err
|
||||
}
|
||||
|
||||
func NewRootRegistrationServer_updateLocalConfiguration_Results(s *capnp.Segment) (RegistrationServer_updateLocalConfiguration_Results, error) {
|
||||
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0})
|
||||
return RegistrationServer_updateLocalConfiguration_Results{st}, err
|
||||
}
|
||||
|
||||
func ReadRootRegistrationServer_updateLocalConfiguration_Results(msg *capnp.Message) (RegistrationServer_updateLocalConfiguration_Results, error) {
|
||||
root, err := msg.RootPtr()
|
||||
return RegistrationServer_updateLocalConfiguration_Results{root.Struct()}, err
|
||||
}
|
||||
|
||||
func (s RegistrationServer_updateLocalConfiguration_Results) String() string {
|
||||
str, _ := text.Marshal(0xe5ceae5d6897d7be, s.Struct)
|
||||
return str
|
||||
}
|
||||
|
||||
// RegistrationServer_updateLocalConfiguration_Results_List is a list of RegistrationServer_updateLocalConfiguration_Results.
|
||||
type RegistrationServer_updateLocalConfiguration_Results_List struct{ capnp.List }
|
||||
|
||||
// NewRegistrationServer_updateLocalConfiguration_Results creates a new list of RegistrationServer_updateLocalConfiguration_Results.
|
||||
func NewRegistrationServer_updateLocalConfiguration_Results_List(s *capnp.Segment, sz int32) (RegistrationServer_updateLocalConfiguration_Results_List, error) {
|
||||
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0}, sz)
|
||||
return RegistrationServer_updateLocalConfiguration_Results_List{l}, err
|
||||
}
|
||||
|
||||
func (s RegistrationServer_updateLocalConfiguration_Results_List) At(i int) RegistrationServer_updateLocalConfiguration_Results {
|
||||
return RegistrationServer_updateLocalConfiguration_Results{s.List.Struct(i)}
|
||||
}
|
||||
|
||||
func (s RegistrationServer_updateLocalConfiguration_Results_List) Set(i int, v RegistrationServer_updateLocalConfiguration_Results) error {
|
||||
return s.List.SetStruct(i, v.Struct)
|
||||
}
|
||||
|
||||
func (s RegistrationServer_updateLocalConfiguration_Results_List) String() string {
|
||||
str, _ := text.MarshalList(0xe5ceae5d6897d7be, s.List)
|
||||
return str
|
||||
}
|
||||
|
||||
// RegistrationServer_updateLocalConfiguration_Results_Promise is a wrapper for a RegistrationServer_updateLocalConfiguration_Results promised by a client call.
|
||||
type RegistrationServer_updateLocalConfiguration_Results_Promise struct{ *capnp.Pipeline }
|
||||
|
||||
func (p RegistrationServer_updateLocalConfiguration_Results_Promise) Struct() (RegistrationServer_updateLocalConfiguration_Results, error) {
|
||||
s, err := p.Pipeline.Struct()
|
||||
return RegistrationServer_updateLocalConfiguration_Results{s}, err
|
||||
}
|
||||
|
||||
type TunnelServer struct{ Client capnp.Client }
|
||||
|
||||
// TunnelServer_TypeID is the unique identifier for the type TunnelServer.
|
||||
@@ -2181,6 +2359,28 @@ func (c TunnelServer) UnregisterConnection(ctx context.Context, params func(Regi
|
||||
}
|
||||
return RegistrationServer_unregisterConnection_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
|
||||
}
|
||||
func (c TunnelServer) UpdateLocalConfiguration(ctx context.Context, params func(RegistrationServer_updateLocalConfiguration_Params) error, opts ...capnp.CallOption) RegistrationServer_updateLocalConfiguration_Results_Promise {
|
||||
if c.Client == nil {
|
||||
return RegistrationServer_updateLocalConfiguration_Results_Promise{Pipeline: capnp.NewPipeline(capnp.ErrorAnswer(capnp.ErrNullClient))}
|
||||
}
|
||||
call := &capnp.Call{
|
||||
Ctx: ctx,
|
||||
Method: capnp.Method{
|
||||
InterfaceID: 0xf71695ec7fe85497,
|
||||
MethodID: 2,
|
||||
InterfaceName: "tunnelrpc/tunnelrpc.capnp:RegistrationServer",
|
||||
MethodName: "updateLocalConfiguration",
|
||||
},
|
||||
Options: capnp.NewCallOptions(opts),
|
||||
}
|
||||
if params != nil {
|
||||
call.ParamsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 1}
|
||||
call.ParamsFunc = func(s capnp.Struct) error {
|
||||
return params(RegistrationServer_updateLocalConfiguration_Params{Struct: s})
|
||||
}
|
||||
}
|
||||
return RegistrationServer_updateLocalConfiguration_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
|
||||
}
|
||||
|
||||
type TunnelServer_Server interface {
|
||||
RegisterTunnel(TunnelServer_registerTunnel) error
|
||||
@@ -2198,6 +2398,8 @@ type TunnelServer_Server interface {
|
||||
RegisterConnection(RegistrationServer_registerConnection) error
|
||||
|
||||
UnregisterConnection(RegistrationServer_unregisterConnection) error
|
||||
|
||||
UpdateLocalConfiguration(RegistrationServer_updateLocalConfiguration) error
|
||||
}
|
||||
|
||||
func TunnelServer_ServerToClient(s TunnelServer_Server) TunnelServer {
|
||||
@@ -2207,7 +2409,7 @@ func TunnelServer_ServerToClient(s TunnelServer_Server) TunnelServer {
|
||||
|
||||
func TunnelServer_Methods(methods []server.Method, s TunnelServer_Server) []server.Method {
|
||||
if cap(methods) == 0 {
|
||||
methods = make([]server.Method, 0, 8)
|
||||
methods = make([]server.Method, 0, 9)
|
||||
}
|
||||
|
||||
methods = append(methods, server.Method{
|
||||
@@ -2322,6 +2524,20 @@ func TunnelServer_Methods(methods []server.Method, s TunnelServer_Server) []serv
|
||||
ResultsSize: capnp.ObjectSize{DataSize: 0, PointerCount: 0},
|
||||
})
|
||||
|
||||
methods = append(methods, server.Method{
|
||||
Method: capnp.Method{
|
||||
InterfaceID: 0xf71695ec7fe85497,
|
||||
MethodID: 2,
|
||||
InterfaceName: "tunnelrpc/tunnelrpc.capnp:RegistrationServer",
|
||||
MethodName: "updateLocalConfiguration",
|
||||
},
|
||||
Impl: func(c context.Context, opts capnp.CallOptions, p, r capnp.Struct) error {
|
||||
call := RegistrationServer_updateLocalConfiguration{c, opts, RegistrationServer_updateLocalConfiguration_Params{Struct: p}, RegistrationServer_updateLocalConfiguration_Results{Struct: r}}
|
||||
return s.UpdateLocalConfiguration(call)
|
||||
},
|
||||
ResultsSize: capnp.ObjectSize{DataSize: 0, PointerCount: 0},
|
||||
})
|
||||
|
||||
return methods
|
||||
}
|
||||
|
||||
@@ -4317,224 +4533,231 @@ func CloudflaredServer_Methods(methods []server.Method, s CloudflaredServer_Serv
|
||||
return methods
|
||||
}
|
||||
|
||||
const schema_db8274f9144abc7e = "x\xda\xccZ{t\x14\xe7u\xbfwfW#\x81V" +
|
||||
"\xab\xf1\xac\xd1\x03T\xb5:P\x179\xd8\x06Jk\xab" +
|
||||
"9\xd1\xc3\x12\xb1d\x03\x9a]\x94\xe3cC\x8eG\xbb" +
|
||||
"\x9f\xa4Qwg\x96\x99Y\x19\x11\x130\x01c\xfb\xb8" +
|
||||
"\x8eq\xc0\xb1Ih0.\xed\x01\xdb\xad\x89\xdd\xa6\xee" +
|
||||
"1\xa7\xa6\xcd\xabq\xc0&\x87\xf4\x90@\x9a&\x84>" +
|
||||
"8\xb8\xae14\x876\xf1\xf4\xdc\x99\x9d\x87v\x17\x09" +
|
||||
"\x8c\xff\xc8\x7f\xab;\xdf\xe3\xde\xdf\xf7\xbb\x8f\xef~\xba" +
|
||||
"\xed\xe6\x9a.nq\xf4\xed:\x00\xf9\xa5h\x95\xcd\xda" +
|
||||
"\x7f\xb0a\xef\x82\x7f\xdc\x02r3\xa2\xfd\xf97\x06\x12" +
|
||||
"\x97\xad-\xa7 \xca\x0b\x00KW\x08\x1bPR\x04\x01" +
|
||||
"@Z+\xfc;\xa0\xfd\xc8\x9cW\xbe\xb6\xbfo\xe7\x17" +
|
||||
"@l\xe6\x83\xc1\x80K\xbb\xab\x07P\x1a\xaa\xa6\x91r" +
|
||||
"\xf5v\xe9\x10\xfd\xb2\xef\x16o\xbd?\xf1\xce1\x1a\x1d" +
|
||||
"^:BK?W\xdd\x8e\xd2\x01g\xc2\xfejZ\xfa" +
|
||||
"\x93\xb9\xb7\xf7\xfd\xc1\xae\xb7\xb6\x82\xd8\xccMYzG" +
|
||||
"\xcd\x06\x94\xf6\xd7\xd0\xc8\xe7kV\x01\xda\x1f\xecl|" +
|
||||
"\xf1\xf9c\xdf\xdd\x06\xe2M\x08EM_\xaf\xf91\x02" +
|
||||
"JGk\xfe\x0a\xd0>z\xe9\xfe\x8b\xaf}{\xd9#" +
|
||||
" .\xa4\x01H\x03r\xb3\xda8@i\xdb\xacN@" +
|
||||
"\xfb\xdc\xf9\xff\xdb\xfe\xb9\x85+\x9f\x02y!r\x00Q" +
|
||||
"\x8eF\xec\x9f\xd5L#\x0e\xcf\"m:\x97\x1f}\xbd" +
|
||||
"y\xe93;KTw\x06\xee\x99\xdd\x8e\xd2\xcb\xb3I" +
|
||||
"\xa1\x03\xb3\x1f\x04\xb4?\xf5\x87\xaf>\xd9\xfb\xcc\xe6]" +
|
||||
" \xde\xea\xef\x17\xab\xbd\x8fV[XK\xfb\xfdO\xdd" +
|
||||
"W\x8e\x15\xee\xfc\xc63E\x85\x9cU\xfak\xdbi\x80" +
|
||||
"RK+\xb4M,x\xe0\x1f\xbe\xf5\xea\x97A^\x84" +
|
||||
"h\x9f\x1e\xbe\xf9\x87\xfc\x9e\x83\xa7`\x08\x05\xd2o\xe9" +
|
||||
"\x91\xda}d\xddqg\xec\xdb\x9fx\xe3\xef\x9ezu" +
|
||||
"\xfbW@\xbe\x09\x11\xc0AsY\xec\x7fi@\x7f\x8c" +
|
||||
"v\xdby\xf2\xf0\xca\xdc\x8e\xdd\xfb\\|\x9c\xef\xebb" +
|
||||
"\x1c\x07\x11{k\xff/sC/\xa4^(\"\x17\xa5" +
|
||||
"O,v\x01\x01\x97N\xc6Z\x11\xd0^\xf6\xe3\xb3\xab" +
|
||||
"V|}\xe4/Bsw\xd5m\xa0\xb9\xdbG.\x1c" +
|
||||
"\xa9O\xe6^,A\xc41vG\xddA\x94\x0e\xd49" +
|
||||
"\x87YG*\xbc\xfc[w\xd7\xac?\xbb\xfc\x15\x10\x17" +
|
||||
"y\xcb|\xab.I\xcbL|\xef\x85\xdf]\xf0\xbd\x07" +
|
||||
"\x0f\x81|+\xfa`\x1d\xa1o(\xfd\xa4\x8e\xec\x8b\x9c" +
|
||||
"Zp\xf0\xf0O\x9f|\xad\x8ccw\xc47\xa0\xb4\"" +
|
||||
"N\xbb\xf4\xc7?-M\xd2/;\xb2\x96\xffPy\xf6" +
|
||||
"\xef_+\xe5\xaf\x83\xb1\x12\x1fF\xa9\x10w\x10\x88;" +
|
||||
"\xf6=~d\xf7\xcd\xd5_\xfb\xe0\xaf+\x9d\xebs\xf5" +
|
||||
"\xc3(\xbd\\\xef\x9ck=irc?\x9e~sq" +
|
||||
"\xe4\x1ba\xa2\xd5\x88\xe7\x08\xe9\x16\x91\x88\xd6\xf2nO" +
|
||||
"L{o\xcb\x9b%\xab9\x03\x0f\x8b\x03(\x1d\x17i" +
|
||||
"\xb5\xa3\xce\xe0\x81\xfb\xbf\xf4t\xf4\xec\x97\xbeC\x9a\x86" +
|
||||
"\x18\x1e%\x1fX\xaa\xde`\xa0\xb4\xf1\x06\xfa9yC" +
|
||||
"\x03\x0fh7\xbf\xf2G\x7f\xd9\x93\xf9\xd1[\x154\x95" +
|
||||
".\xdfxA\x8a\xce\xa1_8\x87\x14=\xb3\xe8\xd0\xe7" +
|
||||
"\xfe\xf3O\x8e\x9f(*\xea`\xbav\x8eC\x89us" +
|
||||
"\xe8<.\xaf\xd9{\xb7j\xdf{\xaa\x14%\xf7\xf4\xe6" +
|
||||
"|\x1d\xa5\x03\xcer\xfb\x9d\xe5|\xfeU\x1a]\xd30" +
|
||||
"\x8eRK\x03\x8dnj\xa0\xb5\xb9\xb3J\xd3\xe6\x7f\xfe" +
|
||||
"\xd4\xe9\x10eZ\x1a~\x8e\x10\xb1W~\xe6\xfe\xf1\x9a" +
|
||||
"\x8dg\xce\x84\xd5\x12\x1b\x1c\xfc\x168S\xff\xeb\xcf\xcf" +
|
||||
"}\xf1|.\xf3o\x0e\xed=\x84\xfb\x1a:\x88\x0ck" +
|
||||
"\x1b\xc8\x0f\x1bZc}m'\x07\xcf\xb9Dr\x97\xb8" +
|
||||
"\xa3\xb1\x87\x06\xc8\x8d\xb4\xc4\xb2\x07\xba\xd9\x9a\xdb\xef=" +
|
||||
"W\xc6\x96u\x8d\x1d(=\xdcH\x1366nGi" +
|
||||
"WS\x03\x80=\xf17;\xee}\xf1\x9b+/\xb8\x9e" +
|
||||
"\xe8(\xbb\xadi\x09\x11\xf3\xc9\xcf\xf7\xae\xba\xa3\xed\xc8" +
|
||||
"\x85\xb0\xb2\x1b\x9b\xc87\xa4\x1dM\xb4\xd3\xc8\xed\xe7?" +
|
||||
"\xbd\xe0\xc9o_\xa8\xe4\x00\x87\x9a\xdaQ:\xd2D\xa0" +
|
||||
"\x1c\xa6\xc1\xef-\xff\xd3\x13\xcd\xf1\xe6\x8b%\x00V\xd1" +
|
||||
"\xd8\x9f5\x8d\xa3t\x89\xc6.}\xbf\xe9;D\xca\xe7" +
|
||||
"\xffl\xdf\xbf\\>v\xd7\xa52\x1b\xce\xce\x1dF\xe9" +
|
||||
"\xf2\\Z\xf6\xd2\\A\xba4\xf7&\x00\xfb\x91S\x9f" +
|
||||
"]\xff\x83/|p\xa9\x94G\x8e\"\xef\xceM\xa2\x84" +
|
||||
"\xf3h\xc6\xaf\xe7\x12\xeb\xbe\xbc\xfa?6\x9d\xdf5\xe7" +
|
||||
"\x97ek\xef\x997\x8e\xd2!g\xe4\xcb\xf3\xb6K\xb1" +
|
||||
"\x16\xf2\xa6w\x84\x17\x16\xf7nz\xebr\xc8o/\xcd" +
|
||||
"\x1b x\x9e\x11\xbezf\xf3O?\xfb\xab0<\xef" +
|
||||
"\xcf\xfb9\xc1\x13m!x\x1ez\xef\xb9\xbb\xbe\xb8\xe6" +
|
||||
"\xa5\x0fC4X\xd0\xb2\x85\xa6Z\x05McY#\x1f" +
|
||||
"I\xdf\xea\xfdL\xdf\x92V\xf2Z\xbe\xa3\xbb`\x8d1" +
|
||||
"\xcdR\xd3\x8a\xc5\x92\xac\xd3\xcc\xeb\x9a\xc9\x06\x11\xe5z" +
|
||||
">\x02\x10A\x00Q\x19\x07\x90\x1f\xe0Q\xcer(\"" +
|
||||
"&\x88(\xa2J\xc21\x1ee\x8bC\x91\xe3\x12\x14%" +
|
||||
"\xc5um\x00r\x96Gy=\x87\xc8'\x90\x07\x10\x0b" +
|
||||
"O\x03\xc8\xeby\x94\xb7rh\xe7\x99\x91S4\xa6A" +
|
||||
"\xdc\xea3\x0c\xac\x05\x0ek\x01m\x83Y\xc6\xa42\x9c" +
|
||||
"\x858\x0b\x89\x85\xf1\x07-\x8c\x01\x871@{L/" +
|
||||
"\x18\xe6\x90f\xa1\x9aM\xb2\x11\x83\x998\x86U\xc0a" +
|
||||
"\x15\xe0t\xe6\xa5\x98i\xaa\xba\xb6B\xd1\x94Qf\x00" +
|
||||
"\x90e\xd5|\x14\xc0\xcf@\xe8\xe5*q\xf1n\xe0\xc4" +
|
||||
"E\x02\x06\xd9\x02=\xb2\x8a\xbfs\x108\xb1E\xb0\x0d" +
|
||||
"6\xaa\x9a\x163p(\x93w\xd6\xe6u\xad\x0b\xed\x82" +
|
||||
"\xe6~@f\xb8\x1f\xe2\xb4k\x17\x0eb\xa0\x1d_\xae" +
|
||||
"\xdd\x9dY\x95iV\xbc_\x1b\xd1K \x1f\xa8\x04\xf9" +
|
||||
"@\x11\xf2\xad!\xc8\x1f\xee\x01\x90\x1f\xe2Q~\x94C" +
|
||||
"\x91/b\xbe\xad\x1d@\xde\xcc\xa3\xfc\x04\x87v\xda\xd9" +
|
||||
"\xa4?\x03\x00>\x9a#L\xb1\x0a\x063IV\x078" +
|
||||
"\xc8\xa3\x03z\x1d\xe0\xa6\x09f\x90\xee\xde!\xc4\x15#" +
|
||||
"=\xe6\x1f\xd44H\xf7\xadWMK\xd5FW;\xf2" +
|
||||
"\xceA=\xab\xa6'\xc9\xaaZG\xcf\x96\x0e\x00D\xf1" +
|
||||
"\xc6\xfb\x00\x90\x13\xc5\x1e\x80NuT\xd3\x0dfgT" +
|
||||
"3\xadk\x1a\x03>mm\x1aV\xb2\x8a\x96f\xfeF" +
|
||||
"U\xe5\x1b\xb9\x1b\xa4\x981\xc1\x8c[\x94\x10}\xe7\x0f" +
|
||||
"*\x86\xc2\xe7L\xb9\xd6\xc7\xb1\xef>\x00\xb9\x97Gy" +
|
||||
"0\x84\xe3\x0a\xc2\xf1\x1e\x1e\xe5{C8\x0e\x11\x8e\x83" +
|
||||
"<\xcak8\xb4uC\x1dU\xb5;\x19\xf0F\x98\x81" +
|
||||
"\xa6\xa5)9F\x98\x15\xf1\xd8\xa4\xe7-U\xd7L\xac" +
|
||||
"\x0fr\x0b \xd6\x87\x90\x12f\xe2\xe4-\x1e\xa5<F" +
|
||||
"\xe9\xda\xfc$3\x0bB\xd62\xe5\x88oI\xac\x03@" +
|
||||
"\xae\xe6QNp\xd8i0\xb3\x90\xb5\xb0>(\x09>" +
|
||||
"\x8e]=\xf8B4LV\xa2\xe1\x12\x009\xc3\xa3\x9c" +
|
||||
"\xe7\x10\x8b\xe8\xe5zB\xd1\x80G\x97\x85\xebv\x03\xc8" +
|
||||
"\x16\x8f\xf2f\x0em\xd3\xdd\xa4\x1f0\xe3!\xda\x9a1" +
|
||||
"\xad\xfe\xbc\xf7\xd7\xa6\x8ci\x0d\xea\x86\x85\x02p(\x00" +
|
||||
"\xf1V7Y\xf7\x08\xf9T\x7f&\xcb\xeeRy\xcd\xc2" +
|
||||
"(p\x18\x85i\x9d\xca\xe5G\x9c\x02\x9b\xeb\xed\x9e5" +
|
||||
"\x0b\x89\x0c\xbf\xc7\xa3\xfc\xfb!k\x16S\x1c\xbb\x8dG" +
|
||||
"\xf9\x93\x1c\xdaJ:\xad\x174k5\xf0\xcah\x09\xe7" +
|
||||
"S\x0c\xe2i\x83\x05t\xf0\xb6\xad\xae\xe0\xd6\xba6\xa2" +
|
||||
"\x8e\x16\x0c\xc5\x0a\x01^\xc8g\x14\x8bM\xf9\xe4\x9cs" +
|
||||
"\x96\xbf\x8as\xf6\xab\x87k>g/2\x95\x9ct\xdc" +
|
||||
"Prf\x18\x9bd%l\xe8T?\xc1\xa3|{\xe5" +
|
||||
"\x03\xdc\x94c\xa6\xa9\x8c\xb2\xb2\xf0\x10\xad\x88\x89\xc6\xd2" +
|
||||
"du\x92\xb9I\xe6\x16\x83\x99B!k\x91\x16\xb5\xb6" +
|
||||
"\xed\xaaA\xdc\x9a\xcf\xa3|\x1b\x871\xfc\xd0v\xf5X" +
|
||||
"\xf4tpF\xad\xcc0t\x03\xeb\x83$\\\x84$]" +
|
||||
"\xdc\x00u\xad\x97Y\x8a\x9aErK\xbf\xda,\x01n" +
|
||||
"\xa6\xb8\x12\xc0\xe6\x8a\xe7w\x92w\xe4\xa6\x9c\x14\xd1\xbb" +
|
||||
"\x9eGy\x1e\x87\xf6\xa8\xa1\xa4\xd9 3P\xd53+" +
|
||||
"\x15MO\xf1,]F\xd6\xbak\xdd\xd4\xe1\x87e\x82" +
|
||||
"?k\xfa\xf9\x06+\x82P\x9c>\xd8\xea\xea\x9c\xf0u" +
|
||||
"\xde\xd8\x16$c\xff\x98\x1f\x1e\x0e\xb2\x85\x1f\x0f\x1f#" +
|
||||
"gy\x94Gyg(\xaf\xec\xa0\xc8\xf9\x14\x8f\xf2W" +
|
||||
"9\x14#\x91\x04F\x00\xc4\xe7\x88%;y\x94\xf7r" +
|
||||
"SS6\x9b`\x9a\xd5\xab\x8e\x82\xc0\xcc@J*\xf6" +
|
||||
"\xaa\xa3\x0cx\xf3zck\xf5\x0cx\xe8\xc3\xa6\x9ee" +
|
||||
"\x16\xebe\xe9\xacB.7\xc1\xdc\xefE2z\x87:" +
|
||||
"\x1do\x93e\xdeC\xfc\x8d{UR\x88\x0em\x81\xe3" +
|
||||
"\x0a,T\xdcL\xa3\xad\xbb\xb8\x1b\x0c\xca8\x10xL" +
|
||||
"\x91\x07h~,A\xc7\xb1\x19\xa78\x7fO\xe0u\x1e" +
|
||||
")\x16u\x04\x01\xc1\xaf\x09\"\xc0a\x04\xb03\xed," +
|
||||
"X\x16\x0a#3i\xd5\xe9\xaa\xe5\x02GE\x98w\x17" +
|
||||
"E\xef\x02/\x8a\xfb\x80\x13c\x82\xedi\x8e\xde|\xa1" +
|
||||
"\xac\xa0\x8aL\x17eV\xe5-U\xd05\x93\xf6\x0a\xf1" +
|
||||
"\xbf\xa3\x12\xff\x8d\x80\xff^B{lK\x98\xfe\xc5\x84" +
|
||||
"\xb6cw\xc0t1\xc2\xb9\xf4\xdf\xb3\x0f@\xde\xcb\xa3" +
|
||||
"\xfc\x12\x87\x9dn\xad\x85\xf5A\xe3\xa5HY\xb7\xa2\xb8" +
|
||||
"G\x87\xd6\xb4\x92\x0d\x92\x9em\xb0|VI\xb3>," +
|
||||
"VO\x80\x08\x1c\xa2\xe3'\xb9\xbc\xc1L\x13U]\x93" +
|
||||
"\x0bJV\xe5\xadI\xbf\xe2\xd5\x0a\xb9A\x83M\xa8\xa8" +
|
||||
"\x17\xccn\xcbb9!o\x99WS\x0f\x07\x00Q\x90" +
|
||||
"\x14\xd4\xacY\x92#\xdb\x03*\xf8\x00-\x1a\x0f\xf2@" +
|
||||
"\xbcPP\xfd\x04`g\xf5\xb4s\xb2\x10_\xa9\xe4\xca" +
|
||||
"\xf3@\xd5\x8c\x01kJ\xb8\xf3\xd2\xd2oR\xfd6\xfd" +
|
||||
"\x95\x89Lw\xee\x14!\x95)\x0et\xf1(\xdf\x13R" +
|
||||
"\xb9\x7fI\xc8\x0eO\xe5\x15\xc3\x81\x1d\xc2\x1f\xb3IO" +
|
||||
"\xabV\x96\xa3\xf4\xe5\x81Y4\xa6\x1b\x84\xbb\x831\xd3" +
|
||||
"\xe9\x17\x8e*\xab\xf2\xad\x8e\x85\xa4\xe3\xed\x9e\x8e\xd2$" +
|
||||
"\x0e\x00\xa4\xd6#\x8f\xa9\xad\x18\xa8)=\x8c=\x00\xa9" +
|
||||
"\x87H\xfe(\x06\x9aJ\xdb\xb0\x19 \xb5\x99\xe4O\xa0" +
|
||||
"\x7f\xb5\x93\x1e\xc3\x83\x00\xa9'H\xfc,\x0d\x8f\xf0\x8e" +
|
||||
"KH\xbb\x9c\xe5w\x92|/\xc9\xa3\x91\x04F\x01\xa4" +
|
||||
"=\xd8\x0e\x90z\x96\xe4\xaf\x91\xbc\x8aK`\x15\x80t" +
|
||||
"\x08\xc7\x01R\xaf\x90\xfc\x0d\x92\x0b\xd1\x04\xddn\xa5\xd7" +
|
||||
"\xd1\x00H\xfd-\xc9\xbfI\xf2\xea\xc6\x04V\x03HG" +
|
||||
"\x1c\xf9\x9b$\xff>\xc9k\x9a\x12X\x03 \xfd\x13n" +
|
||||
"\x01H}\x97\xe4'H>\x0b\x138\x0b@:\x8e\xbb" +
|
||||
"\x01R'H\xfe\xaf$\x9f]\x95\xc0\xd9\x00\xd2O\x1c" +
|
||||
"}N\x92\xfc\x17$\xaf\x8d$\xb0\x16@\xfa\x19\xee\x03" +
|
||||
"H\xfd\x82\xe4\xffM\xf2\x98\x90\xc0\x18\x80\xf4\xaec\xd7" +
|
||||
"y\x92Ws%7+\x8fQ%\xd7'^7\xfd#" +
|
||||
"cE\x1fG\x97\xee\x83z\x9c\xaeH\x18\x0f\x1a\xaf\x80" +
|
||||
"\x18\x07\xb4\xf3\xba\x9e]9\x95\xa9qK\x195\xbd\xab" +
|
||||
"Z}\xd0\x9a\x02$\xa1_\xfc@\\\xd7\xfa3~ " +
|
||||
"(\x8d:\x9e&\xaa\xd9]\xb0\xf4B\x1eZ)\xc8f" +
|
||||
"\xfc\x98c\x14\xb4\xe5\x86\x9e[\x8d\xcc\xc8\xa9\x9a\x92\x9d" +
|
||||
"!\x1a\xd5\x00\x875P\x0c\x09\xde\xda\xd3\x87\xa6+_" +
|
||||
"<}Fs\xa5\x8cn\xcdw\xacVF\xaf&N-" +
|
||||
"\x09rV\\\x0b\x05\xa4\xd6\x09%[\xf8(\xe1ij" +
|
||||
"=\x95\xect\xeb\xb1\x99\xcau\xaf\xf7T\x12J*T" +
|
||||
"\x17C\xe5\xf99\xc9\xccV\xbf\x09\x132\xf8`\x10\x83" +
|
||||
"={\x97\xb5\x85\xee.Y\xc5b\xa6\xd5\x9d\xc7|V" +
|
||||
"e\x99\xcf0#\x1eN\xd9\x15+\x92\xc8Le\xfa\xd4" +
|
||||
"2\x07C]r2\x9c+\x1a|\xd5x\x8e2\xcb\xfd" +
|
||||
"\xd5\xaf\x8d\xe8T\x87\x08\xe1\xe2\xeb\xdaf'\x99\x19\xbf" +
|
||||
"\x9a\xb3\x08\x9a\x863_\x9d*\x94c\x15\x8a1\xef&" +
|
||||
"\x10\xba&\x13\x19\xd7\xf0(\x8f\x85\xc8\xc8\x06*\\\x93" +
|
||||
"\x93A\x7fL\xe4\xb9b\x83\x8c2W\x9eG\xf9!\x0e" +
|
||||
"\xe3J\xc1\x1a\xc3\xfa\xe0\xf1c\x8a\xd2S{8\xc4\xcd" +
|
||||
"~-\xc3\x00\xd7{\xee\x15\xcag~W~\xe6\x9a\xf9" +
|
||||
"\xea\xcc\xf6\xee\"3\x02\xee\xf7\xaeKv\xbe\xe2U\xbd" +
|
||||
"\xd3\xdd\x94x\xd6\xe8T\x85^\xd7\x1f\xbd\x8e\xb0xh" +
|
||||
"\x03p\xe2\x01\x01\x83^7z\xadmq\x8f\x01\x9c\xb8" +
|
||||
"K@\xce\x7f\x97A\xef\xfdE|\xecq\xe0\xc4m\x02" +
|
||||
"\xf2\xfe\xb3\x0az]\xd2\xc5\x93\xb3\x108q\xa3\x80\x11" +
|
||||
"\xff=\x0b\xbd\x1e\xab\xb8n\x1c8Q\x150\xea\xbf\xd8" +
|
||||
"\xa0\xd7\xe2\x17\xd7n\x01N\x1c\x0az\x81\xd0\xe9\xda\xd1" +
|
||||
"\x85\xb6\xc7QhuX:\xb53\xe8\x8e\x02\xe8B\xdb" +
|
||||
"\xbb\x99\xf0W\xba\x9a8\xa3\xbc\xe6\x16\xc4\xd3\x8a\xc5\xba" +
|
||||
"\xa8Zt\x03\x12\x16#\x12t\xa1\x1c\xc1P\x8b\x19\xe0" +
|
||||
"z[\x03I\xd6\xea\x9c\xf3G\xad\xe1\xbc\xf9\x1f1F" +
|
||||
"\xf2\x95\xb4\xa6}\xfc&ih]*Kky\x94\x1b" +
|
||||
"\xb9\x19+\xd1\xc8\x95\xac\xf0\xc8\x1f\xa7\xc9\xb4\xfeo\xfb" +
|
||||
"\xeb\x1f\xa7\xf0\xfa}\x1e\xe5\x93!\xb7\xfe!\x09\xdf\xe1" +
|
||||
"Q>\x1d\xaa\xe4~D\xbe~\x92G\xf9b\xd0\xf7~" +
|
||||
"\xffq\x00\xf9\"\x8f\xc9Pe$\xfe\x9a\x06\xfe\x8a\xea" +
|
||||
"\x07\xa7.B\xb7.\x8a\xe2\xd3\x00\xa9j\xaa+\x12N" +
|
||||
"]\x14q\xeb\"\x11\x87\x01R\xf5$\x9f\x17\xae\x8b\x9a" +
|
||||
"\xf0>\x80T#\xc9\xe7\xe3\xd4\xdb\xa6P0\x82\xca1" +
|
||||
"\xab\x8f\xde\xa3j\x15\x93\xad\xd7\x88Gk\xb9\xa2f\x0b" +
|
||||
"\x06\x83 \xd7\x17\x83Mo\xa8\xfcp;\xf4n3." +
|
||||
"E$\xcc\xa0\xe97\xea\xae\xe1\x9e?]\xe6\xc9\xea\x85" +
|
||||
"\xccHV1X&\xc5\x0c\xc1\x0d\x08\x83|T\xae\xc6" +
|
||||
"\xd0\xab7@\xf0:\x19\"\xfb\xb4\x99\xac\xcf0t4" +
|
||||
"J\xaa\xf4%A\x95\xee\x17\xe9t\xd9\xb8\x8bGy5" +
|
||||
"\x1dm\x97{\xb4\xf2pp\xafhM+\x05\x93\x95a" +
|
||||
"\x02<3\xfc^\x8f9\xa6\x17\xb2\x99$\x03\xc12&" +
|
||||
"K \x9d\xb1ZO\xb1\xb8\x17\x09\xddG\x0a\xefy\x0e" +
|
||||
"\xbdW\xb8\xd0#\x85\xf7R\x84\xdeSo\xf9#\x85\x87" +
|
||||
"A\xd9#\x85\xfb\xc1\xe1\xfc\xd4;\xf5u4)\xdc\xb4" +
|
||||
"\x18:\x94k\xea\xdd_u\xcb\xdb\xffw\x89\x92\xc8Q" +
|
||||
"s\xbd\xcd /\xc1\xfd\x7f\x00\x00\x00\xff\xff\xf1\xc3d" +
|
||||
"\xc6"
|
||||
const schema_db8274f9144abc7e = "x\xda\xccZ}t\x1c\xd5u\xbfwfW#\x19\xad" +
|
||||
"G\xc3,\x96\xb5\xc7B\x8d\x8e]j'\x06d\xd7)" +
|
||||
"u\xd3H2\xb2\x83\x84?4\xbbV\x0e1&\x87\xd1" +
|
||||
"\xee\x934\xea\xee\xcc23+l\x07\xe2\x0fl\x0c\x1c" +
|
||||
"B0\xb1\xf9pBc\x9b\xd2\x9e\x98$\x85`\x9a\xba" +
|
||||
"\x07Z\x9c\x86\x10Cpp\x0e\xa4&\x86\xa6\x89\xe3\xb6" +
|
||||
"\xf8\x98R\x0cn\x8e\xdb\x98\xe9\xb93;\x1f\xda]$" +
|
||||
"+\xce\x1f\xf9ou\xe7\xbe\xf7\xee\xfb\xbd\xdf\xfdx\xf7" +
|
||||
"\xe9\xea;\x1a\xba\xb8\x8e\xf8F\x11@9\x10\xafs\xd8" +
|
||||
"\xbc\x9fl\xd83\xe7\x9f\xb7\x80\x92Bt\xbe\xf8l_" +
|
||||
"\xf2\x9c\xbd\xe58\xc4y\x01`\xe1\xe3\xc2\x06\x94\x0f\x0a" +
|
||||
"\x02\x80\xfc\x8c\xf0\x1f\x80\xce\x9d3\x9e\xfc\xfa\xe3Kw" +
|
||||
"\xde\x01R\x8a\x0f\x95\x01\x17>R\xdf\x87\xf2\xb7\xeaI" +
|
||||
"\xf3\x1b\xf5\xdb\xe5\x86\x06\x01\xc0\xb9^\xba\xea\xc6\xe4\xab" +
|
||||
"GH;:u\x8c\xa6~\xaf~\x1e\xcaHj\xf2\xf9" +
|
||||
"z\x9a\xfaS\x85\x1f\xef\xfb\xe4\xae\x97\xb7\x82\x94\xe2\xc6" +
|
||||
"M\xfdv\xc3\x06\x94\xcf\xbb\x9a\xe7\x1aV\x01:\xef\xef" +
|
||||
"\x9c\xf9\xc4\xde#?\xdc\x06\xd2\x15\x08eK\xa5i?" +
|
||||
"C@y\xce\xb4\xbf\x05t^9{\xe3\x07\x07~\xb0" +
|
||||
"\xe8N\x90\xe6\x92\x02\x92\xc2\x0b\xd3\xda9@\xf9\xadi" +
|
||||
"\x9d\x80\xce\xa9\xd3\xff\xb7\xfd\x0bsW\xde\x0f\xca\\\xe4" +
|
||||
"\x00\xe2\x1ci\x9c\x9f\x96\"\x8d\xcb.!k:\x97\xbd" +
|
||||
"r0\xb5\xf0\xc1\x9d\x15\xa6\xbb\x8ag/\x99\x87r\xbc" +
|
||||
"\x91\x0c\xc2\xc6[\x01\x9dO\xff\xc9\xd3\xf7\xf5<\xb8i" +
|
||||
"\x17HW\x05\xeb\xb1\xc654\xdb\xe6FZ\xef\x7f\xa6" +
|
||||
"\x7f\xf5H\xe9\xda\xef>X6\xc8\x9deo\xe3<R" +
|
||||
"8\xe8\xce\xd0>6\xe7\xe6\xef\xbd\xf0\xf4C\xa0\xccG" +
|
||||
"t\xde\x1c\xfc\xf8\xeb\xfc\xa3\xfb\x8f\xc3\x00\x0ad\xdf\xc2" +
|
||||
"\x96\xc4>\xda\xdd\xdc\x04\xe9\xfe\xf8\x13\xcf\xfe\xc3\xfdO" +
|
||||
"o\xff*(W \x02\xb8h~)\xf1\xbf\xa4\xb07" +
|
||||
"A\xab\xed<\xf6\xdc\xca\xc2\x8e\xdd\xfb<|\xdc\xef\x87" +
|
||||
"\x13\x1c\x071gk\xef\xaf\x0b\x03\x8fe\x1e+#\x17" +
|
||||
"\xa7O\xcf%\xce \xe0\xc2\xa3\x896\x04t\x16\xfd\xec" +
|
||||
"\xe4\xaa\x15\xdf\x19\xfa\x9b\xc8\xd8w\xa6o\xa0\xb1\xdb\x87" +
|
||||
"\xce\x1cjJ\x17\x9e\xa8@\xc4\xdd\xec\xdb\xd3\xf7\xa3\x8c" +
|
||||
"\xa2{\x98\xd3\xc9\x84o]~}\xc3\xba\x93\xcb\x9e\x04" +
|
||||
"i\xbe?M\xab\x98\xa6i\xc6^z\xec\x0f\xe7\xbct" +
|
||||
"\xebS\xa0\\\x85\x01X-\xf4\x0d\xe5E\"\xed/v" +
|
||||
"|\xce\xfe\xe7~~\xdf\x81*\x8e\xed\x107\xa0\xfc\xb8" +
|
||||
"\xbb\xca^\xf13\xf2Q\xfa\xe5\xc4n\xe2?T\x1f\xfe" +
|
||||
"\xa7\x03\x95\xfcu1>(\x0e\xa2\xfc\x8a\xe8\" \xba" +
|
||||
"\xfb\xbb\xe7\xd0\xee\x8f\xd7\x7f\xfd\xfdgj\xaa\xbf\xd74" +
|
||||
"\x88r\\r\x0fV\"\"]\xd6\x8bo>\xdf\x11\xfb" +
|
||||
"n\x94i\x8fK\xa7\x08\xea\x83\xaeB\xeb;K\x12\xfa" +
|
||||
"\xbb[\x9e\xaf\x00\xc5U\xfc\xdc\xa5}(\x17.\xa5\xd9" +
|
||||
"\xb4KI9\xf6\xc9\xd1\xed\xd2\x89\x9f\xbe\xe0\x81\xe2\xed" +
|
||||
"<.\x8f\xd2\xce?&\x13j}7~\xe5\x81\xf8\xc9" +
|
||||
"\xaf\xbcH\xc6E| Nn\xb5\xb0[6Q\x1e\x90" +
|
||||
"\xe9\xa7\"7\xf3\x80N\xea\xc9?\xfb\xf6\x92\xdc\x1b/" +
|
||||
"\xd7\xe0\xa8|t\xc6\x19\xf9\xad\x19\xf4\xeb\x8d\x19\x04\xea" +
|
||||
"\x89\xf9O}\xe1\xed/\x1d}\xad\xbc\x13w\xedE\xcd" +
|
||||
".iz\x9bi\xedsk\xf7\\\xaf97\x1c\xaf\x04" +
|
||||
"\xc6\xd5\xd4\x9a\xbf\x83\xf2\xe6f\x9a\xee\xf6f\x9a.`" +
|
||||
"h-\xed_4\x8f\xa2|\xd6\xd5~\xcf\x9d\x9b;\xa9" +
|
||||
"\xb6l\xfa\xe9\xa7\xdf\x8c\x90\xeal\xf3/\x11b\xce\xca" +
|
||||
"\xcf\xde8\xdap\xfb\x89\x13Q\xb3\xdenv\x01>\xef" +
|
||||
"\x0e\xfd\xc7\x7fyh\xe4\xa6o\x1f9\x19%\xd2L\x93" +
|
||||
"\x88\xf4_\x7f}\xea\xcb\xa7\x0b\xb9\x7fw]\xc6?\x9c" +
|
||||
"\x96\x99\x8b\x09\xce\x8e\x99\xe4\xc3\xcdm\x89\xa5\xed\xc7\xfa" +
|
||||
"OE\xf1N\xb4,!\x859-4\xf9\xa2\x9b\xbb\xd9" +
|
||||
"\xdakn8U\xc5\xb4\xa5-\x8bQ\x1ehq\xb1n" +
|
||||
"\xd9\x8e\xb2\x96j\x06p\xc6\xfen\xc7\x0dO|\x7f\xe5" +
|
||||
"\x19\xcf\x8b][>\x97Z@\xb6\xdc\xf7\xc5\x9eU\x7f" +
|
||||
"\xda~\xe8Lt\x1bJ\x8a\xfcJf)Zi\xe8\x9a" +
|
||||
"\xd3\x9f\x99s\xdf\x0f\xce\xd4r\x9em\xa9y(\xefJ" +
|
||||
"\x11\\;H\xf9\xdde\x7f\xf9ZJL}P\x01m" +
|
||||
"\x1d\xe9>\x93\x1aE\xf9p\xca\x0dh\xa9\x17\x89\xd0{" +
|
||||
"\xffj\xdf\xbf\x9e;r\xdd\xd9\xaa=\x1c\x9cE\xdc\x9f" +
|
||||
"E\xd3\x1e\x9e%\xc8\x87g]\x01\xe0\xdcy\xfc\xf3\xeb" +
|
||||
"~r\xc7\xfbg+\x19\xe6\x1arhV\x1a\xe5\xd7\xdd" +
|
||||
"\x11Gg\x11a\x1fZ\xfd\x9f\x1bO\xef\x9a\xf1\xeb\xaa" +
|
||||
"\xb9oi\x1dEy[+inn}Q\xbe\xecr" +
|
||||
"\xf2\xc4W\x85\xc7:z6\xbe|.rT\xe7[\xfb" +
|
||||
"\x08\x9e\x07\x85\xaf\x9d\xd8\xf4\xf3\xcf\xff&\x0a\xcf\xb9\xd6" +
|
||||
"_\x12<\x89\xcb\x09\x9e\xdb\xde}\xe4\xba/\xaf\xfd\xe6" +
|
||||
"\x87\x11\x82\xcc\xbf|\x0b\x0d\xb5K\xba\xce\xf2f1\x96" +
|
||||
"\xbd\xca\xff\x99\xbd2\xab\x16\xf5\xe2\xe2\xee\x92=\xc2t" +
|
||||
"[\xcb\xaa6K\xb3N\xabh\xe8\x16\xebGT\x9a\xf8" +
|
||||
"\x18@\x0c\x01$u\x14@\xb9\x99G%\xcf\xa1\x84\x98" +
|
||||
"$\xa2H\x1a\x09GxTl\x0e%\x8eKR\x84\x95" +
|
||||
"ni\x07P\xf2<*\xeb8D>\x89<\x80Tz" +
|
||||
"\x00@Y\xc7\xa3\xb2\x95C\xa7\xc8\xcc\x82\xaa3\x1dD" +
|
||||
"{\xa9ib#p\xd8\x08\xe8\x98\xcc6\xd7\xab\x83y" +
|
||||
"\x10YD,\x8c\xdejc\x028L\x00:#F\xc9" +
|
||||
"\xb4\x06t\x1b\xb5|\x9a\x0d\x99\xcc\xc2\x11\xac\x03\x0e\xeb" +
|
||||
"\x00'\xda^\x86Y\x96f\xe8+T]\x1df&\x00" +
|
||||
"\xed\xac\x9e\x8f\x03\x04\xd9\x0b\xfd<'u\xec\x06N\x9a" +
|
||||
"/`\x98i\xd0'\xab\xf4\xb1\xfd\xc0I\xad\x82c\xb2" +
|
||||
"a\xcd\xb2\x99\x89\x03\xb9\xa2;7o\xe8]\xe8\x94t" +
|
||||
"\xef\x032\xd3\xfb \xd2\xaa]\xd8\x8f\xa1u|\xb5u" +
|
||||
"\xd7\xe65\xa6\xdbb\xaf>dT@\xdeW\x0b\xf2\xbe" +
|
||||
"2\xe4[#\x90o^\x02\xa0\xdc\xc6\xa3r\x17\x87\x12" +
|
||||
"_\xc6|\xdb<\x00e\x13\x8f\xca\xbd\x1c:Yw\x91" +
|
||||
"\xde\x1c\x00\x04h\x0e1\xd5.\x99\xcc\"\xd9t\xc0~" +
|
||||
"\x1e]\xd0\xa7\x03n\x1cc&\xd9\xee\x1f\x82\xa8\x9a\xd9" +
|
||||
"\x91\xe0\xa0&@z\xe9:\xcd\xb25}x\xb5+\xef" +
|
||||
"\xec7\xf2Zv=\xed\xaa\xd1\xb5\xb3u1\x00\xa2t" +
|
||||
"\xd9\x1a\x00\xe4$i\x09@\xa76\xac\x1b&sr\x9a" +
|
||||
"\x955t\x9d\x01\x9f\xb57\x0e\xaayU\xcf\xb2`\xa1" +
|
||||
"\xba\xea\x85\xbc\x052\xcc\x1cc\xe6\x95j\x84\xbe\xb3\xfb" +
|
||||
"US\xe5\x0b\x96\xd2\x18\xe0\xb8t\x0d\x80\xd2\xc3\xa3\xd2" +
|
||||
"\x1f\xc1q\x05\xe1\xb8\x9cG\xe5\x86\x08\x8e\x03\x84c?" +
|
||||
"\x8f\xcaZ\x0e\x1d\xc3\xd4\x865\xfdZ\x06\xbc\x19e\xa0" +
|
||||
"e\xebj\x81\x11fe<6\x1aE[3t\x0b\x9b" +
|
||||
"\xc2\xac\x03\x88M\x11\xa4\x84\xc98y\xa5O)\x9fQ" +
|
||||
"\x86>;\xcd\xac\x92\x90\xb7-%\x16\xec$\xb1\x18@" +
|
||||
"\xa9\xe7QIr\xd8i2\xab\x94\xb7\xb1),'~" +
|
||||
"\x17\xab\xfa\xf0Eh\x98\xaeE\xc3\x05\x00J\x8eG\xa5" +
|
||||
"\xc8!\x96\xd1+,\x89D\x03\x1e=\x16\xde\xb2\x1b@" +
|
||||
"\xb1yT6q\xe8X\xde\"\xbd\x809\x1f\xd1\xb6\x9c" +
|
||||
"e\xf7\x16\xfd\xbf6\xe6,\xbb\xdf0m\x14\x80C\x01" +
|
||||
"\x88\xb7\x86\xc5\xba\x87\xc8\xa7zsyv\x9d\xc6\xeb6" +
|
||||
"\xc6\x81\xc38L\xe8T\x1e?D\x0al\x9e\xb7\xfb\xbb" +
|
||||
"\x99Kd\xf8#\x1e\x95?\x8e\xec\xa6\x83\xe2\xd8\xd5<" +
|
||||
"*\x9f\xe2\xd0Q\xb3Y\xa3\xa4\xdb\xab\x81W\x87+8" +
|
||||
"\x9fa fM\x16\xd2\xc1_\xb6\xbe\x86[\x1b\xfa\x90" +
|
||||
"6\\2U;\x02x\xa9\x98Sm6\xee\x93{\xce" +
|
||||
"y\xfe\x02\xce9\xa8+\xa6|\xce~d\xaa8i\xd1" +
|
||||
"T\x0bV\x14\x9bt-l\xe8T?\xc1\xa3rM\xed" +
|
||||
"\x03\xdcX`\x96\xa5\x0e\xb3\xaa\xf0\x10\xaf\x89\x89\xce\xb2" +
|
||||
"\xb4\xeb4\xf3\x92\xcc\x95&\xb3\x84R\xde&+\x1a\x1d" +
|
||||
"\xc73\x83\xb85\x9bG\xe5j\x0e\x13\xf8\xa1\xe3\xd91" +
|
||||
"\xff\x81\xf0\x8c\xda\x98i\x1a&6\x85I\xb8\x0cI\xb6" +
|
||||
"\xbc\x00\x1az\x0f\xb3U-\x8f\xe4\x96A\xa5Z\x01\xdc" +
|
||||
"dq%\x84\xcd\x13\xcf\xee$\xef(\x8c;)\xa2w" +
|
||||
"\x13\x8f\xca,\x0e\x9daS\xcd\xb2~f\xa2f\xe4V" +
|
||||
"\xaa\xba\x91\xe1Y\xb6\x8a\xac\xd3\xa7\xba\xa8\xcb\x0f\xdb\x82" +
|
||||
"`\xd4\xc4\xe3MV\x06\xa1<\xbc\xbf\xcd\xb39\x19\xd8" +
|
||||
"|{{\x98\x8c\x83c\xde<\x18f\x8b \x1e\xdeM" +
|
||||
"\xcer\x17\x8f\xca\xceH^\xd9A\x91\xf3~\x1e\x95\xaf" +
|
||||
"q(\xc5bI\x8c\x01H\x8f\x10Kv\xf2\xa8\xec\xe1" +
|
||||
"\xc6\xa7l6\xc6t\xbbG\x1b\x06\x81Y\xa1\x94L\xec" +
|
||||
"\xd1\x86\x19\xf0\xd6\xc5\xc6\xd6\xfaI\xf00\x06-#\xcf" +
|
||||
"l\xd6\xc3\xb2y\x95\\n\x8cy\xdf\xcbd\xf4\x0fu" +
|
||||
"\"\xde\xa6\xab\xbc\x87\xf8+\xfaUR\x84\x0e\xed\xa1\xe3" +
|
||||
"\x0a,R\xdcL`\xad7\xb9\x17\x0c\xaa8\x10zL" +
|
||||
"\x99\x07h\xfdN\x82\x8e\xbbg\x1c\xe7\xfcKB\xaf\xf3" +
|
||||
"I1\x7fq\x18\x10\x82\x9a \x06\x1c\xc6\x00;\xb3\xee" +
|
||||
"\x84U\xa106\x99U\x9d\x9eY\x1epT\x84\xf9\xf7" +
|
||||
"X\xf4/\xff\x92\xb4\x0f8)!8\xbe\xe5\xe8\x8f\x17" +
|
||||
"\xaa\x0a\xaa\xd8DQfU\xd1\xd6\x04C\xb7h\xad\x08" +
|
||||
"\xff\x17\xd7\xe2\xbf\x19\xf2\xdfOhwo\x89\xd2\xbf\x9c" +
|
||||
"\xd0v\xec\x0e\x99.\xc58\x8f\xfe\x8f\xee\x03P\xf6\xf0" +
|
||||
"\xa8|\x93\xc3N\xaf\xd6\xc2\xa6\xb0iS\xa6\xacWQ" +
|
||||
",7\xa0-\xab\xe6\xc3\xa4\xe7\x98\xac\x98W\xb3l)" +
|
||||
"\x96\xab'@\x04\x0e\xd1\xf5\x93B\xd1d\x96\x85\x9a\xa1" +
|
||||
"+%5\xaf\xf1\xf6\xfa\xa0\xe2\xd5K\x85~\x93\x8di" +
|
||||
"h\x94\xacn\xdbf\x05\xa1h[\x17R\x0f\x87\x00Q" +
|
||||
"\x90\x14\xb4\xbc\x0bP\xa4`\xa2\xca\xb1\x8bGey\x04" +
|
||||
"\xa0^\xca\x91\xd7\xf1\xa8\xac\x0e\x01R\xbe\x07\xa0\xac\xe6" +
|
||||
"Q\xb9\x99C\xb1T\xd2\x82\xa4\xe0\xe4\x8d\xac{\xda " +
|
||||
"\xaeT\x0b\x95\xb9\xa1\xd7\xe2\xd2\xac`\xd8,\xbf\xde\xe3" +
|
||||
"h.\xdc\xf1\x85\xc6\xe6\x8a \xe9'\xb3\xdf\xa7\xaao" +
|
||||
"\xe2\x8b\x16\x81\x03\x15\xb8\xb7\xd7\xc2}Ad\x1f\xbe\xc9" +
|
||||
"+\x06\xc3}\x08\x7f\xc1\xd6\xfbV\xb5\xb1\x02%=\x1f" +
|
||||
"\xee\xf2f\xbaA\xb8>\xd4\x99j,r}p\xb9\x91" +
|
||||
"U\xf3\x95!D\xacL\x86\xd1\xb2\xe5\xc2\xc3Ct\xd1" +
|
||||
"U\xc56\x17V\x02\xe6\x1a\x7fby=\xf6\x01d\xd6" +
|
||||
"!\x8f\x99\xad\x18b#o\xc6%\x00\x99\xdbH~\x17" +
|
||||
"\x86\xf0\xc8\xdb0\x05\x90\xd9D\xf2{1\xb8\x85\xcaw" +
|
||||
"\xe3~\x80\xcc\xbd$~\x98\xd4c\xbc\xeb\xbd\xf2.w" +
|
||||
"\xfa\x9d$\xdfC\xf2x,\x89q\x00\xf9Q\x9c\x07\x90" +
|
||||
"y\x98\xe4\x07H^\xc7%\xb1\x0e@~\x0aG\x012" +
|
||||
"O\x92\xfcY\x92\x0b\xf1$]\xc4\xe5\x83h\x02d\xfe" +
|
||||
"\x9e\xe4\xdf'y\xfd\xcc$\xd6\x03\xc8\x87\\\xf9\xf3$" +
|
||||
"\xff\x11\xc9\x1bZ\x92\xd8\x00 \x1f\xc6-\x00\x99\x1f\x92" +
|
||||
"\xfc5\x92O\xc3$N\x03\x90\x8f\xe2n\x80\xcck$" +
|
||||
"\xff7\x92_R\x97\xc4K\x00\xe4\xb7\\{\x8e\x91\xfc" +
|
||||
"W$o\x8c%\xb1\x11@\xfe\x05\xee\x03\xc8\xfc\x8a\xe4" +
|
||||
"\xffM\xf2\x84\x90\xc4\x04\x80\xfc\x8e\xbb\xaf\xd3$\xaf\xe7" +
|
||||
"*.\x81>\x8d+nz\xbca\x05<a\xe5p\x84" +
|
||||
"\x9e\x8f\xf5\x1b\"\xdd\xe6P\x0c\xfb\xcb\x80(\x02:E" +
|
||||
"\xc3\xc8\xaf\x1c\xef\x1e\xa2\xad\x0e[\xfe\xad\xb2)\xec\xaf" +
|
||||
"\x01\x920\xa8\xd3@4\xf4\xde\\\x10\xb3*\x03\xa4o" +
|
||||
"\x89fu\x97l\xa3T\x846\xe2b.\x08\x16fI" +
|
||||
"_f\x1a\x85\xd5\xc8\xcc\x82\xa6\xab\xf9I\x02g\x03p" +
|
||||
"\xd8\x00\xe5H\xe5\xcf=q\x14\xfd\xe8;r\xc0h\xae" +
|
||||
"\x92\xd1m\xc5\xc5\xab\xd5\xe1\x8ak\xc7\xbc0\xbb\x06\xae" +
|
||||
"=\x7fA\x98^E=\x12'\xdb\xc6\xd4|\xa9\xba\xa2" +
|
||||
"\xae\x9bb\xe9\x97\xee\xf4J\xc7\xc9n\x16~\x9b\xac\"" +
|
||||
"~\xd5(\x84\x06\xaaK\x894\xb3\xda\x82~Qd\xc3" +
|
||||
"\xfb\xc3k\x83\xbf\xdfE\xed\x91kV^\xb5\x99ew" +
|
||||
"\x17\xb1\x98\xd7X\xee\xb3\xcc\x14\xa3\xd5E\xcd\xe2)6" +
|
||||
"\xd9\x8db|E\x86\x91\xc7\x00\xda8W\xde\xf0\x05\xe3" +
|
||||
"9\xccl\xefW\xaf>dP\xc9$D\xeb\xc4\xa9\x8d" +
|
||||
"N3K\xbc\x90\xb3\x08\xfb\x9b\x93\xd7\xb9S\x89\xd6i" +
|
||||
"\xd6\xe6ra\xa2[c\x8d\xf9j\xd4\xa1\xfe%(\xd2" +
|
||||
"! r\xaf\xe5Q\x19\x89\x90\x9b\xf5\xd5\xe8\x10\xa4\xc3" +
|
||||
"\xd6\xa0\xc4s\xe5\xde \xa5\xdf\"\x8f\xcam\x1c\x8aj" +
|
||||
"\xc9\x1e\xc1\xa6\xf0\xcdh\x1c\x08\xe3\xdbW\xc4\xf5^=" +
|
||||
"\xc7\x00\xd7\xf9\xee\x1aI\xca\xc1c\xc6o\x05\xe3G\x96" +
|
||||
"\xdf\x16Lz\x80AC\xbfb\xe5\x8f\xecRtz\x8b" +
|
||||
"\x12og\xba\x05\xb1\xffV\x82~3\\zj\x03p" +
|
||||
"\xd27\x04\x0c\x1f\x00\xd0\xef\xf7K\x8f\x9a\xc0I\xbb\x04" +
|
||||
"\xe4\x82\xe7,\xf4\x9f\xad\xa4\xbb\xef\x01N\xda& \x1f" +
|
||||
"\xbcF\xa1\xdf \xeeX?\x0d\x81\x93n\x170\x16<" +
|
||||
"\x03\xa2\xdf^\x96n\x19\x05N\xd2\x04\x8c\x07\x0f]\xe8" +
|
||||
"\xbf{H7m\x01N\x1a\x08\xdb\xa0\xd0\xe9\xed\xa3\x0b" +
|
||||
"\x1d\x9f\xf3\xd0\xe6\xb2~|S\xd4\xd3\x02\xe8B\xc7\xbf" +
|
||||
"\x94\xf1\x1fu+s\xb5\xfc\xbe\x1e\x88Y\xd5f]T" +
|
||||
"({\x01\x0e\xcb\x11\x0e\xbaP\x89a\xa4\xbb\x0ep\xb1" +
|
||||
"]\x91*?\x99b!\xea\x8f\xff-c._\xcbj" +
|
||||
"Z'\xe8\x0fG\xe6\xa5\x8a\xbc\x91Ge&7I\xc1" +
|
||||
"]3tz\x06\xfb\xe4\x17i0\xcd\xff\x07\xc1\xfcG" +
|
||||
")\\\xff\x88G\xe5X\xc4\xad_'\xe1\xab<*o" +
|
||||
"F\xca\xd17\xc8\xd7\x8f\xf1\xa8|\x10\xb6\xfc\xdf\xbb\x07" +
|
||||
"@\xf9\x80\xc7t\xa4\xd2\x92\xce\x93\xe2o\xa8\x1eq\xeb" +
|
||||
",\xf4\xea\xac8>\x00\x90\xa9\xa7:%\xe9\xd6Y1" +
|
||||
"\xaf\xce\x92p\x10 \xd3D\xf2Y\xd1:\xab\x05\xd7\x00" +
|
||||
"df\x92|6\x8e\xbfh\x0b%3,\x7f\xf3\xc6\xf0" +
|
||||
"rM\xaf\x99\xbc\xfd7\x08\xb4\x97\xa9Z\xbed2\xa8" +
|
||||
"\xbc\x82\xf4\xf6D\xca\x19\xefq\xc2\xebCf\x88\x849" +
|
||||
"\xb4\x82\x1e\xe5\x14Z\x1c\x13e\xb2\xbcQ\xca\x0d\xe5U" +
|
||||
"\x93\xe52\xcc\x14\xbc\x80\xd0\xcf\xc7\x95z\x8c\xfc\xb3\x00" +
|
||||
"@\xf8\xa8\x1b!\xfb\x84\x99q\xa9i\x1ahV\\5" +
|
||||
"\x16\x84W\x8d\xe0\xa6\xb1&\xbc\xe1I\\W\xf9\x8a7" +
|
||||
"\x18^\x8e\xda\xb2j\xc9bU\x98\x00\xcf\xcc\xa0\xcde" +
|
||||
"\x8d\x18\xa5|.\xcd@\xb0\xcd\xf5U\xb7\xba\xd8d\xd1" +
|
||||
"W\xf4#a\xa3\x1b\x09\xfd\x97I\xf4\x1f %e7" +
|
||||
"p\xd2\x0a\x8a\x84\xfe#\x19\xfa/\xe4R\xf7~\xe0\xa4" +
|
||||
"?\xa7H\xe8\xbf\x0f\xa3\xff\xe8)u\xbc\x04\x9c\xd4\x11" +
|
||||
"y\xbb\xf1\xf1\xa9z\xbb\xf1>\xb8\xfe@\x1f\xca\x09\x95" +
|
||||
"\xab\xcc\xa8\x14\xa1\xa2\x8d\x88\x8b\xe8\xecx\x095r\x9c" +
|
||||
"Sz\xf0\xb8\xe0w\x82\xe0\xffS*bN\xc3\xc5v" +
|
||||
"\xd0\xfc\xd4\xf8\xff\x01\x00\x00\xff\xff|\xba\xdf\xe8"
|
||||
|
||||
func init() {
|
||||
schemas.Register(schema_db8274f9144abc7e,
|
||||
@@ -4561,6 +4784,7 @@ func init() {
|
||||
0xb5f39f082b9ac18a,
|
||||
0xb70431c0dc014915,
|
||||
0xc082ef6e0d42ed1d,
|
||||
0xc5d6e311876a3604,
|
||||
0xc793e50592935b4a,
|
||||
0xcbd96442ae3bb01a,
|
||||
0xd4d18de97bb12de3,
|
||||
@@ -4568,6 +4792,7 @@ func init() {
|
||||
0xdbaa9d03d52b62dc,
|
||||
0xdc3ed6801961e502,
|
||||
0xe3e37d096a5b564e,
|
||||
0xe5ceae5d6897d7be,
|
||||
0xe6646dec8feaa6ee,
|
||||
0xea50d822450d1f17,
|
||||
0xea58385c65416035,
|
||||
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- "1.x"
|
||||
- master
|
||||
env:
|
||||
- TAGS=""
|
||||
- TAGS="-tags purego"
|
||||
script: go test $TAGS -v ./...
|
||||
+4
-2
@@ -1,7 +1,7 @@
|
||||
# xxhash
|
||||
|
||||
[](https://godoc.org/github.com/cespare/xxhash)
|
||||
[](https://travis-ci.org/cespare/xxhash)
|
||||
[](https://pkg.go.dev/github.com/cespare/xxhash/v2)
|
||||
[](https://github.com/cespare/xxhash/actions/workflows/test.yml)
|
||||
|
||||
xxhash is a Go implementation of the 64-bit
|
||||
[xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a
|
||||
@@ -64,4 +64,6 @@ $ go test -benchtime 10s -bench '/xxhash,direct,bytes'
|
||||
|
||||
- [InfluxDB](https://github.com/influxdata/influxdb)
|
||||
- [Prometheus](https://github.com/prometheus/prometheus)
|
||||
- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
|
||||
- [FreeCache](https://github.com/coocood/freecache)
|
||||
- [FastCache](https://github.com/VictoriaMetrics/fastcache)
|
||||
|
||||
-1
@@ -193,7 +193,6 @@ func (d *Digest) UnmarshalBinary(b []byte) error {
|
||||
b, d.v4 = consumeUint64(b)
|
||||
b, d.total = consumeUint64(b)
|
||||
copy(d.mem[:], b)
|
||||
b = b[len(d.mem):]
|
||||
d.n = int(d.total % uint64(len(d.mem)))
|
||||
return nil
|
||||
}
|
||||
|
||||
+31
-31
@@ -6,7 +6,7 @@
|
||||
|
||||
// Register allocation:
|
||||
// AX h
|
||||
// CX pointer to advance through b
|
||||
// SI pointer to advance through b
|
||||
// DX n
|
||||
// BX loop end
|
||||
// R8 v1, k1
|
||||
@@ -16,39 +16,39 @@
|
||||
// R12 tmp
|
||||
// R13 prime1v
|
||||
// R14 prime2v
|
||||
// R15 prime4v
|
||||
// DI prime4v
|
||||
|
||||
// round reads from and advances the buffer pointer in CX.
|
||||
// round reads from and advances the buffer pointer in SI.
|
||||
// It assumes that R13 has prime1v and R14 has prime2v.
|
||||
#define round(r) \
|
||||
MOVQ (CX), R12 \
|
||||
ADDQ $8, CX \
|
||||
MOVQ (SI), R12 \
|
||||
ADDQ $8, SI \
|
||||
IMULQ R14, R12 \
|
||||
ADDQ R12, r \
|
||||
ROLQ $31, r \
|
||||
IMULQ R13, r
|
||||
|
||||
// mergeRound applies a merge round on the two registers acc and val.
|
||||
// It assumes that R13 has prime1v, R14 has prime2v, and R15 has prime4v.
|
||||
// It assumes that R13 has prime1v, R14 has prime2v, and DI has prime4v.
|
||||
#define mergeRound(acc, val) \
|
||||
IMULQ R14, val \
|
||||
ROLQ $31, val \
|
||||
IMULQ R13, val \
|
||||
XORQ val, acc \
|
||||
IMULQ R13, acc \
|
||||
ADDQ R15, acc
|
||||
ADDQ DI, acc
|
||||
|
||||
// func Sum64(b []byte) uint64
|
||||
TEXT ·Sum64(SB), NOSPLIT, $0-32
|
||||
// Load fixed primes.
|
||||
MOVQ ·prime1v(SB), R13
|
||||
MOVQ ·prime2v(SB), R14
|
||||
MOVQ ·prime4v(SB), R15
|
||||
MOVQ ·prime4v(SB), DI
|
||||
|
||||
// Load slice.
|
||||
MOVQ b_base+0(FP), CX
|
||||
MOVQ b_base+0(FP), SI
|
||||
MOVQ b_len+8(FP), DX
|
||||
LEAQ (CX)(DX*1), BX
|
||||
LEAQ (SI)(DX*1), BX
|
||||
|
||||
// The first loop limit will be len(b)-32.
|
||||
SUBQ $32, BX
|
||||
@@ -65,14 +65,14 @@ TEXT ·Sum64(SB), NOSPLIT, $0-32
|
||||
XORQ R11, R11
|
||||
SUBQ R13, R11
|
||||
|
||||
// Loop until CX > BX.
|
||||
// Loop until SI > BX.
|
||||
blockLoop:
|
||||
round(R8)
|
||||
round(R9)
|
||||
round(R10)
|
||||
round(R11)
|
||||
|
||||
CMPQ CX, BX
|
||||
CMPQ SI, BX
|
||||
JLE blockLoop
|
||||
|
||||
MOVQ R8, AX
|
||||
@@ -100,16 +100,16 @@ noBlocks:
|
||||
afterBlocks:
|
||||
ADDQ DX, AX
|
||||
|
||||
// Right now BX has len(b)-32, and we want to loop until CX > len(b)-8.
|
||||
// Right now BX has len(b)-32, and we want to loop until SI > len(b)-8.
|
||||
ADDQ $24, BX
|
||||
|
||||
CMPQ CX, BX
|
||||
CMPQ SI, BX
|
||||
JG fourByte
|
||||
|
||||
wordLoop:
|
||||
// Calculate k1.
|
||||
MOVQ (CX), R8
|
||||
ADDQ $8, CX
|
||||
MOVQ (SI), R8
|
||||
ADDQ $8, SI
|
||||
IMULQ R14, R8
|
||||
ROLQ $31, R8
|
||||
IMULQ R13, R8
|
||||
@@ -117,18 +117,18 @@ wordLoop:
|
||||
XORQ R8, AX
|
||||
ROLQ $27, AX
|
||||
IMULQ R13, AX
|
||||
ADDQ R15, AX
|
||||
ADDQ DI, AX
|
||||
|
||||
CMPQ CX, BX
|
||||
CMPQ SI, BX
|
||||
JLE wordLoop
|
||||
|
||||
fourByte:
|
||||
ADDQ $4, BX
|
||||
CMPQ CX, BX
|
||||
CMPQ SI, BX
|
||||
JG singles
|
||||
|
||||
MOVL (CX), R8
|
||||
ADDQ $4, CX
|
||||
MOVL (SI), R8
|
||||
ADDQ $4, SI
|
||||
IMULQ R13, R8
|
||||
XORQ R8, AX
|
||||
|
||||
@@ -138,19 +138,19 @@ fourByte:
|
||||
|
||||
singles:
|
||||
ADDQ $4, BX
|
||||
CMPQ CX, BX
|
||||
CMPQ SI, BX
|
||||
JGE finalize
|
||||
|
||||
singlesLoop:
|
||||
MOVBQZX (CX), R12
|
||||
ADDQ $1, CX
|
||||
MOVBQZX (SI), R12
|
||||
ADDQ $1, SI
|
||||
IMULQ ·prime5v(SB), R12
|
||||
XORQ R12, AX
|
||||
|
||||
ROLQ $11, AX
|
||||
IMULQ R13, AX
|
||||
|
||||
CMPQ CX, BX
|
||||
CMPQ SI, BX
|
||||
JL singlesLoop
|
||||
|
||||
finalize:
|
||||
@@ -179,9 +179,9 @@ TEXT ·writeBlocks(SB), NOSPLIT, $0-40
|
||||
MOVQ ·prime2v(SB), R14
|
||||
|
||||
// Load slice.
|
||||
MOVQ b_base+8(FP), CX
|
||||
MOVQ b_base+8(FP), SI
|
||||
MOVQ b_len+16(FP), DX
|
||||
LEAQ (CX)(DX*1), BX
|
||||
LEAQ (SI)(DX*1), BX
|
||||
SUBQ $32, BX
|
||||
|
||||
// Load vN from d.
|
||||
@@ -199,7 +199,7 @@ blockLoop:
|
||||
round(R10)
|
||||
round(R11)
|
||||
|
||||
CMPQ CX, BX
|
||||
CMPQ SI, BX
|
||||
JLE blockLoop
|
||||
|
||||
// Copy vN back to d.
|
||||
@@ -208,8 +208,8 @@ blockLoop:
|
||||
MOVQ R10, 16(AX)
|
||||
MOVQ R11, 24(AX)
|
||||
|
||||
// The number of bytes written is CX minus the old base pointer.
|
||||
SUBQ b_base+8(FP), CX
|
||||
MOVQ CX, ret+32(FP)
|
||||
// The number of bytes written is SI minus the old base pointer.
|
||||
SUBQ b_base+8(FP), SI
|
||||
MOVQ SI, ret+32(FP)
|
||||
|
||||
RET
|
||||
|
||||
+33
-22
@@ -6,41 +6,52 @@
|
||||
package xxhash
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Notes:
|
||||
//
|
||||
// See https://groups.google.com/d/msg/golang-nuts/dcjzJy-bSpw/tcZYBzQqAQAJ
|
||||
// for some discussion about these unsafe conversions.
|
||||
//
|
||||
// In the future it's possible that compiler optimizations will make these
|
||||
// unsafe operations unnecessary: https://golang.org/issue/2205.
|
||||
// XxxString functions unnecessary by realizing that calls such as
|
||||
// Sum64([]byte(s)) don't need to copy s. See https://golang.org/issue/2205.
|
||||
// If that happens, even if we keep these functions they can be replaced with
|
||||
// the trivial safe code.
|
||||
|
||||
// NOTE: The usual way of doing an unsafe string-to-[]byte conversion is:
|
||||
//
|
||||
// Both of these wrapper functions still incur function call overhead since they
|
||||
// will not be inlined. We could write Go/asm copies of Sum64 and Digest.Write
|
||||
// for strings to squeeze out a bit more speed. Mid-stack inlining should
|
||||
// eventually fix this.
|
||||
// var b []byte
|
||||
// bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
// bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
|
||||
// bh.Len = len(s)
|
||||
// bh.Cap = len(s)
|
||||
//
|
||||
// Unfortunately, as of Go 1.15.3 the inliner's cost model assigns a high enough
|
||||
// weight to this sequence of expressions that any function that uses it will
|
||||
// not be inlined. Instead, the functions below use a different unsafe
|
||||
// conversion designed to minimize the inliner weight and allow both to be
|
||||
// inlined. There is also a test (TestInlining) which verifies that these are
|
||||
// inlined.
|
||||
//
|
||||
// See https://github.com/golang/go/issues/42739 for discussion.
|
||||
|
||||
// Sum64String computes the 64-bit xxHash digest of s.
|
||||
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
|
||||
func Sum64String(s string) uint64 {
|
||||
var b []byte
|
||||
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
|
||||
bh.Len = len(s)
|
||||
bh.Cap = len(s)
|
||||
b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))
|
||||
return Sum64(b)
|
||||
}
|
||||
|
||||
// WriteString adds more data to d. It always returns len(s), nil.
|
||||
// It may be faster than Write([]byte(s)) by avoiding a copy.
|
||||
func (d *Digest) WriteString(s string) (n int, err error) {
|
||||
var b []byte
|
||||
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
|
||||
bh.Len = len(s)
|
||||
bh.Cap = len(s)
|
||||
return d.Write(b)
|
||||
d.Write(*(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)})))
|
||||
// d.Write always returns len(s), nil.
|
||||
// Ignoring the return output and returning these fixed values buys a
|
||||
// savings of 6 in the inliner's cost model.
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
// sliceHeader is similar to reflect.SliceHeader, but it assumes that the layout
|
||||
// of the first two words is the same as the layout of a string.
|
||||
type sliceHeader struct {
|
||||
s string
|
||||
cap int
|
||||
}
|
||||
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
vendor
|
||||
*.out
|
||||
*.log
|
||||
*.test
|
||||
.vscode
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.10.x"
|
||||
- "1.11.x"
|
||||
- "1.12.x"
|
||||
|
||||
script:
|
||||
- go get github.com/golang/dep/cmd/dep github.com/stretchr/testify
|
||||
- dep ensure -v -vendor-only
|
||||
- go test ./gojay/codegen/test/... -race
|
||||
- go test -race -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
-163
@@ -1,163 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1a37f9f2ae10d161d9688fb6008ffa14e1631e5068cc3e9698008b9e8d40d575"
|
||||
name = "cloud.google.com/go"
|
||||
packages = ["compute/metadata"]
|
||||
pruneopts = ""
|
||||
revision = "457ea5c15ccf3b87db582c450e80101989da35f7"
|
||||
version = "v0.40.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:968d8903d598e3fae738325d3410f33f07ea6a2b9ee5591e9c262ee37df6845a"
|
||||
name = "github.com/go-errors/errors"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "a6af135bd4e28680facf08a3d206b454abc877a4"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:529d738b7976c3848cae5cf3a8036440166835e389c1f617af701eeb12a0518d"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
pruneopts = ""
|
||||
revision = "b5d812f8a3706043e23a9cd5babf2e5423744d30"
|
||||
version = "v1.3.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:cae59d7b8243c671c9f544965522ba35c0fec48ee80adb9f1400cd2f33abbbec"
|
||||
name = "github.com/mailru/easyjson"
|
||||
packages = [
|
||||
".",
|
||||
"buffer",
|
||||
"jlexer",
|
||||
"jwriter",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "1ea4449da9834f4d333f1cc461c374aea217d249"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1d7e1867c49a6dd9856598ef7c3123604ea3daabf5b83f303ff457bcbc410b1d"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
|
||||
version = "v0.8.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8d4bbd8ab012efc77ab6b97286f2aff262bcdeac9803bb57d75cf7d0a5e6a877"
|
||||
name = "github.com/viant/assertly"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "04f45e0aeb6f3455884877b047a97bcc95dc9493"
|
||||
version = "v0.4.8"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5913451bc2d274673c0716efe226a137625740cd9380641f4d8300ff4f2d82a0"
|
||||
name = "github.com/viant/toolbox"
|
||||
packages = [
|
||||
".",
|
||||
"cred",
|
||||
"data",
|
||||
"storage",
|
||||
"url",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "1be8e4d172138324f40d55ea61a2aeab0c5ce864"
|
||||
version = "v0.24.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:9d150270ca2c3356f2224a0878daa1652e4d0b25b345f18b4f6e156cc4b8ec5e"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"blowfish",
|
||||
"curve25519",
|
||||
"ed25519",
|
||||
"ed25519/internal/edwards25519",
|
||||
"internal/chacha20",
|
||||
"internal/subtle",
|
||||
"poly1305",
|
||||
"ssh",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "f99c8df09eb5bff426315721bfa5f16a99cad32c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:5a56f211e7c12a65c5585c629457a2fb91d8719844ee8fab92727ea8adb5721c"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"context/ctxhttp",
|
||||
"websocket",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "461777fb6f67e8cb9d70cda16573678d085a74cf"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:01bdbbc604dcd5afb6f66a717f69ad45e9643c72d5bc11678d44ffa5c50f9e42"
|
||||
name = "golang.org/x/oauth2"
|
||||
packages = [
|
||||
".",
|
||||
"google",
|
||||
"internal",
|
||||
"jws",
|
||||
"jwt",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "0f29369cfe4552d0e4bcddc57cc75f4d7e672a33"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8ddb956f67d4c176abbbc42b7514aaeaf9ea30daa24e27d2cf30ad82f9334a2c"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["cpu"]
|
||||
pruneopts = ""
|
||||
revision = "1e42afee0f762ed3d76e6dd942e4181855fd1849"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:47f391ee443f578f01168347818cb234ed819521e49e4d2c8dd2fb80d48ee41a"
|
||||
name = "google.golang.org/appengine"
|
||||
packages = [
|
||||
".",
|
||||
"internal",
|
||||
"internal/app_identity",
|
||||
"internal/base",
|
||||
"internal/datastore",
|
||||
"internal/log",
|
||||
"internal/modules",
|
||||
"internal/remote_api",
|
||||
"internal/urlfetch",
|
||||
"urlfetch",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "b2f4a3cf3c67576a2ee09e1fe62656a5086ce880"
|
||||
version = "v1.6.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cedccf16b71e86db87a24f8d4c70b0a855872eb967cb906a66b95de56aefbd0d"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
|
||||
version = "v2.2.2"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/go-errors/errors",
|
||||
"github.com/mailru/easyjson",
|
||||
"github.com/mailru/easyjson/jlexer",
|
||||
"github.com/mailru/easyjson/jwriter",
|
||||
"github.com/viant/assertly",
|
||||
"github.com/viant/toolbox",
|
||||
"github.com/viant/toolbox/url",
|
||||
"golang.org/x/net/websocket",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
ignored = ["github.com/francoispqt/benchmarks*","github.com/stretchr/testify*","github.com/stretchr/testify","github.com/json-iterator/go","github.com/buger/jsonparser"]
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 gojay
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -race -run=^Test -v
|
||||
|
||||
.PHONY: cover
|
||||
cover:
|
||||
go test -coverprofile=coverage.out -covermode=atomic
|
||||
|
||||
.PHONY: coverhtml
|
||||
coverhtml:
|
||||
go tool cover -html=coverage.out
|
||||
-855
@@ -1,855 +0,0 @@
|
||||
[](https://travis-ci.org/francoispqt/gojay)
|
||||
[](https://codecov.io/gh/francoispqt/gojay)
|
||||
[](https://goreportcard.com/report/github.com/francoispqt/gojay)
|
||||
[](https://godoc.org/github.com/francoispqt/gojay)
|
||||

|
||||
[](https://sourcegraph.com/github.com/francoispqt/gojay)
|
||||

|
||||
|
||||
# GoJay
|
||||
|
||||
<img src="https://github.com/francoispqt/gojay/raw/master/gojay.png" width="200px">
|
||||
|
||||
GoJay is a performant JSON encoder/decoder for Golang (currently the most performant, [see benchmarks](#benchmark-results)).
|
||||
|
||||
It has a simple API and doesn't use reflection. It relies on small interfaces to decode/encode structures and slices.
|
||||
|
||||
Gojay also comes with powerful stream decoding features and an even faster [Unsafe](#unsafe-api) API.
|
||||
|
||||
There is also a [code generation tool](https://github.com/francoispqt/gojay/tree/master/gojay) to make usage easier and faster.
|
||||
|
||||
# Why another JSON parser?
|
||||
|
||||
I looked at other fast decoder/encoder and realised it was mostly hardly readable static code generation or a lot of reflection, poor streaming features, and not so fast in the end.
|
||||
|
||||
Also, I wanted to build a decoder that could consume an io.Reader of line or comma delimited JSON, in a JIT way. To consume a flow of JSON objects from a TCP connection for example or from a standard output. Same way I wanted to build an encoder that could encode a flow of data to a io.Writer.
|
||||
|
||||
This is how GoJay aims to be a very fast, JIT stream parser with 0 reflection, low allocation with a friendly API.
|
||||
|
||||
# Get started
|
||||
|
||||
```bash
|
||||
go get github.com/francoispqt/gojay
|
||||
```
|
||||
|
||||
* [Encoder](#encoding)
|
||||
* [Decoder](#decoding)
|
||||
* [Stream API](#stream-api)
|
||||
* [Code Generation](https://github.com/francoispqt/gojay/tree/master/gojay)
|
||||
|
||||
## Decoding
|
||||
|
||||
Decoding is done through two different API similar to standard `encoding/json`:
|
||||
* [Unmarshal](#unmarshal-api)
|
||||
* [Decode](#decode-api)
|
||||
|
||||
|
||||
Example of basic stucture decoding with Unmarshal:
|
||||
```go
|
||||
import "github.com/francoispqt/gojay"
|
||||
|
||||
type user struct {
|
||||
id int
|
||||
name string
|
||||
email string
|
||||
}
|
||||
// implement gojay.UnmarshalerJSONObject
|
||||
func (u *user) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
|
||||
switch key {
|
||||
case "id":
|
||||
return dec.Int(&u.id)
|
||||
case "name":
|
||||
return dec.String(&u.name)
|
||||
case "email":
|
||||
return dec.String(&u.email)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (u *user) NKeys() int {
|
||||
return 3
|
||||
}
|
||||
|
||||
func main() {
|
||||
u := &user{}
|
||||
d := []byte(`{"id":1,"name":"gojay","email":"gojay@email.com"}`)
|
||||
err := gojay.UnmarshalJSONObject(d, u)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
with Decode:
|
||||
```go
|
||||
func main() {
|
||||
u := &user{}
|
||||
dec := gojay.NewDecoder(bytes.NewReader([]byte(`{"id":1,"name":"gojay","email":"gojay@email.com"}`)))
|
||||
err := dec.DecodeObject(d, u)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Unmarshal API
|
||||
|
||||
Unmarshal API decodes a `[]byte` to a given pointer with a single function.
|
||||
|
||||
Behind the doors, Unmarshal API borrows a `*gojay.Decoder` resets its settings and decodes the data to the given pointer and releases the `*gojay.Decoder` to the pool when it finishes, whether it encounters an error or not.
|
||||
|
||||
If it cannot find the right Decoding strategy for the type of the given pointer, it returns an `InvalidUnmarshalError`. You can test the error returned by doing `if ok := err.(InvalidUnmarshalError); ok {}`.
|
||||
|
||||
Unmarshal API comes with three functions:
|
||||
* Unmarshal
|
||||
```go
|
||||
func Unmarshal(data []byte, v interface{}) error
|
||||
```
|
||||
|
||||
* UnmarshalJSONObject
|
||||
```go
|
||||
func UnmarshalJSONObject(data []byte, v gojay.UnmarshalerJSONObject) error
|
||||
```
|
||||
|
||||
* UnmarshalJSONArray
|
||||
```go
|
||||
func UnmarshalJSONArray(data []byte, v gojay.UnmarshalerJSONArray) error
|
||||
```
|
||||
|
||||
|
||||
### Decode API
|
||||
|
||||
Decode API decodes a `[]byte` to a given pointer by creating or borrowing a `*gojay.Decoder` with an `io.Reader` and calling `Decode` methods.
|
||||
|
||||
__Getting a *gojay.Decoder or Borrowing__
|
||||
|
||||
You can either get a fresh `*gojay.Decoder` calling `dec := gojay.NewDecoder(io.Reader)` or borrow one from the pool by calling `dec := gojay.BorrowDecoder(io.Reader)`.
|
||||
|
||||
After using a decoder, you can release it by calling `dec.Release()`. Beware, if you reuse the decoder after releasing it, it will panic with an error of type `InvalidUsagePooledDecoderError`. If you want to fully benefit from the pooling, you must release your decoders after using.
|
||||
|
||||
Example getting a fresh an releasing:
|
||||
```go
|
||||
str := ""
|
||||
dec := gojay.NewDecoder(strings.NewReader(`"test"`))
|
||||
defer dec.Release()
|
||||
if err := dec.Decode(&str); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
Example borrowing a decoder and releasing:
|
||||
```go
|
||||
str := ""
|
||||
dec := gojay.BorrowDecoder(strings.NewReader(`"test"`))
|
||||
defer dec.Release()
|
||||
if err := dec.Decode(&str); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
`*gojay.Decoder` has multiple methods to decode to specific types:
|
||||
* Decode
|
||||
```go
|
||||
func (dec *gojay.Decoder) Decode(v interface{}) error
|
||||
```
|
||||
* DecodeObject
|
||||
```go
|
||||
func (dec *gojay.Decoder) DecodeObject(v gojay.UnmarshalerJSONObject) error
|
||||
```
|
||||
* DecodeArray
|
||||
```go
|
||||
func (dec *gojay.Decoder) DecodeArray(v gojay.UnmarshalerJSONArray) error
|
||||
```
|
||||
* DecodeInt
|
||||
```go
|
||||
func (dec *gojay.Decoder) DecodeInt(v *int) error
|
||||
```
|
||||
* DecodeBool
|
||||
```go
|
||||
func (dec *gojay.Decoder) DecodeBool(v *bool) error
|
||||
```
|
||||
* DecodeString
|
||||
```go
|
||||
func (dec *gojay.Decoder) DecodeString(v *string) error
|
||||
```
|
||||
|
||||
All DecodeXxx methods are used to decode top level JSON values. If you are decoding keys or items of a JSON object or array, don't use the Decode methods.
|
||||
|
||||
Example:
|
||||
```go
|
||||
reader := strings.NewReader(`"John Doe"`)
|
||||
dec := NewDecoder(reader)
|
||||
|
||||
var str string
|
||||
err := dec.DecodeString(&str)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(str) // John Doe
|
||||
```
|
||||
|
||||
### Structs and Maps
|
||||
#### UnmarshalerJSONObject Interface
|
||||
|
||||
To unmarshal a JSON object to a structure, the structure must implement the `UnmarshalerJSONObject` interface:
|
||||
```go
|
||||
type UnmarshalerJSONObject interface {
|
||||
UnmarshalJSONObject(*gojay.Decoder, string) error
|
||||
NKeys() int
|
||||
}
|
||||
```
|
||||
`UnmarshalJSONObject` method takes two arguments, the first one is a pointer to the Decoder (*gojay.Decoder) and the second one is the string value of the current key being parsed. If the JSON data is not an object, the UnmarshalJSONObject method will never be called.
|
||||
|
||||
`NKeys` method must return the number of keys to Unmarshal in the JSON object or 0. If zero is returned, all keys will be parsed.
|
||||
|
||||
Example of implementation for a struct:
|
||||
```go
|
||||
type user struct {
|
||||
id int
|
||||
name string
|
||||
email string
|
||||
}
|
||||
// implement UnmarshalerJSONObject
|
||||
func (u *user) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
|
||||
switch key {
|
||||
case "id":
|
||||
return dec.Int(&u.id)
|
||||
case "name":
|
||||
return dec.String(&u.name)
|
||||
case "email":
|
||||
return dec.String(&u.email)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (u *user) NKeys() int {
|
||||
return 3
|
||||
}
|
||||
```
|
||||
|
||||
Example of implementation for a `map[string]string`:
|
||||
```go
|
||||
// define our custom map type implementing UnmarshalerJSONObject
|
||||
type message map[string]string
|
||||
|
||||
// Implementing Unmarshaler
|
||||
func (m message) UnmarshalJSONObject(dec *gojay.Decoder, k string) error {
|
||||
str := ""
|
||||
err := dec.String(&str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m[k] = str
|
||||
return nil
|
||||
}
|
||||
|
||||
// we return 0, it tells the Decoder to decode all keys
|
||||
func (m message) NKeys() int {
|
||||
return 0
|
||||
}
|
||||
```
|
||||
|
||||
### Arrays, Slices and Channels
|
||||
|
||||
To unmarshal a JSON object to a slice an array or a channel, it must implement the UnmarshalerJSONArray interface:
|
||||
```go
|
||||
type UnmarshalerJSONArray interface {
|
||||
UnmarshalJSONArray(*gojay.Decoder) error
|
||||
}
|
||||
```
|
||||
UnmarshalJSONArray method takes one argument, a pointer to the Decoder (*gojay.Decoder). If the JSON data is not an array, the Unmarshal method will never be called.
|
||||
|
||||
Example of implementation with a slice:
|
||||
```go
|
||||
type testSlice []string
|
||||
// implement UnmarshalerJSONArray
|
||||
func (t *testSlice) UnmarshalJSONArray(dec *gojay.Decoder) error {
|
||||
str := ""
|
||||
if err := dec.String(&str); err != nil {
|
||||
return err
|
||||
}
|
||||
*t = append(*t, str)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
dec := gojay.BorrowDecoder(strings.NewReader(`["Tom", "Jim"]`))
|
||||
var slice testSlice
|
||||
err := dec.DecodeArray(&slice)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(slice) // [Tom Jim]
|
||||
dec.Release()
|
||||
}
|
||||
```
|
||||
|
||||
Example of implementation with a channel:
|
||||
```go
|
||||
type testChannel chan string
|
||||
// implement UnmarshalerJSONArray
|
||||
func (c testChannel) UnmarshalJSONArray(dec *gojay.Decoder) error {
|
||||
str := ""
|
||||
if err := dec.String(&str); err != nil {
|
||||
return err
|
||||
}
|
||||
c <- str
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
dec := gojay.BorrowDecoder(strings.NewReader(`["Tom", "Jim"]`))
|
||||
c := make(testChannel, 2)
|
||||
err := dec.DecodeArray(c)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
close(c)
|
||||
dec.Release()
|
||||
}
|
||||
```
|
||||
|
||||
Example of implementation with an array:
|
||||
```go
|
||||
type testArray [3]string
|
||||
// implement UnmarshalerJSONArray
|
||||
func (a *testArray) UnmarshalJSONArray(dec *Decoder) error {
|
||||
var str string
|
||||
if err := dec.String(&str); err != nil {
|
||||
return err
|
||||
}
|
||||
a[dec.Index()] = str
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
dec := gojay.BorrowDecoder(strings.NewReader(`["Tom", "Jim", "Bob"]`))
|
||||
var a testArray
|
||||
err := dec.DecodeArray(&a)
|
||||
fmt.Println(a) // [Tom Jim Bob]
|
||||
dec.Release()
|
||||
}
|
||||
```
|
||||
|
||||
### Other types
|
||||
To decode other types (string, int, int32, int64, uint32, uint64, float, booleans), you don't need to implement any interface.
|
||||
|
||||
Example of encoding strings:
|
||||
```go
|
||||
func main() {
|
||||
json := []byte(`"Jay"`)
|
||||
var v string
|
||||
err := gojay.Unmarshal(json, &v)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(v) // Jay
|
||||
}
|
||||
```
|
||||
|
||||
### Decode values methods
|
||||
When decoding a JSON object of a JSON array using `UnmarshalerJSONObject` or `UnmarshalerJSONArray` interface, the `gojay.Decoder` provides dozens of methods to Decode multiple types.
|
||||
|
||||
Non exhaustive list of methods available (to see all methods, check the godoc):
|
||||
```go
|
||||
dec.Int
|
||||
dec.Int8
|
||||
dec.Int16
|
||||
dec.Int32
|
||||
dec.Int64
|
||||
dec.Uint8
|
||||
dec.Uint16
|
||||
dec.Uint32
|
||||
dec.Uint64
|
||||
dec.String
|
||||
dec.Time
|
||||
dec.Bool
|
||||
dec.SQLNullString
|
||||
dec.SQLNullInt64
|
||||
```
|
||||
|
||||
|
||||
## Encoding
|
||||
|
||||
Encoding is done through two different API similar to standard `encoding/json`:
|
||||
* [Marshal](#marshal-api)
|
||||
* [Encode](#encode-api)
|
||||
|
||||
Example of basic structure encoding with Marshal:
|
||||
```go
|
||||
import "github.com/francoispqt/gojay"
|
||||
|
||||
type user struct {
|
||||
id int
|
||||
name string
|
||||
email string
|
||||
}
|
||||
|
||||
// implement MarshalerJSONObject
|
||||
func (u *user) MarshalJSONObject(enc *gojay.Encoder) {
|
||||
enc.IntKey("id", u.id)
|
||||
enc.StringKey("name", u.name)
|
||||
enc.StringKey("email", u.email)
|
||||
}
|
||||
func (u *user) IsNil() bool {
|
||||
return u == nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
u := &user{1, "gojay", "gojay@email.com"}
|
||||
b, err := gojay.MarshalJSONObject(u)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(b)) // {"id":1,"name":"gojay","email":"gojay@email.com"}
|
||||
}
|
||||
```
|
||||
|
||||
with Encode:
|
||||
```go
|
||||
func main() {
|
||||
u := &user{1, "gojay", "gojay@email.com"}
|
||||
b := strings.Builder{}
|
||||
enc := gojay.NewEncoder(&b)
|
||||
if err := enc.Encode(u); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(b.String()) // {"id":1,"name":"gojay","email":"gojay@email.com"}
|
||||
}
|
||||
```
|
||||
|
||||
### Marshal API
|
||||
|
||||
Marshal API encodes a value to a JSON `[]byte` with a single function.
|
||||
|
||||
Behind the doors, Marshal API borrows a `*gojay.Encoder` resets its settings and encodes the data to an internal byte buffer and releases the `*gojay.Encoder` to the pool when it finishes, whether it encounters an error or not.
|
||||
|
||||
If it cannot find the right Encoding strategy for the type of the given value, it returns an `InvalidMarshalError`. You can test the error returned by doing `if ok := err.(InvalidMarshalError); ok {}`.
|
||||
|
||||
Marshal API comes with three functions:
|
||||
* Marshal
|
||||
```go
|
||||
func Marshal(v interface{}) ([]byte, error)
|
||||
```
|
||||
|
||||
* MarshalJSONObject
|
||||
```go
|
||||
func MarshalJSONObject(v gojay.MarshalerJSONObject) ([]byte, error)
|
||||
```
|
||||
|
||||
* MarshalJSONArray
|
||||
```go
|
||||
func MarshalJSONArray(v gojay.MarshalerJSONArray) ([]byte, error)
|
||||
```
|
||||
|
||||
### Encode API
|
||||
|
||||
Encode API decodes a value to JSON by creating or borrowing a `*gojay.Encoder` sending it to an `io.Writer` and calling `Encode` methods.
|
||||
|
||||
__Getting a *gojay.Encoder or Borrowing__
|
||||
|
||||
You can either get a fresh `*gojay.Encoder` calling `enc := gojay.NewEncoder(io.Writer)` or borrow one from the pool by calling `enc := gojay.BorrowEncoder(io.Writer)`.
|
||||
|
||||
After using an encoder, you can release it by calling `enc.Release()`. Beware, if you reuse the encoder after releasing it, it will panic with an error of type `InvalidUsagePooledEncoderError`. If you want to fully benefit from the pooling, you must release your encoders after using.
|
||||
|
||||
Example getting a fresh encoder an releasing:
|
||||
```go
|
||||
str := "test"
|
||||
b := strings.Builder{}
|
||||
enc := gojay.NewEncoder(&b)
|
||||
defer enc.Release()
|
||||
if err := enc.Encode(str); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
Example borrowing an encoder and releasing:
|
||||
```go
|
||||
str := "test"
|
||||
b := strings.Builder{}
|
||||
enc := gojay.BorrowEncoder(b)
|
||||
defer enc.Release()
|
||||
if err := enc.Encode(str); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
`*gojay.Encoder` has multiple methods to encoder specific types to JSON:
|
||||
* Encode
|
||||
```go
|
||||
func (enc *gojay.Encoder) Encode(v interface{}) error
|
||||
```
|
||||
* EncodeObject
|
||||
```go
|
||||
func (enc *gojay.Encoder) EncodeObject(v gojay.MarshalerJSONObject) error
|
||||
```
|
||||
* EncodeArray
|
||||
```go
|
||||
func (enc *gojay.Encoder) EncodeArray(v gojay.MarshalerJSONArray) error
|
||||
```
|
||||
* EncodeInt
|
||||
```go
|
||||
func (enc *gojay.Encoder) EncodeInt(n int) error
|
||||
```
|
||||
* EncodeInt64
|
||||
```go
|
||||
func (enc *gojay.Encoder) EncodeInt64(n int64) error
|
||||
```
|
||||
* EncodeFloat
|
||||
```go
|
||||
func (enc *gojay.Encoder) EncodeFloat(n float64) error
|
||||
```
|
||||
* EncodeBool
|
||||
```go
|
||||
func (enc *gojay.Encoder) EncodeBool(v bool) error
|
||||
```
|
||||
* EncodeString
|
||||
```go
|
||||
func (enc *gojay.Encoder) EncodeString(s string) error
|
||||
```
|
||||
|
||||
### Structs and Maps
|
||||
|
||||
To encode a structure, the structure must implement the MarshalerJSONObject interface:
|
||||
```go
|
||||
type MarshalerJSONObject interface {
|
||||
MarshalJSONObject(enc *gojay.Encoder)
|
||||
IsNil() bool
|
||||
}
|
||||
```
|
||||
`MarshalJSONObject` method takes one argument, a pointer to the Encoder (*gojay.Encoder). The method must add all the keys in the JSON Object by calling Decoder's methods.
|
||||
|
||||
IsNil method returns a boolean indicating if the interface underlying value is nil or not. It is used to safely ensure that the underlying value is not nil without using Reflection.
|
||||
|
||||
Example of implementation for a struct:
|
||||
```go
|
||||
type user struct {
|
||||
id int
|
||||
name string
|
||||
email string
|
||||
}
|
||||
|
||||
// implement MarshalerJSONObject
|
||||
func (u *user) MarshalJSONObject(enc *gojay.Encoder) {
|
||||
enc.IntKey("id", u.id)
|
||||
enc.StringKey("name", u.name)
|
||||
enc.StringKey("email", u.email)
|
||||
}
|
||||
func (u *user) IsNil() bool {
|
||||
return u == nil
|
||||
}
|
||||
```
|
||||
|
||||
Example of implementation for a `map[string]string`:
|
||||
```go
|
||||
// define our custom map type implementing MarshalerJSONObject
|
||||
type message map[string]string
|
||||
|
||||
// Implementing Marshaler
|
||||
func (m message) MarshalJSONObject(enc *gojay.Encoder) {
|
||||
for k, v := range m {
|
||||
enc.StringKey(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (m message) IsNil() bool {
|
||||
return m == nil
|
||||
}
|
||||
```
|
||||
|
||||
### Arrays and Slices
|
||||
To encode an array or a slice, the slice/array must implement the MarshalerJSONArray interface:
|
||||
```go
|
||||
type MarshalerJSONArray interface {
|
||||
MarshalJSONArray(enc *gojay.Encoder)
|
||||
IsNil() bool
|
||||
}
|
||||
```
|
||||
`MarshalJSONArray` method takes one argument, a pointer to the Encoder (*gojay.Encoder). The method must add all element in the JSON Array by calling Decoder's methods.
|
||||
|
||||
`IsNil` method returns a boolean indicating if the interface underlying value is nil(empty) or not. It is used to safely ensure that the underlying value is not nil without using Reflection and also to in `OmitEmpty` feature.
|
||||
|
||||
Example of implementation:
|
||||
```go
|
||||
type users []*user
|
||||
// implement MarshalerJSONArray
|
||||
func (u *users) MarshalJSONArray(enc *gojay.Encoder) {
|
||||
for _, e := range u {
|
||||
enc.Object(e)
|
||||
}
|
||||
}
|
||||
func (u *users) IsNil() bool {
|
||||
return len(u) == 0
|
||||
}
|
||||
```
|
||||
|
||||
### Other types
|
||||
To encode other types (string, int, float, booleans), you don't need to implement any interface.
|
||||
|
||||
Example of encoding strings:
|
||||
```go
|
||||
func main() {
|
||||
name := "Jay"
|
||||
b, err := gojay.Marshal(name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(b)) // "Jay"
|
||||
}
|
||||
```
|
||||
|
||||
# Stream API
|
||||
|
||||
### Stream Decoding
|
||||
GoJay ships with a powerful stream decoder.
|
||||
|
||||
It allows to read continuously from an io.Reader stream and do JIT decoding writing unmarshalled JSON to a channel to allow async consuming.
|
||||
|
||||
When using the Stream API, the Decoder implements context.Context to provide graceful cancellation.
|
||||
|
||||
To decode a stream of JSON, you must call `gojay.Stream.DecodeStream` and pass it a `UnmarshalerStream` implementation.
|
||||
|
||||
```go
|
||||
type UnmarshalerStream interface {
|
||||
UnmarshalStream(*StreamDecoder) error
|
||||
}
|
||||
```
|
||||
|
||||
Example of implementation of stream reading from a WebSocket connection:
|
||||
```go
|
||||
// implement UnmarshalerStream
|
||||
type ChannelStream chan *user
|
||||
|
||||
func (c ChannelStream) UnmarshalStream(dec *gojay.StreamDecoder) error {
|
||||
u := &user{}
|
||||
if err := dec.Object(u); err != nil {
|
||||
return err
|
||||
}
|
||||
c <- u
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// get our websocket connection
|
||||
origin := "http://localhost/"
|
||||
url := "ws://localhost:12345/ws"
|
||||
ws, err := websocket.Dial(url, "", origin)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// create our channel which will receive our objects
|
||||
streamChan := ChannelStream(make(chan *user))
|
||||
// borrow a decoder
|
||||
dec := gojay.Stream.BorrowDecoder(ws)
|
||||
// start decoding, it will block until a JSON message is decoded from the WebSocket
|
||||
// or until Done channel is closed
|
||||
go dec.DecodeStream(streamChan)
|
||||
for {
|
||||
select {
|
||||
case v := <-streamChan:
|
||||
// Got something from my websocket!
|
||||
log.Println(v)
|
||||
case <-dec.Done():
|
||||
log.Println("finished reading from WebSocket")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Stream Encoding
|
||||
GoJay ships with a powerful stream encoder part of the Stream API.
|
||||
|
||||
It allows to write continuously to an io.Writer and do JIT encoding of data fed to a channel to allow async consuming. You can set multiple consumers on the channel to be as performant as possible. Consumers are non blocking and are scheduled individually in their own go routine.
|
||||
|
||||
When using the Stream API, the Encoder implements context.Context to provide graceful cancellation.
|
||||
|
||||
To encode a stream of data, you must call `EncodeStream` and pass it a `MarshalerStream` implementation.
|
||||
|
||||
```go
|
||||
type MarshalerStream interface {
|
||||
MarshalStream(enc *gojay.StreamEncoder)
|
||||
}
|
||||
```
|
||||
|
||||
Example of implementation of stream writing to a WebSocket:
|
||||
```go
|
||||
// Our structure which will be pushed to our stream
|
||||
type user struct {
|
||||
id int
|
||||
name string
|
||||
email string
|
||||
}
|
||||
|
||||
func (u *user) MarshalJSONObject(enc *gojay.Encoder) {
|
||||
enc.IntKey("id", u.id)
|
||||
enc.StringKey("name", u.name)
|
||||
enc.StringKey("email", u.email)
|
||||
}
|
||||
func (u *user) IsNil() bool {
|
||||
return u == nil
|
||||
}
|
||||
|
||||
// Our MarshalerStream implementation
|
||||
type StreamChan chan *user
|
||||
|
||||
func (s StreamChan) MarshalStream(enc *gojay.StreamEncoder) {
|
||||
select {
|
||||
case <-enc.Done():
|
||||
return
|
||||
case o := <-s:
|
||||
enc.Object(o)
|
||||
}
|
||||
}
|
||||
|
||||
// Our main function
|
||||
func main() {
|
||||
// get our websocket connection
|
||||
origin := "http://localhost/"
|
||||
url := "ws://localhost:12345/ws"
|
||||
ws, err := websocket.Dial(url, "", origin)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// we borrow an encoder set stdout as the writer,
|
||||
// set the number of consumer to 10
|
||||
// and tell the encoder to separate each encoded element
|
||||
// added to the channel by a new line character
|
||||
enc := gojay.Stream.BorrowEncoder(ws).NConsumer(10).LineDelimited()
|
||||
// instantiate our MarshalerStream
|
||||
s := StreamChan(make(chan *user))
|
||||
// start the stream encoder
|
||||
// will block its goroutine until enc.Cancel(error) is called
|
||||
// or until something is written to the channel
|
||||
go enc.EncodeStream(s)
|
||||
// write to our MarshalerStream
|
||||
for i := 0; i < 1000; i++ {
|
||||
s <- &user{i, "username", "user@email.com"}
|
||||
}
|
||||
// Wait
|
||||
<-enc.Done()
|
||||
}
|
||||
```
|
||||
|
||||
# Unsafe API
|
||||
|
||||
Unsafe API has the same functions than the regular API, it only has `Unmarshal API` for now. It is unsafe because it makes assumptions on the quality of the given JSON.
|
||||
|
||||
If you are not sure if your JSON is valid, don't use the Unsafe API.
|
||||
|
||||
Also, the `Unsafe` API does not copy the buffer when using Unmarshal API, which, in case of string decoding, can lead to data corruption if a byte buffer is reused. Using the `Decode` API makes `Unsafe` API safer as the io.Reader relies on `copy` builtin method and `Decoder` will have its own internal buffer :)
|
||||
|
||||
Access the `Unsafe` API this way:
|
||||
```go
|
||||
gojay.Unsafe.Unmarshal(b, v)
|
||||
```
|
||||
|
||||
|
||||
# Benchmarks
|
||||
|
||||
Benchmarks encode and decode three different data based on size (small, medium, large).
|
||||
|
||||
To run benchmark for decoder:
|
||||
```bash
|
||||
cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/decoder && make bench
|
||||
```
|
||||
|
||||
To run benchmark for encoder:
|
||||
```bash
|
||||
cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/encoder && make bench
|
||||
```
|
||||
|
||||
# Benchmark Results
|
||||
## Decode
|
||||
|
||||
<img src="https://images2.imgbox.com/78/01/49OExcPh_o.png" width="500px">
|
||||
|
||||
### Small Payload
|
||||
[benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/decoder/decoder_bench_small_test.go)
|
||||
|
||||
[benchmark data is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/benchmarks_small.go)
|
||||
|
||||
| | ns/op | bytes/op | allocs/op |
|
||||
|-----------------|-----------|--------------|-----------|
|
||||
| Std Library | 2547 | 496 | 4 |
|
||||
| JsonIter | 2046 | 312 | 12 |
|
||||
| JsonParser | 1408 | 0 | 0 |
|
||||
| EasyJson | 929 | 240 | 2 |
|
||||
| **GoJay** | **807** | **256** | **2** |
|
||||
| **GoJay-unsafe**| **712** | **112** | **1** |
|
||||
|
||||
### Medium Payload
|
||||
[benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/decoder/decoder_bench_medium_test.go)
|
||||
|
||||
[benchmark data is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/benchmarks_medium.go)
|
||||
|
||||
| | ns/op | bytes/op | allocs/op |
|
||||
|-----------------|-----------|----------|-----------|
|
||||
| Std Library | 30148 | 2152 | 496 |
|
||||
| JsonIter | 16309 | 2976 | 80 |
|
||||
| JsonParser | 7793 | 0 | 0 |
|
||||
| EasyJson | 7957 | 232 | 6 |
|
||||
| **GoJay** | **4984** | **2448** | **8** |
|
||||
| **GoJay-unsafe**| **4809** | **144** | **7** |
|
||||
|
||||
### Large Payload
|
||||
[benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/decoder/decoder_bench_large_test.go)
|
||||
|
||||
[benchmark data is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/benchmarks_large.go)
|
||||
|
||||
| | ns/op | bytes/op | allocs/op |
|
||||
|-----------------|-----------|-------------|-----------|
|
||||
| JsonIter | 210078 | 41712 | 1136 |
|
||||
| EasyJson | 106626 | 160 | 2 |
|
||||
| JsonParser | 66813 | 0 | 0 |
|
||||
| **GoJay** | **52153** | **31241** | **77** |
|
||||
| **GoJay-unsafe**| **48277** | **2561** | **76** |
|
||||
|
||||
## Encode
|
||||
|
||||
<img src="https://images2.imgbox.com/e9/cc/pnM8c7Gf_o.png" width="500px">
|
||||
|
||||
### Small Struct
|
||||
[benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/encoder/encoder_bench_small_test.go)
|
||||
|
||||
[benchmark data is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/benchmarks_small.go)
|
||||
|
||||
| | ns/op | bytes/op | allocs/op |
|
||||
|----------------|----------|--------------|-----------|
|
||||
| Std Library | 1280 | 464 | 3 |
|
||||
| EasyJson | 871 | 944 | 6 |
|
||||
| JsonIter | 866 | 272 | 3 |
|
||||
| **GoJay** | **543** | **112** | **1** |
|
||||
| **GoJay-func** | **347** | **0** | **0** |
|
||||
|
||||
### Medium Struct
|
||||
[benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/encoder/encoder_bench_medium_test.go)
|
||||
|
||||
[benchmark data is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/benchmarks_medium.go)
|
||||
|
||||
| | ns/op | bytes/op | allocs/op |
|
||||
|-------------|----------|--------------|-----------|
|
||||
| Std Library | 5006 | 1496 | 25 |
|
||||
| JsonIter | 2232 | 1544 | 20 |
|
||||
| EasyJson | 1997 | 1544 | 19 |
|
||||
| **GoJay** | **1522** | **312** | **14** |
|
||||
|
||||
### Large Struct
|
||||
[benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/encoder/encoder_bench_large_test.go)
|
||||
|
||||
[benchmark data is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/benchmarks_large.go)
|
||||
|
||||
| | ns/op | bytes/op | allocs/op |
|
||||
|-------------|-----------|--------------|-----------|
|
||||
| Std Library | 66441 | 20576 | 332 |
|
||||
| JsonIter | 35247 | 20255 | 328 |
|
||||
| EasyJson | 32053 | 15474 | 327 |
|
||||
| **GoJay** | **27847** | **9802** | **318** |
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are welcome :)
|
||||
|
||||
If you encounter issues please report it in Github and/or send an email at [francois@parquet.ninja](mailto:francois@parquet.ninja)
|
||||
|
||||
-386
@@ -1,386 +0,0 @@
|
||||
package gojay
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// UnmarshalJSONArray parses the JSON-encoded data and stores the result in the value pointed to by v.
|
||||
//
|
||||
// v must implement UnmarshalerJSONArray.
|
||||
//
|
||||
// If a JSON value is not appropriate for a given target type, or if a JSON number
|
||||
// overflows the target type, UnmarshalJSONArray skips that field and completes the unmarshaling as best it can.
|
||||
func UnmarshalJSONArray(data []byte, v UnmarshalerJSONArray) error {
|
||||
dec := borrowDecoder(nil, 0)
|
||||
defer dec.Release()
|
||||
dec.data = make([]byte, len(data))
|
||||
copy(dec.data, data)
|
||||
dec.length = len(data)
|
||||
_, err := dec.decodeArray(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.err != nil {
|
||||
return dec.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSONObject parses the JSON-encoded data and stores the result in the value pointed to by v.
|
||||
//
|
||||
// v must implement UnmarshalerJSONObject.
|
||||
//
|
||||
// If a JSON value is not appropriate for a given target type, or if a JSON number
|
||||
// overflows the target type, UnmarshalJSONObject skips that field and completes the unmarshaling as best it can.
|
||||
func UnmarshalJSONObject(data []byte, v UnmarshalerJSONObject) error {
|
||||
dec := borrowDecoder(nil, 0)
|
||||
defer dec.Release()
|
||||
dec.data = make([]byte, len(data))
|
||||
copy(dec.data, data)
|
||||
dec.length = len(data)
|
||||
_, err := dec.decodeObject(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.err != nil {
|
||||
return dec.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v.
|
||||
// If v is nil, not an implementation of UnmarshalerJSONObject or UnmarshalerJSONArray or not one of the following types:
|
||||
// *string, **string, *int, **int, *int8, **int8, *int16, **int16, *int32, **int32, *int64, **int64, *uint8, **uint8, *uint16, **uint16,
|
||||
// *uint32, **uint32, *uint64, **uint64, *float64, **float64, *float32, **float32, *bool, **bool
|
||||
// Unmarshal returns an InvalidUnmarshalError.
|
||||
//
|
||||
//
|
||||
// If a JSON value is not appropriate for a given target type, or if a JSON number
|
||||
// overflows the target type, Unmarshal skips that field and completes the unmarshaling as best it can.
|
||||
// If no more serious errors are encountered, Unmarshal returns an UnmarshalTypeError describing the earliest such error.
|
||||
// In any case, it's not guaranteed that all the remaining fields following the problematic one will be unmarshaled into the target object.
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
var err error
|
||||
var dec *Decoder
|
||||
switch vt := v.(type) {
|
||||
case *string:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeString(vt)
|
||||
case **string:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeStringNull(vt)
|
||||
case *int:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt(vt)
|
||||
case **int:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeIntNull(vt)
|
||||
case *int8:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt8(vt)
|
||||
case **int8:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt8Null(vt)
|
||||
case *int16:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt16(vt)
|
||||
case **int16:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt16Null(vt)
|
||||
case *int32:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt32(vt)
|
||||
case **int32:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt32Null(vt)
|
||||
case *int64:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt64(vt)
|
||||
case **int64:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeInt64Null(vt)
|
||||
case *uint8:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint8(vt)
|
||||
case **uint8:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint8Null(vt)
|
||||
case *uint16:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint16(vt)
|
||||
case **uint16:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint16Null(vt)
|
||||
case *uint32:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint32(vt)
|
||||
case **uint32:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint32Null(vt)
|
||||
case *uint64:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint64(vt)
|
||||
case **uint64:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeUint64Null(vt)
|
||||
case *float64:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeFloat64(vt)
|
||||
case **float64:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeFloat64Null(vt)
|
||||
case *float32:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeFloat32(vt)
|
||||
case **float32:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeFloat32Null(vt)
|
||||
case *bool:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeBool(vt)
|
||||
case **bool:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = data
|
||||
err = dec.decodeBoolNull(vt)
|
||||
case UnmarshalerJSONObject:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = make([]byte, len(data))
|
||||
copy(dec.data, data)
|
||||
_, err = dec.decodeObject(vt)
|
||||
case UnmarshalerJSONArray:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = make([]byte, len(data))
|
||||
copy(dec.data, data)
|
||||
_, err = dec.decodeArray(vt)
|
||||
case *interface{}:
|
||||
dec = borrowDecoder(nil, 0)
|
||||
dec.length = len(data)
|
||||
dec.data = make([]byte, len(data))
|
||||
copy(dec.data, data)
|
||||
err = dec.decodeInterface(vt)
|
||||
default:
|
||||
return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, vt))
|
||||
}
|
||||
defer dec.Release()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dec.err
|
||||
}
|
||||
|
||||
// UnmarshalerJSONObject is the interface to implement to decode a JSON Object.
|
||||
type UnmarshalerJSONObject interface {
|
||||
UnmarshalJSONObject(*Decoder, string) error
|
||||
NKeys() int
|
||||
}
|
||||
|
||||
// UnmarshalerJSONArray is the interface to implement to decode a JSON Array.
|
||||
type UnmarshalerJSONArray interface {
|
||||
UnmarshalJSONArray(*Decoder) error
|
||||
}
|
||||
|
||||
// A Decoder reads and decodes JSON values from an input stream.
|
||||
type Decoder struct {
|
||||
r io.Reader
|
||||
data []byte
|
||||
err error
|
||||
isPooled byte
|
||||
called byte
|
||||
child byte
|
||||
cursor int
|
||||
length int
|
||||
keysDone int
|
||||
arrayIndex int
|
||||
}
|
||||
|
||||
// Decode reads the next JSON-encoded value from the decoder's input (io.Reader) and stores it in the value pointed to by v.
|
||||
//
|
||||
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
|
||||
// The differences between Decode and Unmarshal are:
|
||||
// - Decode reads from an io.Reader in the Decoder, whereas Unmarshal reads from a []byte
|
||||
// - Decode leaves to the user the option of borrowing and releasing a Decoder, whereas Unmarshal internally always borrows a Decoder and releases it when the unmarshaling is completed
|
||||
func (dec *Decoder) Decode(v interface{}) error {
|
||||
if dec.isPooled == 1 {
|
||||
panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
|
||||
}
|
||||
var err error
|
||||
switch vt := v.(type) {
|
||||
case *string:
|
||||
err = dec.decodeString(vt)
|
||||
case **string:
|
||||
err = dec.decodeStringNull(vt)
|
||||
case *int:
|
||||
err = dec.decodeInt(vt)
|
||||
case **int:
|
||||
err = dec.decodeIntNull(vt)
|
||||
case *int8:
|
||||
err = dec.decodeInt8(vt)
|
||||
case **int8:
|
||||
err = dec.decodeInt8Null(vt)
|
||||
case *int16:
|
||||
err = dec.decodeInt16(vt)
|
||||
case **int16:
|
||||
err = dec.decodeInt16Null(vt)
|
||||
case *int32:
|
||||
err = dec.decodeInt32(vt)
|
||||
case **int32:
|
||||
err = dec.decodeInt32Null(vt)
|
||||
case *int64:
|
||||
err = dec.decodeInt64(vt)
|
||||
case **int64:
|
||||
err = dec.decodeInt64Null(vt)
|
||||
case *uint8:
|
||||
err = dec.decodeUint8(vt)
|
||||
case **uint8:
|
||||
err = dec.decodeUint8Null(vt)
|
||||
case *uint16:
|
||||
err = dec.decodeUint16(vt)
|
||||
case **uint16:
|
||||
err = dec.decodeUint16Null(vt)
|
||||
case *uint32:
|
||||
err = dec.decodeUint32(vt)
|
||||
case **uint32:
|
||||
err = dec.decodeUint32Null(vt)
|
||||
case *uint64:
|
||||
err = dec.decodeUint64(vt)
|
||||
case **uint64:
|
||||
err = dec.decodeUint64Null(vt)
|
||||
case *float64:
|
||||
err = dec.decodeFloat64(vt)
|
||||
case **float64:
|
||||
err = dec.decodeFloat64Null(vt)
|
||||
case *float32:
|
||||
err = dec.decodeFloat32(vt)
|
||||
case **float32:
|
||||
err = dec.decodeFloat32Null(vt)
|
||||
case *bool:
|
||||
err = dec.decodeBool(vt)
|
||||
case **bool:
|
||||
err = dec.decodeBoolNull(vt)
|
||||
case UnmarshalerJSONObject:
|
||||
_, err = dec.decodeObject(vt)
|
||||
case UnmarshalerJSONArray:
|
||||
_, err = dec.decodeArray(vt)
|
||||
case *EmbeddedJSON:
|
||||
err = dec.decodeEmbeddedJSON(vt)
|
||||
case *interface{}:
|
||||
err = dec.decodeInterface(vt)
|
||||
default:
|
||||
return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, vt))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dec.err
|
||||
}
|
||||
|
||||
// Non exported
|
||||
|
||||
func isDigit(b byte) bool {
|
||||
switch b {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *Decoder) read() bool {
|
||||
if dec.r != nil {
|
||||
// if we reach the end, double the buffer to ensure there's always more space
|
||||
if len(dec.data) == dec.length {
|
||||
nLen := dec.length * 2
|
||||
if nLen == 0 {
|
||||
nLen = 512
|
||||
}
|
||||
Buf := make([]byte, nLen, nLen)
|
||||
copy(Buf, dec.data)
|
||||
dec.data = Buf
|
||||
}
|
||||
var n int
|
||||
var err error
|
||||
for n == 0 {
|
||||
n, err = dec.r.Read(dec.data[dec.length:])
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
dec.err = err
|
||||
return false
|
||||
}
|
||||
if n == 0 {
|
||||
return false
|
||||
}
|
||||
dec.length = dec.length + n
|
||||
return true
|
||||
}
|
||||
}
|
||||
dec.length = dec.length + n
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (dec *Decoder) nextChar() byte {
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch dec.data[dec.cursor] {
|
||||
case ' ', '\n', '\t', '\r', ',':
|
||||
continue
|
||||
}
|
||||
d := dec.data[dec.cursor]
|
||||
return d
|
||||
}
|
||||
return 0
|
||||
}
|
||||
-247
@@ -1,247 +0,0 @@
|
||||
package gojay
|
||||
|
||||
import "reflect"
|
||||
|
||||
// DecodeArray reads the next JSON-encoded value from the decoder's input (io.Reader)
|
||||
// and stores it in the value pointed to by v.
|
||||
//
|
||||
// v must implement UnmarshalerJSONArray.
|
||||
//
|
||||
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
|
||||
func (dec *Decoder) DecodeArray(v UnmarshalerJSONArray) error {
|
||||
if dec.isPooled == 1 {
|
||||
panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
|
||||
}
|
||||
_, err := dec.decodeArray(v)
|
||||
return err
|
||||
}
|
||||
func (dec *Decoder) decodeArray(arr UnmarshalerJSONArray) (int, error) {
|
||||
// remember last array index in case of nested arrays
|
||||
lastArrayIndex := dec.arrayIndex
|
||||
dec.arrayIndex = 0
|
||||
defer func() {
|
||||
dec.arrayIndex = lastArrayIndex
|
||||
}()
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch dec.data[dec.cursor] {
|
||||
case ' ', '\n', '\t', '\r', ',':
|
||||
continue
|
||||
case '[':
|
||||
dec.cursor = dec.cursor + 1
|
||||
// array is open, char is not space start readings
|
||||
for dec.nextChar() != 0 {
|
||||
// closing array
|
||||
if dec.data[dec.cursor] == ']' {
|
||||
dec.cursor = dec.cursor + 1
|
||||
return dec.cursor, nil
|
||||
}
|
||||
// calling unmarshall function for each element of the slice
|
||||
err := arr.UnmarshalJSONArray(dec)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dec.arrayIndex++
|
||||
}
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
case 'n':
|
||||
// is null
|
||||
dec.cursor++
|
||||
err := dec.assertNull()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return dec.cursor, nil
|
||||
case '{', '"', 'f', 't', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
// can't unmarshall to struct
|
||||
// we skip array and set Error
|
||||
dec.err = dec.makeInvalidUnmarshalErr(arr)
|
||||
err := dec.skipData()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return dec.cursor, nil
|
||||
default:
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
}
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
func (dec *Decoder) decodeArrayNull(v interface{}) (int, error) {
|
||||
// remember last array index in case of nested arrays
|
||||
lastArrayIndex := dec.arrayIndex
|
||||
dec.arrayIndex = 0
|
||||
defer func() {
|
||||
dec.arrayIndex = lastArrayIndex
|
||||
}()
|
||||
vv := reflect.ValueOf(v)
|
||||
vvt := vv.Type()
|
||||
if vvt.Kind() != reflect.Ptr || vvt.Elem().Kind() != reflect.Ptr {
|
||||
dec.err = ErrUnmarshalPtrExpected
|
||||
return 0, dec.err
|
||||
}
|
||||
// not an array not an error, but do not know what to do
|
||||
// do not check syntax
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch dec.data[dec.cursor] {
|
||||
case ' ', '\n', '\t', '\r', ',':
|
||||
continue
|
||||
case '[':
|
||||
dec.cursor = dec.cursor + 1
|
||||
// create our new type
|
||||
elt := vv.Elem()
|
||||
n := reflect.New(elt.Type().Elem())
|
||||
var arr UnmarshalerJSONArray
|
||||
var ok bool
|
||||
if arr, ok = n.Interface().(UnmarshalerJSONArray); !ok {
|
||||
dec.err = dec.makeInvalidUnmarshalErr((UnmarshalerJSONArray)(nil))
|
||||
return 0, dec.err
|
||||
}
|
||||
// array is open, char is not space start readings
|
||||
for dec.nextChar() != 0 {
|
||||
// closing array
|
||||
if dec.data[dec.cursor] == ']' {
|
||||
elt.Set(n)
|
||||
dec.cursor = dec.cursor + 1
|
||||
return dec.cursor, nil
|
||||
}
|
||||
// calling unmarshall function for each element of the slice
|
||||
err := arr.UnmarshalJSONArray(dec)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dec.arrayIndex++
|
||||
}
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
case 'n':
|
||||
// is null
|
||||
dec.cursor++
|
||||
err := dec.assertNull()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return dec.cursor, nil
|
||||
case '{', '"', 'f', 't', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
// can't unmarshall to struct
|
||||
// we skip array and set Error
|
||||
dec.err = dec.makeInvalidUnmarshalErr((UnmarshalerJSONArray)(nil))
|
||||
err := dec.skipData()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return dec.cursor, nil
|
||||
default:
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
}
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
|
||||
func (dec *Decoder) skipArray() (int, error) {
|
||||
var arraysOpen = 1
|
||||
var arraysClosed = 0
|
||||
// var stringOpen byte = 0
|
||||
for j := dec.cursor; j < dec.length || dec.read(); j++ {
|
||||
switch dec.data[j] {
|
||||
case ']':
|
||||
arraysClosed++
|
||||
// everything is closed return
|
||||
if arraysOpen == arraysClosed {
|
||||
// add char to object data
|
||||
return j + 1, nil
|
||||
}
|
||||
case '[':
|
||||
arraysOpen++
|
||||
case '"':
|
||||
j++
|
||||
var isInEscapeSeq bool
|
||||
var isFirstQuote = true
|
||||
for ; j < dec.length || dec.read(); j++ {
|
||||
if dec.data[j] != '"' {
|
||||
continue
|
||||
}
|
||||
if dec.data[j-1] != '\\' || (!isInEscapeSeq && !isFirstQuote) {
|
||||
break
|
||||
} else {
|
||||
isInEscapeSeq = false
|
||||
}
|
||||
if isFirstQuote {
|
||||
isFirstQuote = false
|
||||
}
|
||||
// loop backward and count how many anti slash found
|
||||
// to see if string is effectively escaped
|
||||
ct := 0
|
||||
for i := j - 1; i > 0; i-- {
|
||||
if dec.data[i] != '\\' {
|
||||
break
|
||||
}
|
||||
ct++
|
||||
}
|
||||
// is pair number of slashes, quote is not escaped
|
||||
if ct&1 == 0 {
|
||||
break
|
||||
}
|
||||
isInEscapeSeq = true
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
|
||||
// DecodeArrayFunc is a func type implementing UnmarshalerJSONArray.
|
||||
// Use it to cast a `func(*Decoder) error` to Unmarshal an array on the fly.
|
||||
|
||||
type DecodeArrayFunc func(*Decoder) error
|
||||
|
||||
// UnmarshalJSONArray implements UnmarshalerJSONArray.
|
||||
func (f DecodeArrayFunc) UnmarshalJSONArray(dec *Decoder) error {
|
||||
return f(dec)
|
||||
}
|
||||
|
||||
// IsNil implements UnmarshalerJSONArray.
|
||||
func (f DecodeArrayFunc) IsNil() bool {
|
||||
return f == nil
|
||||
}
|
||||
|
||||
// Add Values functions
|
||||
|
||||
// AddArray decodes the JSON value within an object or an array to a UnmarshalerJSONArray.
|
||||
func (dec *Decoder) AddArray(v UnmarshalerJSONArray) error {
|
||||
return dec.Array(v)
|
||||
}
|
||||
|
||||
// AddArrayNull decodes the JSON value within an object or an array to a UnmarshalerJSONArray.
|
||||
func (dec *Decoder) AddArrayNull(v interface{}) error {
|
||||
return dec.ArrayNull(v)
|
||||
}
|
||||
|
||||
// Array decodes the JSON value within an object or an array to a UnmarshalerJSONArray.
|
||||
func (dec *Decoder) Array(v UnmarshalerJSONArray) error {
|
||||
newCursor, err := dec.decodeArray(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dec.cursor = newCursor
|
||||
dec.called |= 1
|
||||
return nil
|
||||
}
|
||||
|
||||
// ArrayNull decodes the JSON value within an object or an array to a UnmarshalerJSONArray.
|
||||
// v should be a pointer to an UnmarshalerJSONArray,
|
||||
// if `null` value is encountered in JSON, it will leave the value v untouched,
|
||||
// else it will create a new instance of the UnmarshalerJSONArray behind v.
|
||||
func (dec *Decoder) ArrayNull(v interface{}) error {
|
||||
newCursor, err := dec.decodeArrayNull(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dec.cursor = newCursor
|
||||
dec.called |= 1
|
||||
return nil
|
||||
}
|
||||
|
||||
// Index returns the index of an array being decoded.
|
||||
func (dec *Decoder) Index() int {
|
||||
return dec.arrayIndex
|
||||
}
|
||||
-241
@@ -1,241 +0,0 @@
|
||||
package gojay
|
||||
|
||||
// DecodeBool reads the next JSON-encoded value from the decoder's input (io.Reader)
|
||||
// and stores it in the boolean pointed to by v.
|
||||
//
|
||||
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
|
||||
func (dec *Decoder) DecodeBool(v *bool) error {
|
||||
if dec.isPooled == 1 {
|
||||
panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
|
||||
}
|
||||
return dec.decodeBool(v)
|
||||
}
|
||||
func (dec *Decoder) decodeBool(v *bool) error {
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch dec.data[dec.cursor] {
|
||||
case ' ', '\n', '\t', '\r', ',':
|
||||
continue
|
||||
case 't':
|
||||
dec.cursor++
|
||||
err := dec.assertTrue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = true
|
||||
return nil
|
||||
case 'f':
|
||||
dec.cursor++
|
||||
err := dec.assertFalse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = false
|
||||
return nil
|
||||
case 'n':
|
||||
dec.cursor++
|
||||
err := dec.assertNull()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = false
|
||||
return nil
|
||||
default:
|
||||
dec.err = dec.makeInvalidUnmarshalErr(v)
|
||||
err := dec.skipData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (dec *Decoder) decodeBoolNull(v **bool) error {
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch dec.data[dec.cursor] {
|
||||
case ' ', '\n', '\t', '\r', ',':
|
||||
continue
|
||||
case 't':
|
||||
dec.cursor++
|
||||
err := dec.assertTrue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *v == nil {
|
||||
*v = new(bool)
|
||||
}
|
||||
**v = true
|
||||
return nil
|
||||
case 'f':
|
||||
dec.cursor++
|
||||
err := dec.assertFalse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *v == nil {
|
||||
*v = new(bool)
|
||||
}
|
||||
**v = false
|
||||
return nil
|
||||
case 'n':
|
||||
dec.cursor++
|
||||
err := dec.assertNull()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
dec.err = dec.makeInvalidUnmarshalErr(v)
|
||||
err := dec.skipData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dec *Decoder) assertTrue() error {
|
||||
i := 0
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch i {
|
||||
case 0:
|
||||
if dec.data[dec.cursor] != 'r' {
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
case 1:
|
||||
if dec.data[dec.cursor] != 'u' {
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
case 2:
|
||||
if dec.data[dec.cursor] != 'e' {
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
case 3:
|
||||
switch dec.data[dec.cursor] {
|
||||
case ' ', '\b', '\t', '\n', ',', ']', '}':
|
||||
// dec.cursor--
|
||||
return nil
|
||||
default:
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i == 3 {
|
||||
return nil
|
||||
}
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
|
||||
func (dec *Decoder) assertNull() error {
|
||||
i := 0
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch i {
|
||||
case 0:
|
||||
if dec.data[dec.cursor] != 'u' {
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
case 1:
|
||||
if dec.data[dec.cursor] != 'l' {
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
case 2:
|
||||
if dec.data[dec.cursor] != 'l' {
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
case 3:
|
||||
switch dec.data[dec.cursor] {
|
||||
case ' ', '\t', '\n', ',', ']', '}':
|
||||
// dec.cursor--
|
||||
return nil
|
||||
default:
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i == 3 {
|
||||
return nil
|
||||
}
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
|
||||
func (dec *Decoder) assertFalse() error {
|
||||
i := 0
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch i {
|
||||
case 0:
|
||||
if dec.data[dec.cursor] != 'a' {
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
case 1:
|
||||
if dec.data[dec.cursor] != 'l' {
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
case 2:
|
||||
if dec.data[dec.cursor] != 's' {
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
case 3:
|
||||
if dec.data[dec.cursor] != 'e' {
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
case 4:
|
||||
switch dec.data[dec.cursor] {
|
||||
case ' ', '\t', '\n', ',', ']', '}':
|
||||
// dec.cursor--
|
||||
return nil
|
||||
default:
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i == 4 {
|
||||
return nil
|
||||
}
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
|
||||
// Add Values functions
|
||||
|
||||
// AddBool decodes the JSON value within an object or an array to a *bool.
|
||||
// If next key is neither null nor a JSON boolean, an InvalidUnmarshalError will be returned.
|
||||
// If next key is null, bool will be false.
|
||||
func (dec *Decoder) AddBool(v *bool) error {
|
||||
return dec.Bool(v)
|
||||
}
|
||||
|
||||
// AddBoolNull decodes the JSON value within an object or an array to a *bool.
|
||||
// If next key is neither null nor a JSON boolean, an InvalidUnmarshalError will be returned.
|
||||
// If next key is null, bool will be false.
|
||||
// If a `null` is encountered, gojay does not change the value of the pointer.
|
||||
func (dec *Decoder) AddBoolNull(v **bool) error {
|
||||
return dec.BoolNull(v)
|
||||
}
|
||||
|
||||
// Bool decodes the JSON value within an object or an array to a *bool.
|
||||
// If next key is neither null nor a JSON boolean, an InvalidUnmarshalError will be returned.
|
||||
// If next key is null, bool will be false.
|
||||
func (dec *Decoder) Bool(v *bool) error {
|
||||
err := dec.decodeBool(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dec.called |= 1
|
||||
return nil
|
||||
}
|
||||
|
||||
// BoolNull decodes the JSON value within an object or an array to a *bool.
|
||||
// If next key is neither null nor a JSON boolean, an InvalidUnmarshalError will be returned.
|
||||
// If next key is null, bool will be false.
|
||||
func (dec *Decoder) BoolNull(v **bool) error {
|
||||
err := dec.decodeBoolNull(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dec.called |= 1
|
||||
return nil
|
||||
}
|
||||
-85
@@ -1,85 +0,0 @@
|
||||
package gojay
|
||||
|
||||
// EmbeddedJSON is a raw encoded JSON value.
|
||||
// It can be used to delay JSON decoding or precompute a JSON encoding.
|
||||
type EmbeddedJSON []byte
|
||||
|
||||
func (dec *Decoder) decodeEmbeddedJSON(ej *EmbeddedJSON) error {
|
||||
var err error
|
||||
if ej == nil {
|
||||
return InvalidUnmarshalError("Invalid nil pointer given")
|
||||
}
|
||||
var beginOfEmbeddedJSON int
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch dec.data[dec.cursor] {
|
||||
case ' ', '\n', '\t', '\r', ',':
|
||||
continue
|
||||
// is null
|
||||
case 'n':
|
||||
beginOfEmbeddedJSON = dec.cursor
|
||||
dec.cursor++
|
||||
err := dec.assertNull()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case 't':
|
||||
beginOfEmbeddedJSON = dec.cursor
|
||||
dec.cursor++
|
||||
err := dec.assertTrue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// is false
|
||||
case 'f':
|
||||
beginOfEmbeddedJSON = dec.cursor
|
||||
dec.cursor++
|
||||
err := dec.assertFalse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// is an object
|
||||
case '{':
|
||||
beginOfEmbeddedJSON = dec.cursor
|
||||
dec.cursor = dec.cursor + 1
|
||||
dec.cursor, err = dec.skipObject()
|
||||
// is string
|
||||
case '"':
|
||||
beginOfEmbeddedJSON = dec.cursor
|
||||
dec.cursor = dec.cursor + 1
|
||||
err = dec.skipString() // why no new dec.cursor in result?
|
||||
// is array
|
||||
case '[':
|
||||
beginOfEmbeddedJSON = dec.cursor
|
||||
dec.cursor = dec.cursor + 1
|
||||
dec.cursor, err = dec.skipArray()
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-':
|
||||
beginOfEmbeddedJSON = dec.cursor
|
||||
dec.cursor, err = dec.skipNumber()
|
||||
}
|
||||
break
|
||||
}
|
||||
if err == nil {
|
||||
if dec.cursor-1 >= beginOfEmbeddedJSON {
|
||||
*ej = append(*ej, dec.data[beginOfEmbeddedJSON:dec.cursor]...)
|
||||
}
|
||||
dec.called |= 1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// AddEmbeddedJSON adds an EmbeddedsJSON to the value pointed by v.
|
||||
// It can be used to delay JSON decoding or precompute a JSON encoding.
|
||||
func (dec *Decoder) AddEmbeddedJSON(v *EmbeddedJSON) error {
|
||||
return dec.EmbeddedJSON(v)
|
||||
}
|
||||
|
||||
// EmbeddedJSON adds an EmbeddedsJSON to the value pointed by v.
|
||||
// It can be used to delay JSON decoding or precompute a JSON encoding.
|
||||
func (dec *Decoder) EmbeddedJSON(v *EmbeddedJSON) error {
|
||||
err := dec.decodeEmbeddedJSON(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dec.called |= 1
|
||||
return nil
|
||||
}
|
||||
-130
@@ -1,130 +0,0 @@
|
||||
package gojay
|
||||
|
||||
// TODO @afiune for now we are using the standard json unmarshaling but in
|
||||
// the future it would be great to implement one here inside this repo
|
||||
import "encoding/json"
|
||||
|
||||
// DecodeInterface reads the next JSON-encoded value from the decoder's input (io.Reader) and stores it in the value pointed to by i.
|
||||
//
|
||||
// i must be an interface poiter
|
||||
func (dec *Decoder) DecodeInterface(i *interface{}) error {
|
||||
if dec.isPooled == 1 {
|
||||
panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
|
||||
}
|
||||
err := dec.decodeInterface(i)
|
||||
return err
|
||||
}
|
||||
|
||||
func (dec *Decoder) decodeInterface(i *interface{}) error {
|
||||
start, end, err := dec.getObject()
|
||||
if err != nil {
|
||||
dec.cursor = start
|
||||
return err
|
||||
}
|
||||
|
||||
// if start & end are equal the object is a null, don't unmarshal
|
||||
if start == end {
|
||||
return nil
|
||||
}
|
||||
|
||||
object := dec.data[start:end]
|
||||
if err = json.Unmarshal(object, i); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dec.cursor = end
|
||||
return nil
|
||||
}
|
||||
|
||||
// @afiune Maybe return the type as well?
|
||||
func (dec *Decoder) getObject() (start int, end int, err error) {
|
||||
// start cursor
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch dec.data[dec.cursor] {
|
||||
case ' ', '\n', '\t', '\r', ',':
|
||||
continue
|
||||
// is null
|
||||
case 'n':
|
||||
dec.cursor++
|
||||
err = dec.assertNull()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Set start & end to the same cursor to indicate the object
|
||||
// is a null and should not be unmarshal
|
||||
start = dec.cursor
|
||||
end = dec.cursor
|
||||
return
|
||||
case 't':
|
||||
start = dec.cursor
|
||||
dec.cursor++
|
||||
err = dec.assertTrue()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
end = dec.cursor
|
||||
dec.cursor++
|
||||
return
|
||||
// is false
|
||||
case 'f':
|
||||
start = dec.cursor
|
||||
dec.cursor++
|
||||
err = dec.assertFalse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
end = dec.cursor
|
||||
dec.cursor++
|
||||
return
|
||||
// is an object
|
||||
case '{':
|
||||
start = dec.cursor
|
||||
dec.cursor++
|
||||
end, err = dec.skipObject()
|
||||
dec.cursor = end
|
||||
return
|
||||
// is string
|
||||
case '"':
|
||||
start = dec.cursor
|
||||
dec.cursor++
|
||||
start, end, err = dec.getString()
|
||||
start--
|
||||
dec.cursor = end
|
||||
return
|
||||
// is array
|
||||
case '[':
|
||||
start = dec.cursor
|
||||
dec.cursor++
|
||||
end, err = dec.skipArray()
|
||||
dec.cursor = end
|
||||
return
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-':
|
||||
start = dec.cursor
|
||||
end, err = dec.skipNumber()
|
||||
dec.cursor = end
|
||||
return
|
||||
default:
|
||||
err = dec.raiseInvalidJSONErr(dec.cursor)
|
||||
return
|
||||
}
|
||||
}
|
||||
err = dec.raiseInvalidJSONErr(dec.cursor)
|
||||
return
|
||||
}
|
||||
|
||||
// Add Values functions
|
||||
|
||||
// AddInterface decodes the JSON value within an object or an array to a interface{}.
|
||||
func (dec *Decoder) AddInterface(v *interface{}) error {
|
||||
return dec.Interface(v)
|
||||
}
|
||||
|
||||
// Interface decodes the JSON value within an object or an array to an interface{}.
|
||||
func (dec *Decoder) Interface(value *interface{}) error {
|
||||
err := dec.decodeInterface(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dec.called |= 1
|
||||
return nil
|
||||
}
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
package gojay
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
var digits []int8
|
||||
|
||||
const maxInt64toMultiply = math.MaxInt64 / 10
|
||||
const maxInt32toMultiply = math.MaxInt32 / 10
|
||||
const maxInt16toMultiply = math.MaxInt16 / 10
|
||||
const maxInt8toMultiply = math.MaxInt8 / 10
|
||||
const maxUint8toMultiply = math.MaxUint8 / 10
|
||||
const maxUint16toMultiply = math.MaxUint16 / 10
|
||||
const maxUint32toMultiply = math.MaxUint32 / 10
|
||||
const maxUint64toMultiply = math.MaxUint64 / 10
|
||||
const maxUint32Length = 10
|
||||
const maxUint64Length = 20
|
||||
const maxUint16Length = 5
|
||||
const maxUint8Length = 3
|
||||
const maxInt32Length = 10
|
||||
const maxInt64Length = 19
|
||||
const maxInt16Length = 5
|
||||
const maxInt8Length = 3
|
||||
const invalidNumber = int8(-1)
|
||||
|
||||
var pow10uint64 = [21]uint64{
|
||||
0,
|
||||
1,
|
||||
10,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
10000000,
|
||||
100000000,
|
||||
1000000000,
|
||||
10000000000,
|
||||
100000000000,
|
||||
1000000000000,
|
||||
10000000000000,
|
||||
100000000000000,
|
||||
1000000000000000,
|
||||
10000000000000000,
|
||||
100000000000000000,
|
||||
1000000000000000000,
|
||||
10000000000000000000,
|
||||
}
|
||||
|
||||
var skipNumberEndCursorIncrement [256]int
|
||||
|
||||
func init() {
|
||||
digits = make([]int8, 256)
|
||||
for i := 0; i < len(digits); i++ {
|
||||
digits[i] = invalidNumber
|
||||
}
|
||||
for i := int8('0'); i <= int8('9'); i++ {
|
||||
digits[i] = i - int8('0')
|
||||
}
|
||||
|
||||
for i := 0; i < 256; i++ {
|
||||
switch i {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e', 'E', '+', '-':
|
||||
skipNumberEndCursorIncrement[i] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *Decoder) skipNumber() (int, error) {
|
||||
end := dec.cursor + 1
|
||||
// look for following numbers
|
||||
for j := dec.cursor + 1; j < dec.length || dec.read(); j++ {
|
||||
end += skipNumberEndCursorIncrement[dec.data[j]]
|
||||
|
||||
switch dec.data[j] {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e', 'E', '+', '-', ' ', '\n', '\t', '\r':
|
||||
continue
|
||||
case ',', '}', ']':
|
||||
return end, nil
|
||||
default:
|
||||
// invalid json we expect numbers, dot (single one), comma, or spaces
|
||||
return end, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
}
|
||||
|
||||
return end, nil
|
||||
}
|
||||
|
||||
func (dec *Decoder) getExponent() (int64, error) {
|
||||
start := dec.cursor
|
||||
end := dec.cursor
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch dec.data[dec.cursor] { // is positive
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
end = dec.cursor + 1
|
||||
case '-':
|
||||
dec.cursor++
|
||||
exp, err := dec.getExponent()
|
||||
return -exp, err
|
||||
case '+':
|
||||
dec.cursor++
|
||||
return dec.getExponent()
|
||||
default:
|
||||
// if nothing return 0
|
||||
// could raise error
|
||||
if start == end {
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
return dec.atoi64(start, end-1), nil
|
||||
}
|
||||
}
|
||||
if start == end {
|
||||
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
return dec.atoi64(start, end-1), nil
|
||||
}
|
||||
-516
@@ -1,516 +0,0 @@
|
||||
package gojay
|
||||
|
||||
// DecodeFloat64 reads the next JSON-encoded value from the decoder's input (io.Reader) and stores it in the float64 pointed to by v.
|
||||
//
|
||||
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
|
||||
func (dec *Decoder) DecodeFloat64(v *float64) error {
|
||||
if dec.isPooled == 1 {
|
||||
panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
|
||||
}
|
||||
return dec.decodeFloat64(v)
|
||||
}
|
||||
func (dec *Decoder) decodeFloat64(v *float64) error {
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch c := dec.data[dec.cursor]; c {
|
||||
case ' ', '\n', '\t', '\r', ',':
|
||||
continue
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
val, err := dec.getFloat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = val
|
||||
return nil
|
||||
case '-':
|
||||
dec.cursor = dec.cursor + 1
|
||||
val, err := dec.getFloatNegative()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = -val
|
||||
return nil
|
||||
case 'n':
|
||||
dec.cursor++
|
||||
err := dec.assertNull()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
dec.err = dec.makeInvalidUnmarshalErr(v)
|
||||
err := dec.skipData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
func (dec *Decoder) decodeFloat64Null(v **float64) error {
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch c := dec.data[dec.cursor]; c {
|
||||
case ' ', '\n', '\t', '\r', ',':
|
||||
continue
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
val, err := dec.getFloat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *v == nil {
|
||||
*v = new(float64)
|
||||
}
|
||||
**v = val
|
||||
return nil
|
||||
case '-':
|
||||
dec.cursor = dec.cursor + 1
|
||||
val, err := dec.getFloatNegative()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *v == nil {
|
||||
*v = new(float64)
|
||||
}
|
||||
**v = -val
|
||||
return nil
|
||||
case 'n':
|
||||
dec.cursor++
|
||||
err := dec.assertNull()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
dec.err = dec.makeInvalidUnmarshalErr(v)
|
||||
err := dec.skipData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
|
||||
func (dec *Decoder) getFloatNegative() (float64, error) {
|
||||
// look for following numbers
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch dec.data[dec.cursor] {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
return dec.getFloat()
|
||||
default:
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
}
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
|
||||
func (dec *Decoder) getFloat() (float64, error) {
|
||||
var end = dec.cursor
|
||||
var start = dec.cursor
|
||||
// look for following numbers
|
||||
for j := dec.cursor + 1; j < dec.length || dec.read(); j++ {
|
||||
switch dec.data[j] {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
end = j
|
||||
continue
|
||||
case '.':
|
||||
// we get part before decimal as integer
|
||||
beforeDecimal := dec.atoi64(start, end)
|
||||
// then we get part after decimal as integer
|
||||
start = j + 1
|
||||
// get number after the decimal point
|
||||
for i := j + 1; i < dec.length || dec.read(); i++ {
|
||||
c := dec.data[i]
|
||||
if isDigit(c) {
|
||||
end = i
|
||||
// multiply the before decimal point portion by 10 using bitwise
|
||||
// make sure it doesn't overflow
|
||||
if end-start < 18 {
|
||||
beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1)
|
||||
}
|
||||
continue
|
||||
} else if (c == 'e' || c == 'E') && j < i-1 {
|
||||
// we have an exponent, convert first the value we got before the exponent
|
||||
var afterDecimal int64
|
||||
expI := end - start + 2
|
||||
// if exp is too long, it means number is too long, just truncate the number
|
||||
if expI >= len(pow10uint64) || expI < 0 {
|
||||
expI = len(pow10uint64) - 2
|
||||
afterDecimal = dec.atoi64(start, start+expI-2)
|
||||
} else {
|
||||
// then we add both integers
|
||||
// then we divide the number by the power found
|
||||
afterDecimal = dec.atoi64(start, end)
|
||||
}
|
||||
dec.cursor = i + 1
|
||||
pow := pow10uint64[expI]
|
||||
floatVal := float64(beforeDecimal+afterDecimal) / float64(pow)
|
||||
exp, err := dec.getExponent()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
pExp := (exp + (exp >> 31)) ^ (exp >> 31) + 1 // absolute exponent
|
||||
if pExp >= int64(len(pow10uint64)) || pExp < 0 {
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
// if exponent is negative
|
||||
if exp < 0 {
|
||||
return float64(floatVal) * (1 / float64(pow10uint64[pExp])), nil
|
||||
}
|
||||
return float64(floatVal) * float64(pow10uint64[pExp]), nil
|
||||
}
|
||||
dec.cursor = i
|
||||
break
|
||||
}
|
||||
if end >= dec.length || end < start {
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
var afterDecimal int64
|
||||
expI := end - start + 2
|
||||
// if exp is too long, it means number is too long, just truncate the number
|
||||
if expI >= len(pow10uint64) || expI < 0 {
|
||||
expI = 19
|
||||
afterDecimal = dec.atoi64(start, start+expI-2)
|
||||
} else {
|
||||
afterDecimal = dec.atoi64(start, end)
|
||||
}
|
||||
|
||||
pow := pow10uint64[expI]
|
||||
// then we add both integers
|
||||
// then we divide the number by the power found
|
||||
return float64(beforeDecimal+afterDecimal) / float64(pow), nil
|
||||
case 'e', 'E':
|
||||
dec.cursor = j + 1
|
||||
// we get part before decimal as integer
|
||||
beforeDecimal := uint64(dec.atoi64(start, end))
|
||||
// get exponent
|
||||
exp, err := dec.getExponent()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
pExp := (exp + (exp >> 31)) ^ (exp >> 31) + 1 // abs
|
||||
if pExp >= int64(len(pow10uint64)) || pExp < 0 {
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
// if exponent is negative
|
||||
if exp < 0 {
|
||||
return float64(beforeDecimal) * (1 / float64(pow10uint64[pExp])), nil
|
||||
}
|
||||
return float64(beforeDecimal) * float64(pow10uint64[pExp]), nil
|
||||
case ' ', '\n', '\t', '\r', ',', '}', ']': // does not have decimal
|
||||
dec.cursor = j
|
||||
return float64(dec.atoi64(start, end)), nil
|
||||
}
|
||||
// invalid json we expect numbers, dot (single one), comma, or spaces
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
return float64(dec.atoi64(start, end)), nil
|
||||
}
|
||||
|
||||
// DecodeFloat32 reads the next JSON-encoded value from the decoder's input (io.Reader) and stores it in the float32 pointed to by v.
|
||||
//
|
||||
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
|
||||
func (dec *Decoder) DecodeFloat32(v *float32) error {
|
||||
if dec.isPooled == 1 {
|
||||
panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
|
||||
}
|
||||
return dec.decodeFloat32(v)
|
||||
}
|
||||
func (dec *Decoder) decodeFloat32(v *float32) error {
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch c := dec.data[dec.cursor]; c {
|
||||
case ' ', '\n', '\t', '\r', ',':
|
||||
continue
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
val, err := dec.getFloat32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = val
|
||||
return nil
|
||||
case '-':
|
||||
dec.cursor = dec.cursor + 1
|
||||
val, err := dec.getFloat32Negative()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = -val
|
||||
return nil
|
||||
case 'n':
|
||||
dec.cursor++
|
||||
err := dec.assertNull()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
dec.err = dec.makeInvalidUnmarshalErr(v)
|
||||
err := dec.skipData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
func (dec *Decoder) decodeFloat32Null(v **float32) error {
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch c := dec.data[dec.cursor]; c {
|
||||
case ' ', '\n', '\t', '\r', ',':
|
||||
continue
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
val, err := dec.getFloat32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *v == nil {
|
||||
*v = new(float32)
|
||||
}
|
||||
**v = val
|
||||
return nil
|
||||
case '-':
|
||||
dec.cursor = dec.cursor + 1
|
||||
val, err := dec.getFloat32Negative()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *v == nil {
|
||||
*v = new(float32)
|
||||
}
|
||||
**v = -val
|
||||
return nil
|
||||
case 'n':
|
||||
dec.cursor++
|
||||
err := dec.assertNull()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
dec.err = dec.makeInvalidUnmarshalErr(v)
|
||||
err := dec.skipData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
|
||||
func (dec *Decoder) getFloat32Negative() (float32, error) {
|
||||
// look for following numbers
|
||||
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
|
||||
switch dec.data[dec.cursor] {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
return dec.getFloat32()
|
||||
default:
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
}
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
|
||||
func (dec *Decoder) getFloat32() (float32, error) {
|
||||
var end = dec.cursor
|
||||
var start = dec.cursor
|
||||
// look for following numbers
|
||||
for j := dec.cursor + 1; j < dec.length || dec.read(); j++ {
|
||||
switch dec.data[j] {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
end = j
|
||||
continue
|
||||
case '.':
|
||||
// we get part before decimal as integer
|
||||
beforeDecimal := dec.atoi64(start, end)
|
||||
// then we get part after decimal as integer
|
||||
start = j + 1
|
||||
// get number after the decimal point
|
||||
// multiple the before decimal point portion by 10 using bitwise
|
||||
for i := j + 1; i < dec.length || dec.read(); i++ {
|
||||
c := dec.data[i]
|
||||
if isDigit(c) {
|
||||
end = i
|
||||
// multiply the before decimal point portion by 10 using bitwise
|
||||
// make sure it desn't overflow
|
||||
if end-start < 9 {
|
||||
beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1)
|
||||
}
|
||||
continue
|
||||
} else if (c == 'e' || c == 'E') && j < i-1 {
|
||||
// we get the number before decimal
|
||||
var afterDecimal int64
|
||||
expI := end - start + 2
|
||||
// if exp is too long, it means number is too long, just truncate the number
|
||||
if expI >= 12 || expI < 0 {
|
||||
expI = 10
|
||||
afterDecimal = dec.atoi64(start, start+expI-2)
|
||||
} else {
|
||||
afterDecimal = dec.atoi64(start, end)
|
||||
}
|
||||
dec.cursor = i + 1
|
||||
pow := pow10uint64[expI]
|
||||
// then we add both integers
|
||||
// then we divide the number by the power found
|
||||
floatVal := float32(beforeDecimal+afterDecimal) / float32(pow)
|
||||
exp, err := dec.getExponent()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
pExp := (exp + (exp >> 31)) ^ (exp >> 31) + 1 // abs
|
||||
if pExp >= int64(len(pow10uint64)) || pExp < 0 {
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
// if exponent is negative
|
||||
if exp < 0 {
|
||||
return float32(floatVal) * (1 / float32(pow10uint64[pExp])), nil
|
||||
}
|
||||
return float32(floatVal) * float32(pow10uint64[pExp]), nil
|
||||
}
|
||||
dec.cursor = i
|
||||
break
|
||||
}
|
||||
if end >= dec.length || end < start {
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
// then we add both integers
|
||||
// then we divide the number by the power found
|
||||
var afterDecimal int64
|
||||
expI := end - start + 2
|
||||
// if exp is too long, it means number is too long, just truncate the number
|
||||
if expI >= 12 || expI < 0 {
|
||||
expI = 10
|
||||
afterDecimal = dec.atoi64(start, start+expI-2)
|
||||
} else {
|
||||
// then we add both integers
|
||||
// then we divide the number by the power found
|
||||
afterDecimal = dec.atoi64(start, end)
|
||||
}
|
||||
pow := pow10uint64[expI]
|
||||
return float32(beforeDecimal+afterDecimal) / float32(pow), nil
|
||||
case 'e', 'E':
|
||||
dec.cursor = j + 1
|
||||
// we get part before decimal as integer
|
||||
beforeDecimal := dec.atoi64(start, end)
|
||||
// get exponent
|
||||
exp, err := dec.getExponent()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
pExp := (exp + (exp >> 31)) ^ (exp >> 31) + 1
|
||||
if pExp >= int64(len(pow10uint64)) || pExp < 0 {
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
// if exponent is negative
|
||||
if exp < 0 {
|
||||
return float32(beforeDecimal) * (1 / float32(pow10uint64[pExp])), nil
|
||||
}
|
||||
return float32(beforeDecimal) * float32(pow10uint64[pExp]), nil
|
||||
case ' ', '\n', '\t', '\r', ',', '}', ']': // does not have decimal
|
||||
dec.cursor = j
|
||||
return float32(dec.atoi64(start, end)), nil
|
||||
}
|
||||
// invalid json we expect numbers, dot (single one), comma, or spaces
|
||||
return 0, dec.raiseInvalidJSONErr(dec.cursor)
|
||||
}
|
||||
return float32(dec.atoi64(start, end)), nil
|
||||
}
|
||||
|
||||
// Add Values functions
|
||||
|
||||
// AddFloat decodes the JSON value within an object or an array to a *float64.
|
||||
// If next key value overflows float64, an InvalidUnmarshalError error will be returned.
|
||||
func (dec *Decoder) AddFloat(v *float64) error {
|
||||
return dec.Float64(v)
|
||||
}
|
||||
|
||||
// AddFloatNull decodes the JSON value within an object or an array to a *float64.
|
||||
// If next key value overflows float64, an InvalidUnmarshalError error will be returned.
|
||||
// If a `null` is encountered, gojay does not change the value of the pointer.
|
||||
func (dec *Decoder) AddFloatNull(v **float64) error {
|
||||
return dec.Float64Null(v)
|
||||
}
|
||||
|
||||
// AddFloat64 decodes the JSON value within an object or an array to a *float64.
|
||||
// If next key value overflows float64, an InvalidUnmarshalError error will be returned.
|
||||
func (dec *Decoder) AddFloat64(v *float64) error {
|
||||
return dec.Float64(v)
|
||||
}
|
||||
|
||||
// AddFloat64Null decodes the JSON value within an object or an array to a *float64.
|
||||
// If next key value overflows float64, an InvalidUnmarshalError error will be returned.
|
||||
// If a `null` is encountered, gojay does not change the value of the pointer.
|
||||
func (dec *Decoder) AddFloat64Null(v **float64) error {
|
||||
return dec.Float64Null(v)
|
||||
}
|
||||
|
||||
// AddFloat32 decodes the JSON value within an object or an array to a *float64.
|
||||
// If next key value overflows float64, an InvalidUnmarshalError error will be returned.
|
||||
func (dec *Decoder) AddFloat32(v *float32) error {
|
||||
return dec.Float32(v)
|
||||
}
|
||||
|
||||
// AddFloat32Null decodes the JSON value within an object or an array to a *float64.
|
||||
// If next key value overflows float64, an InvalidUnmarshalError error will be returned.
|
||||
// If a `null` is encountered, gojay does not change the value of the pointer.
|
||||
func (dec *Decoder) AddFloat32Null(v **float32) error {
|
||||
return dec.Float32Null(v)
|
||||
}
|
||||
|
||||
// Float decodes the JSON value within an object or an array to a *float64.
|
||||
// If next key value overflows float64, an InvalidUnmarshalError error will be returned.
|
||||
func (dec *Decoder) Float(v *float64) error {
|
||||
return dec.Float64(v)
|
||||
}
|
||||
|
||||
// FloatNull decodes the JSON value within an object or an array to a *float64.
|
||||
// If next key value overflows float64, an InvalidUnmarshalError error will be returned.
|
||||
func (dec *Decoder) FloatNull(v **float64) error {
|
||||
return dec.Float64Null(v)
|
||||
}
|
||||
|
||||
// Float64 decodes the JSON value within an object or an array to a *float64.
|
||||
// If next key value overflows float64, an InvalidUnmarshalError error will be returned.
|
||||
func (dec *Decoder) Float64(v *float64) error {
|
||||
err := dec.decodeFloat64(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dec.called |= 1
|
||||
return nil
|
||||
}
|
||||
|
||||
// Float64Null decodes the JSON value within an object or an array to a *float64.
|
||||
// If next key value overflows float64, an InvalidUnmarshalError error will be returned.
|
||||
func (dec *Decoder) Float64Null(v **float64) error {
|
||||
err := dec.decodeFloat64Null(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dec.called |= 1
|
||||
return nil
|
||||
}
|
||||
|
||||
// Float32 decodes the JSON value within an object or an array to a *float64.
|
||||
// If next key value overflows float64, an InvalidUnmarshalError error will be returned.
|
||||
func (dec *Decoder) Float32(v *float32) error {
|
||||
err := dec.decodeFloat32(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dec.called |= 1
|
||||
return nil
|
||||
}
|
||||
|
||||
// Float32Null decodes the JSON value within an object or an array to a *float64.
|
||||
// If next key value overflows float64, an InvalidUnmarshalError error will be returned.
|
||||
func (dec *Decoder) Float32Null(v **float32) error {
|
||||
err := dec.decodeFloat32Null(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dec.called |= 1
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user