Create ga-proxy
This commit is contained in:
parent
5eb506adbd
commit
8ba6b097b0
|
@ -0,0 +1,42 @@
|
||||||
|
name: "ga-proxy docker build"
|
||||||
|
|
||||||
|
env:
|
||||||
|
PROJECT: ga-proxy
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Set tag
|
||||||
|
id: tag
|
||||||
|
run: |
|
||||||
|
TAG=$(cat ${{ env.PROJECT }}/Dockerfile | awk 'NR==4 {print $3}')
|
||||||
|
echo "::set-env name=TAG::$TAG"
|
||||||
|
- name: Docker Hub login
|
||||||
|
env:
|
||||||
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: crazy-max/ghaction-docker-buildx@v1
|
||||||
|
with:
|
||||||
|
buildx-version: latest
|
||||||
|
- name: Build Dockerfile
|
||||||
|
env:
|
||||||
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
run: |
|
||||||
|
docker buildx build \
|
||||||
|
--platform=linux/amd64,linux/arm64 \
|
||||||
|
--output "type=image,push=true" \
|
||||||
|
--file ${{ env.PROJECT }}/Dockerfile ./${{ env.PROJECT }} \
|
||||||
|
--tag $(echo "${DOCKER_USERNAME}" | tr '[:upper:]' '[:lower:]')/${{ env.PROJECT }}:latest \
|
||||||
|
--tag $(echo "${DOCKER_USERNAME}" | tr '[:upper:]' '[:lower:]')/${{ env.PROJECT }}:${TAG}
|
|
@ -0,0 +1,5 @@
|
||||||
|
node_module/
|
||||||
|
js/
|
||||||
|
package.json
|
||||||
|
|
||||||
|
*.log
|
|
@ -0,0 +1,17 @@
|
||||||
|
# build dir
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||||
|
.glide/
|
|
@ -0,0 +1,25 @@
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- language: go
|
||||||
|
go:
|
||||||
|
- 1.11.x
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
|
script: make
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $GOPATH/pkg/mod
|
||||||
|
- $HOME/.cache/go-build
|
||||||
|
- language: node_js
|
||||||
|
node_js:
|
||||||
|
- "10"
|
||||||
|
before_install: cd packages/ga
|
||||||
|
install: yarn install
|
||||||
|
script:
|
||||||
|
- yarn test
|
||||||
|
- yarn build
|
||||||
|
- yarn size
|
||||||
|
cache:
|
||||||
|
yarn: true
|
||||||
|
directories:
|
||||||
|
- packages/ga/node_modules
|
|
@ -0,0 +1,32 @@
|
||||||
|
FROM golang:1.11-alpine AS BUILD
|
||||||
|
|
||||||
|
|
||||||
|
ENV VERSION 1.2.0
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# module
|
||||||
|
RUN apk --no-cache add git ca-certificates tzdata && update-ca-certificates
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# build
|
||||||
|
COPY ga ga
|
||||||
|
COPY server server
|
||||||
|
COPY main.go ./
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-w -s" -o ga-proxy
|
||||||
|
|
||||||
|
FROM alpine:3.8
|
||||||
|
LABEL maintainer "giuem <giuemcom+docker@gmail.com>"
|
||||||
|
EXPOSE 80
|
||||||
|
ENV IP=0.0.0.0
|
||||||
|
ENV PORT=80
|
||||||
|
ENV GIN_MODE=release
|
||||||
|
|
||||||
|
COPY --from=BUILD /usr/share/zoneinfo /usr/share/zoneinfo
|
||||||
|
COPY --from=BUILD /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
COPY --from=BUILD /src/ga-proxy /ga-proxy
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=3m --timeout=10s --start-period=2s --retries=3 \
|
||||||
|
CMD /ga-proxy ping
|
||||||
|
|
||||||
|
CMD ["/ga-proxy"]
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Giuem
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,20 @@
|
||||||
|
.PHONY: build clean test
|
||||||
|
|
||||||
|
GOCMD=go
|
||||||
|
GOBUILD=$(GOCMD) build
|
||||||
|
GOCLEAN=$(GOCMD) clean
|
||||||
|
GOTEST=$(GOCMD) test
|
||||||
|
BINARY_NAME=ga-proxy
|
||||||
|
OUTPUT_DIR=build
|
||||||
|
|
||||||
|
all: test build
|
||||||
|
|
||||||
|
build:
|
||||||
|
$(GOBUILD) -o $(OUTPUT_DIR)/$(BINARY_NAME) -v
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(GOCLEAN)
|
||||||
|
rm -rf $(OUTPUT_DIR)
|
||||||
|
|
||||||
|
test:
|
||||||
|
$(GOTEST) -v ./...
|
|
@ -0,0 +1,56 @@
|
||||||
|
# ga-proxy
|
||||||
|
|
||||||
|
[![Travis Status](https://img.shields.io/travis/com/giuem/ga-proxy.svg?style=flat-square)](https://travis-ci.com/giuem/ga-proxy)
|
||||||
|
[![Docker Build Status](https://img.shields.io/docker/build/giuem/ga-proxy.svg?style=flat-square)](https://hub.docker.com/r/giuem/ga-proxy/)
|
||||||
|
[![GitHub release](https://img.shields.io/github/release/giuem/ga-proxy.svg?style=flat-square)](https://github.com/giuem/ga-proxy/releases/latest)
|
||||||
|
[![Size](https://img.badgesize.io/https://unpkg.com/@giuem/ga-proxy/dist/ga.min.js?compression=gzip&style=flat-square)](https://unpkg.com/@giuem/ga-proxy/dist/)
|
||||||
|
[![](https://data.jsdelivr.com/v1/package/npm/@giuem/ga-proxy/badge)](https://www.jsdelivr.com/package/npm/@giuem/ga-proxy)
|
||||||
|
|
||||||
|
Accelerate Google Analytics.
|
||||||
|
|
||||||
|
## Get Start
|
||||||
|
|
||||||
|
### Run via Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull giuem/ga-proxy
|
||||||
|
docker run -d -p <port>:80 --name <container_name> giuem/ga-proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run as you like
|
||||||
|
|
||||||
|
#### 1. Install
|
||||||
|
|
||||||
|
Download binary from [release](https://github.com/giuem/ga-proxy/releases) or build yourself.
|
||||||
|
|
||||||
|
#### 2. Run
|
||||||
|
|
||||||
|
```
|
||||||
|
GIN_MODE=release ./ga-proxy [arguments]
|
||||||
|
```
|
||||||
|
|
||||||
|
options:
|
||||||
|
|
||||||
|
```
|
||||||
|
--ip IP, -i IP IP to listen (default: "127.0.0.1") [$IP]
|
||||||
|
--port port, -p port port to listen (default: "9080") [$PORT]
|
||||||
|
```
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
./ga_proxy -i 0.0.0.0 -p 80
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Insert script to your website
|
||||||
|
|
||||||
|
``` html
|
||||||
|
<script>
|
||||||
|
// replace following variables to your own
|
||||||
|
window.ga_tid = "UA-XXXXX-Y";
|
||||||
|
window.ga_url = "https://ga.giuem.com";
|
||||||
|
</script>
|
||||||
|
<script src="https://unpkg.com/@giuem/ga-proxy/dist/ga.min.js" async></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: `ga.giuem.com` is my own service, it do not promise any SLA and may shutdown at some day. You'd better deploy your own server.
|
|
@ -0,0 +1,8 @@
|
||||||
|
version: "3.3"
|
||||||
|
services:
|
||||||
|
proxy:
|
||||||
|
image: giuem/ga-proxy
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 9080:80
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package ga
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const gaURL = "https://www.google-analytics.com/collect"
|
||||||
|
|
||||||
|
var httpClient = &http.Client{}
|
||||||
|
|
||||||
|
func send(qs string) error {
|
||||||
|
req, err := http.NewRequest(http.MethodPost, gaURL, strings.NewReader(qs))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not create request")
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://golang.org/pkg/net/http/#Client.Do
|
||||||
|
// On error, any Response can be ignored. A non-nil Response with a non-nil error only occurs when
|
||||||
|
// CheckRedirect fails, and even then the returned Response.Body is already closed.
|
||||||
|
resp, err := httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not make request")
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func concatURLValues(v1 url.Values, v2 url.Values) {
|
||||||
|
for key, values := range v2 {
|
||||||
|
if len(v2.Get(key)) != 0 && len(v1.Get(key)) == 0 {
|
||||||
|
// do not replace existed key
|
||||||
|
v1[key] = values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package ga
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConcatURLValues(t *testing.T) {
|
||||||
|
v1 := make(url.Values)
|
||||||
|
v2 := make(url.Values)
|
||||||
|
|
||||||
|
v1.Set("a", "1")
|
||||||
|
v1.Set("b", "2")
|
||||||
|
v2.Set("a", "2")
|
||||||
|
v2.Set("c", "3")
|
||||||
|
|
||||||
|
concatURLValues(v1, v2)
|
||||||
|
|
||||||
|
if v1.Encode() != "a=1&b=2&c=3" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if v2.Encode() != "a=2&c=3" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package ga
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PageView sends analysis data of t=pageview
|
||||||
|
func PageView(data CommonData) error {
|
||||||
|
data.HitType = "pageview"
|
||||||
|
|
||||||
|
v, err := query.Values(data)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not encode query")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = send(v.Encode())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timing sends analysis data of t=timing
|
||||||
|
func Timing(data CommonData, tData TimingData) error {
|
||||||
|
data.HitType = "timing"
|
||||||
|
|
||||||
|
v1, err := query.Values(data)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not encode query")
|
||||||
|
}
|
||||||
|
v2, err := query.Values(tData)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not encode query")
|
||||||
|
}
|
||||||
|
concatURLValues(v1, v2)
|
||||||
|
|
||||||
|
err = send(v1.Encode())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect tests network connection
|
||||||
|
func Detect() error {
|
||||||
|
err := send("")
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package ga
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestDetect(t *testing.T) {
|
||||||
|
err := Detect()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package ga
|
||||||
|
|
||||||
|
// CommonData includes all necessary data
|
||||||
|
type CommonData struct {
|
||||||
|
// general
|
||||||
|
Version int `url:"v"`
|
||||||
|
TrackingID string `url:"tid"`
|
||||||
|
|
||||||
|
// user
|
||||||
|
ClientID string `url:"cid"`
|
||||||
|
|
||||||
|
// t
|
||||||
|
HitType string `url:"t"`
|
||||||
|
|
||||||
|
// session
|
||||||
|
UserIP string `url:"uip"`
|
||||||
|
UserAgent string `url:"ua"`
|
||||||
|
|
||||||
|
// trafficsources
|
||||||
|
DocumentReferer string `url:"dr,omitempty"`
|
||||||
|
|
||||||
|
// system
|
||||||
|
ScreenResolution string `url:"sr,omitempty"`
|
||||||
|
ViewportSize string `url:"vp,omitempty"`
|
||||||
|
DocumentEncoding string `url:"de,omitempty"`
|
||||||
|
ScreenColors string `url:"sd,omitempty"`
|
||||||
|
UserLanguage string `url:"ul,omitempty"`
|
||||||
|
|
||||||
|
// content
|
||||||
|
DocumentLink string `url:"dl"`
|
||||||
|
DocumentTitle string `url:"dt,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimingData contains all fields of `HitType=timing`
|
||||||
|
type TimingData struct {
|
||||||
|
PageLoadedTime string `url:"plt,omitempty"`
|
||||||
|
DNSTime string `url:"dns,omitempty"`
|
||||||
|
PageDownloadedTime string `url:"pdt,omitempty"`
|
||||||
|
RedirectTime string `url:"rrt,omitempty"`
|
||||||
|
TCPTime string `url:"tcp,omitempty"`
|
||||||
|
ServerResponseTime string `url:"srt,omitempty"`
|
||||||
|
DomInteractiveTime string `url:"dit,omitempty"`
|
||||||
|
ContentLoadedTime string `url:"clt,omitempty"`
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
module github.com/giuem/ga-proxy
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74 // indirect
|
||||||
|
github.com/gin-gonic/gin v1.3.0
|
||||||
|
github.com/gofrs/uuid v3.1.0+incompatible
|
||||||
|
github.com/golang/protobuf v1.2.0 // indirect
|
||||||
|
github.com/google/go-querystring v1.0.0
|
||||||
|
github.com/json-iterator/go v1.1.5 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.4 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
|
github.com/pkg/errors v0.8.1
|
||||||
|
github.com/stretchr/testify v1.3.0 // indirect
|
||||||
|
github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 // indirect
|
||||||
|
github.com/urfave/cli v1.20.0
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,39 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74 h1:FaI7wNyesdMBSkIRVUuEEYEvmzufs7EqQvRAxfEXGbQ=
|
||||||
|
github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||||
|
github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs=
|
||||||
|
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
|
||||||
|
github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA=
|
||||||
|
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||||
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
|
github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
|
||||||
|
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||||
|
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/ugorji/go v1.1.2 h1:JON3E2/GPW2iDNGoSAusl1KDf5TRQ8k8q7Tp097pZGs=
|
||||||
|
github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||||
|
github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 h1:BasDe+IErOQKrMVXab7UayvSlIpiyGwRvuX3EKYY7UA=
|
||||||
|
github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA=
|
||||||
|
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||||
|
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
@ -0,0 +1,68 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/giuem/ga-proxy/server"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "ga-proxy"
|
||||||
|
app.HideVersion = true
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "ip, i",
|
||||||
|
Value: "127.0.0.1",
|
||||||
|
Usage: "`IP` to listen",
|
||||||
|
EnvVar: "IP",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "port, p",
|
||||||
|
Value: "9080",
|
||||||
|
Usage: "`port` to listen",
|
||||||
|
EnvVar: "PORT",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Action = func(c *cli.Context) error {
|
||||||
|
server.Run(c.String("ip"), c.String("port"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
cli.Command{
|
||||||
|
Name: "ping",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "ip, i",
|
||||||
|
Value: "127.0.0.1",
|
||||||
|
Usage: "server `IP`",
|
||||||
|
EnvVar: "IP",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "port, p",
|
||||||
|
Value: "9080",
|
||||||
|
Usage: "server `port`",
|
||||||
|
EnvVar: "PORT",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
resp, err := http.Get(fmt.Sprintf("http://%v:%v/ping", c.String("ip"), c.String("port")))
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return cli.NewExitError(fmt.Errorf("server returns non-200 status code"), 1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
js/dist
|
|
@ -0,0 +1,13 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: ["plugin:prettier/recommended"],
|
||||||
|
rules: {
|
||||||
|
"prettier/prettier": "error",
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es6: true
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: "module"
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,87 @@
|
||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/node
|
||||||
|
# Edit at https://www.gitignore.io/?templates=node
|
||||||
|
|
||||||
|
### Node ###
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/node
|
|
@ -0,0 +1,93 @@
|
||||||
|
(function(win, doc, navigator) {
|
||||||
|
var screen = win.screen;
|
||||||
|
var encode = encodeURIComponent;
|
||||||
|
var max = Math.max;
|
||||||
|
// const min = Math.min;
|
||||||
|
var performance = win.performance;
|
||||||
|
var timing = performance && performance.timing;
|
||||||
|
var navigation = performance && performance.navigation;
|
||||||
|
|
||||||
|
var pvData = {
|
||||||
|
dt: doc.title,
|
||||||
|
de: doc.characterSet || doc.charset,
|
||||||
|
dr: doc.referrer || void 0,
|
||||||
|
ul:
|
||||||
|
navigator.language ||
|
||||||
|
navigator.browserLanguage ||
|
||||||
|
navigator.userLanguage ||
|
||||||
|
void 0,
|
||||||
|
sd: screen.colorDepth + "-bit",
|
||||||
|
sr: screen.width + "x" + screen.height,
|
||||||
|
vp:
|
||||||
|
max(doc.documentElement.clientWidth, win.innerWidth || 0) +
|
||||||
|
"x" +
|
||||||
|
max(doc.documentElement.clientHeight, win.innerHeight || 0),
|
||||||
|
ga: win.ga_tid,
|
||||||
|
z: new Date().getTime()
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildQueryString(params) {
|
||||||
|
var qs = [];
|
||||||
|
for (var k in params) {
|
||||||
|
if (params.hasOwnProperty(k) && params[k] !== void 0) {
|
||||||
|
qs.push(encode(k) + "=" + encode(params[k]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return qs.join("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendViaImg(uri, params) {
|
||||||
|
var img = new Image();
|
||||||
|
// img.width = img.height = 1;
|
||||||
|
img.referrerPolicy = "unsafe-url";
|
||||||
|
img.src = uri + "?" + buildQueryString(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// function sendBeacon(uri, params) {
|
||||||
|
// if (!navigator.sendBeacon) return false;
|
||||||
|
// return navigator.sendBeacon(uri, params);
|
||||||
|
// }
|
||||||
|
|
||||||
|
function send(uri, params) {
|
||||||
|
uri = win.ga_url + uri;
|
||||||
|
// if (!sendBeacon(uri, params)) {
|
||||||
|
sendViaImg(uri, params);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendTiming() {
|
||||||
|
if (!timing) { return; }
|
||||||
|
var navigationStart = timing.navigationStart;
|
||||||
|
if (navigationStart == 0) { return; }
|
||||||
|
|
||||||
|
var filterNumber = function (num) { return isNaN(num) || num == Infinity || num < 0 ? void 0 : num; };
|
||||||
|
|
||||||
|
var perfData = {
|
||||||
|
plt: filterNumber(timing.loadEventStart - navigationStart),
|
||||||
|
dns: filterNumber(timing.domainLookupEnd - timing.domainLookupStart),
|
||||||
|
pdt: filterNumber(timing.responseEnd - timing.responseStart),
|
||||||
|
rrt: filterNumber(timing.redirectEnd - timing.redirectStart),
|
||||||
|
tcp: filterNumber(timing.connectEnd - timing.connectStart),
|
||||||
|
srt: filterNumber(timing.responseStart - timing.requestStart),
|
||||||
|
dit: filterNumber(timing.domInteractive - navigationStart),
|
||||||
|
clt: filterNumber(timing.domContentLoadedEventStart - navigationStart)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var key in pvData) {
|
||||||
|
perfData[key] = pvData[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
send("/t", perfData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!navigation || navigation.type != navigation.TYPE_RELOAD) {
|
||||||
|
// page view
|
||||||
|
send("/p", pvData);
|
||||||
|
// timing
|
||||||
|
if (document.readyState == "complete") {
|
||||||
|
sendTiming();
|
||||||
|
} else {
|
||||||
|
win.addEventListener("load", sendTiming);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(window, document, navigator);
|
|
@ -0,0 +1 @@
|
||||||
|
!function(n,t,e){var r=n.screen,a=encodeURIComponent,i=Math.max,o=n.performance,d=o&&o.timing,c=o&&o.navigation,u={dt:t.title,de:t.characterSet||t.charset,dr:t.referrer||void 0,ul:e.language||e.browserLanguage||e.userLanguage||void 0,sd:r.colorDepth+"-bit",sr:r.width+"x"+r.height,vp:i(t.documentElement.clientWidth,n.innerWidth||0)+"x"+i(t.documentElement.clientHeight,n.innerHeight||0),ga:n.ga_tid,z:(new Date).getTime()};function s(t,e){var n=new Image;n.referrerPolicy="unsafe-url",n.src=t+"?"+function r(t){var e=[];for(var n in t)t.hasOwnProperty(n)&&void 0!==t[n]&&e.push(a(n)+"="+a(t[n]));return e.join("&")}(e)}function v(t,e){s(t=n.ga_url+t,e)}function g(){if(d){var t=d.navigationStart;if(0!=t){var e=function(t){return isNaN(t)||t==Infinity||t<0?void 0:t},n={plt:e(d.loadEventStart-t),dns:e(d.domainLookupEnd-d.domainLookupStart),pdt:e(d.responseEnd-d.responseStart),rrt:e(d.redirectEnd-d.redirectStart),tcp:e(d.connectEnd-d.connectStart),srt:e(d.responseStart-d.requestStart),dit:e(d.domInteractive-t),clt:e(d.domContentLoadedEventStart-t)};for(var r in u)n[r]=u[r];v("/t",n)}}}c&&c.type==c.TYPE_RELOAD||(v("/p",u),"complete"==document.readyState?g():n.addEventListener("load",g))}(window,document,navigator);
|
|
@ -0,0 +1,21 @@
|
||||||
|
const gulp = require("gulp");
|
||||||
|
const buble = require("gulp-buble");
|
||||||
|
const uglify = require("gulp-uglify");
|
||||||
|
const rename = require("gulp-rename");
|
||||||
|
|
||||||
|
function build() {
|
||||||
|
return gulp
|
||||||
|
.src("./src/**/*.js")
|
||||||
|
.pipe(buble())
|
||||||
|
.pipe(gulp.dest("./dist"))
|
||||||
|
.pipe(
|
||||||
|
uglify({
|
||||||
|
mangle: true,
|
||||||
|
ie8: true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(rename({ suffix: ".min" }))
|
||||||
|
.pipe(gulp.dest("./dist"));
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.build = build;
|
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"name": "@giuem/ga-proxy",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"main": "dist/ga.min.js",
|
||||||
|
"repository": "git@github.com:giuem/ga-proxy.git",
|
||||||
|
"author": "giuem <giuemcom@gmail.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"build": "gulp build",
|
||||||
|
"test": "npm run lint",
|
||||||
|
"lint": "eslint ./src/**/*.js",
|
||||||
|
"size": "bundlesize"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"devDependencies": {
|
||||||
|
"bundlesize": "^0.17.1",
|
||||||
|
"eslint": "^5.13.0",
|
||||||
|
"eslint-config-prettier": "^4.0.0",
|
||||||
|
"eslint-plugin-prettier": "^3.0.1",
|
||||||
|
"gulp": "^4.0.0",
|
||||||
|
"gulp-buble": "^0.9.0",
|
||||||
|
"gulp-rename": "^1.4.0",
|
||||||
|
"gulp-uglify": "^3.0.1",
|
||||||
|
"prettier": "^1.16.4"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"gulp-buble/buble": "0.19.6"
|
||||||
|
},
|
||||||
|
"bundlesize": [
|
||||||
|
{
|
||||||
|
"path": "./dist/*.min.js",
|
||||||
|
"maxSize": "1kb"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
(function(win, doc, navigator) {
|
||||||
|
const screen = win.screen;
|
||||||
|
const encode = encodeURIComponent;
|
||||||
|
const max = Math.max;
|
||||||
|
// const min = Math.min;
|
||||||
|
const performance = win.performance;
|
||||||
|
const timing = performance && performance.timing;
|
||||||
|
const navigation = performance && performance.navigation;
|
||||||
|
|
||||||
|
const pvData = {
|
||||||
|
dt: doc.title,
|
||||||
|
de: doc.characterSet || doc.charset,
|
||||||
|
dr: doc.referrer || void 0,
|
||||||
|
ul:
|
||||||
|
navigator.language ||
|
||||||
|
navigator.browserLanguage ||
|
||||||
|
navigator.userLanguage ||
|
||||||
|
void 0,
|
||||||
|
sd: screen.colorDepth + "-bit",
|
||||||
|
sr: screen.width + "x" + screen.height,
|
||||||
|
vp:
|
||||||
|
max(doc.documentElement.clientWidth, win.innerWidth || 0) +
|
||||||
|
"x" +
|
||||||
|
max(doc.documentElement.clientHeight, win.innerHeight || 0),
|
||||||
|
ga: win.ga_tid,
|
||||||
|
z: new Date().getTime()
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildQueryString(params) {
|
||||||
|
const qs = [];
|
||||||
|
for (const k in params) {
|
||||||
|
if (params.hasOwnProperty(k) && params[k] !== void 0) {
|
||||||
|
qs.push(encode(k) + "=" + encode(params[k]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return qs.join("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendViaImg(uri, params) {
|
||||||
|
const img = new Image();
|
||||||
|
// img.width = img.height = 1;
|
||||||
|
img.referrerPolicy = "unsafe-url";
|
||||||
|
img.src = uri + "?" + buildQueryString(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// function sendBeacon(uri, params) {
|
||||||
|
// if (!navigator.sendBeacon) return false;
|
||||||
|
// return navigator.sendBeacon(uri, params);
|
||||||
|
// }
|
||||||
|
|
||||||
|
function send(uri, params) {
|
||||||
|
uri = win.ga_url + uri;
|
||||||
|
// if (!sendBeacon(uri, params)) {
|
||||||
|
sendViaImg(uri, params);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendTiming() {
|
||||||
|
if (!timing) return;
|
||||||
|
const navigationStart = timing.navigationStart;
|
||||||
|
if (navigationStart == 0) return;
|
||||||
|
|
||||||
|
const filterNumber = num =>
|
||||||
|
isNaN(num) || num == Infinity || num < 0 ? void 0 : num;
|
||||||
|
|
||||||
|
const perfData = {
|
||||||
|
plt: filterNumber(timing.loadEventStart - navigationStart),
|
||||||
|
dns: filterNumber(timing.domainLookupEnd - timing.domainLookupStart),
|
||||||
|
pdt: filterNumber(timing.responseEnd - timing.responseStart),
|
||||||
|
rrt: filterNumber(timing.redirectEnd - timing.redirectStart),
|
||||||
|
tcp: filterNumber(timing.connectEnd - timing.connectStart),
|
||||||
|
srt: filterNumber(timing.responseStart - timing.requestStart),
|
||||||
|
dit: filterNumber(timing.domInteractive - navigationStart),
|
||||||
|
clt: filterNumber(timing.domContentLoadedEventStart - navigationStart)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const key in pvData) {
|
||||||
|
perfData[key] = pvData[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
send("/t", perfData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!navigation || navigation.type != navigation.TYPE_RELOAD) {
|
||||||
|
// page view
|
||||||
|
send("/p", pvData);
|
||||||
|
// timing
|
||||||
|
if (document.readyState == "complete") {
|
||||||
|
sendTiming();
|
||||||
|
} else {
|
||||||
|
win.addEventListener("load", sendTiming);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(window, document, navigator);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,66 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/giuem/ga-proxy/ga"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getUUID(c *gin.Context) string {
|
||||||
|
uid, err := c.Cookie("uuid")
|
||||||
|
if err != nil { // cookie no found
|
||||||
|
uid = generateUUID(c.Request.UserAgent())
|
||||||
|
c.SetCookie("uuid", uid, 2147483647, "/", "", false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uid
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateUUID(name string) string {
|
||||||
|
ns, err := uuid.NewV4()
|
||||||
|
if err != nil {
|
||||||
|
// error fallback
|
||||||
|
unix32bits := uint32(time.Now().UTC().Unix())
|
||||||
|
nameBytes := md5.Sum([]byte(name))
|
||||||
|
|
||||||
|
return fmt.Sprintf("%x-%x-%x-%x-%x\n", unix32bits, nameBytes[0:2], nameBytes[2:4], nameBytes[4:6], nameBytes[6:12])
|
||||||
|
}
|
||||||
|
|
||||||
|
return uuid.NewV5(ns, name).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCommonData(c *gin.Context) ga.CommonData {
|
||||||
|
return ga.CommonData{
|
||||||
|
Version: 1,
|
||||||
|
TrackingID: c.Query("ga"),
|
||||||
|
ClientID: getUUID(c),
|
||||||
|
UserIP: c.ClientIP(),
|
||||||
|
UserAgent: c.Request.UserAgent(),
|
||||||
|
DocumentReferer: c.Query("dr"),
|
||||||
|
ScreenResolution: c.Query("sr"),
|
||||||
|
ViewportSize: c.Query("vp"),
|
||||||
|
DocumentEncoding: c.Query("de"),
|
||||||
|
ScreenColors: c.Query("sd"),
|
||||||
|
UserLanguage: c.Query("ul"),
|
||||||
|
DocumentLink: c.Request.Referer(),
|
||||||
|
DocumentTitle: c.Query("dt"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTimingData(c *gin.Context) ga.TimingData {
|
||||||
|
return ga.TimingData{
|
||||||
|
PageLoadedTime: c.Query("plt"),
|
||||||
|
DNSTime: c.Query("dns"),
|
||||||
|
PageDownloadedTime: c.Query("pdt"),
|
||||||
|
RedirectTime: c.Query("rrt"),
|
||||||
|
TCPTime: c.Query("tcp"),
|
||||||
|
ServerResponseTime: c.Query("srt"),
|
||||||
|
DomInteractiveTime: c.Query("dit"),
|
||||||
|
ContentLoadedTime: c.Query("clt"),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/giuem/ga-proxy/ga"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handlePageView(c *gin.Context) {
|
||||||
|
if len(c.Request.Referer()) == 0 || len(c.Query("ga")) == 0 {
|
||||||
|
handleRedirect(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
|
||||||
|
go ga.PageView(getCommonData(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTiming(c *gin.Context) {
|
||||||
|
if len(c.Request.Referer()) == 0 || len(c.Query("ga")) == 0 {
|
||||||
|
handleRedirect(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
|
||||||
|
go ga.Timing(getCommonData(c), getTimingData(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlePing(c *gin.Context) {
|
||||||
|
err := ga.Detect()
|
||||||
|
if err != nil {
|
||||||
|
if c.Request.Method == http.MethodHead {
|
||||||
|
c.Status(http.StatusBadGateway)
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusBadGateway, gin.H{"msg": err.Error()})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Request.Method == http.MethodHead {
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"msg": "ok"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRedirect(c *gin.Context) {
|
||||||
|
c.Redirect(http.StatusFound, "https://github.com/giuem/ga-proxy")
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run starts a HTTP server
|
||||||
|
func Run(ip, port string) {
|
||||||
|
addr := fmt.Sprintf("%v:%v", ip, port)
|
||||||
|
|
||||||
|
r := gin.New()
|
||||||
|
logger := gin.Logger()
|
||||||
|
r.Use(func(c *gin.Context) {
|
||||||
|
if c.Request.URL.Path == "/ping" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger(c)
|
||||||
|
})
|
||||||
|
|
||||||
|
r.NoRoute(handleRedirect)
|
||||||
|
// version < 1
|
||||||
|
r.GET("/", handlePageView)
|
||||||
|
// version >= 1
|
||||||
|
r.GET("/p", handlePageView)
|
||||||
|
r.GET("/t", handleTiming)
|
||||||
|
|
||||||
|
r.GET("/ping", handlePing)
|
||||||
|
r.HEAD("/ping", handlePing)
|
||||||
|
|
||||||
|
r.Run(addr)
|
||||||
|
}
|
Loading…
Reference in New Issue