added initial skeleton
This commit is contained in:
commit
b71f88cbf7
|
@ -0,0 +1,127 @@
|
|||
|
||||
# Created by https://www.gitignore.io/api/intellij,go,linux,osx,windows
|
||||
|
||||
### Intellij ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
## File-based project format:
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
|
||||
### Go ###
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
*.swp
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
|
||||
### OSX ###
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
|
||||
### Windows ###
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
build/
|
||||
bind/
|
||||
vendor/
|
||||
mecab/
|
||||
*.log
|
||||
rooibos
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "rooibos dev",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"remotePath": "",
|
||||
"port": 2345,
|
||||
"host": "127.0.0.1",
|
||||
"program": "${workspaceRoot}/rooibos",
|
||||
"env": {},
|
||||
"args": [
|
||||
"-C",
|
||||
"dev.yml"
|
||||
],
|
||||
"showLog": true
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
|
||||
|
||||
# This how we want to name the binary output
|
||||
BINARY=rooibos
|
||||
|
||||
# These are the values we want to pass for VERSION and BUILD
|
||||
# git tag 1.0.1
|
||||
# git commit -am "One more change after the tags"
|
||||
VERSION=`git describe --always`
|
||||
BUILD=`date +%FT%T%z`
|
||||
|
||||
# Setup the -ldflags option for go build here, interpolate the variable values
|
||||
LDFLAGS=-ldflags "-w -s -X amuz.es/gogs/infra/rooibos/util.version=${VERSION} -X amuz.es/gogs/infra/rooibos/util.buildDate=${BUILD} "
|
||||
CGO_ENABLED=0
|
||||
|
||||
|
||||
build:
|
||||
go build ${LDFLAGS} -tags=jsoniter -o ${BINARY} .
|
||||
strip -x ${BINARY}
|
||||
|
||||
setup:
|
||||
go get -u github.com/kardianos/govendor
|
||||
# go get -u github.com/gogo/protobuf/proto
|
||||
# go get -u github.com/gogo/protobuf/protoc-gen-gogo
|
||||
# go get -u github.com/gogo/protobuf/gogoproto
|
||||
# go get -u github.com/gogo/protobuf/protoc-gen-gofast
|
||||
# go get -u github.com/gogo/protobuf/protoc-gen-gogofaster
|
||||
# go get -u github.com/golang/protobuf/proto
|
||||
${GOPATH}/bin/govendor fetch -v +missing
|
||||
#libjpeg-turbo
|
||||
generate:
|
||||
go get -u github.com/gogo/protobuf/proto
|
||||
go get -u github.com/gogo/protobuf/protoc-gen-gogo
|
||||
go get -u github.com/gogo/protobuf/gogoproto
|
||||
go get -u github.com/gogo/protobuf/protoc-gen-gofast
|
||||
go get -u github.com/gogo/protobuf/protoc-gen-gogofaster
|
||||
go get -u github.com/golang/protobuf/proto
|
||||
protoc --gogofaster_out=. --proto_path=../../../../:. subsys/redis/app_token_data.proto
|
||||
#${GOPATH}/bin/ffjson subsys/http/iface/rest.go
|
||||
#${GOPATH}/bin/easyjson subsys/http/iface/rest.go
|
||||
|
||||
|
||||
strip:
|
||||
upx rooibos
|
||||
# Installs our project: copies binaries
|
||||
install:
|
||||
go install ${LDFLAGS}
|
||||
|
||||
# Cleans our project: deletes binaries
|
||||
clean:
|
||||
if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi
|
||||
|
||||
.PHONY: clean install
|
|
@ -0,0 +1,98 @@
|
|||
![alt tag](https://upload.wikimedia.org/wikipedia/commons/2/23/Golang.png)
|
||||
|
||||
[![Build Status](https://travis-ci.org/Massad/gin-boilerplate.svg?branch=master)](https://travis-ci.org/Massad/gin-boilerplate)
|
||||
[![Join the chat at https://gitter.im/Massad/gin-boilerplate](https://badges.gitter.im/Massad/gin-boilerplate.svg)](https://gitter.im/Massad/gin-boilerplate?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Welcome to **Golang Gin boilerplate**!
|
||||
|
||||
The fastest way to deploy a restful api's with [Gin Framework](https://gin-gonic.github.io/gin/) with a structured project that defaults to **PostgreSQL** database and **Redis** as the session storage.
|
||||
|
||||
## Configured with
|
||||
|
||||
* [go-gorp](github.com/go-gorp/gorp): Go Relational Persistence
|
||||
* [RedisStore](https://github.com/gin-gonic/contrib/tree/master/sessions): Gin middleware for session management with multi-backend support (currently cookie, Redis).
|
||||
* Built-in **CORS Middleware**
|
||||
* Feature **PostgreSQL 9.4** JSON queries
|
||||
* Unit test
|
||||
|
||||
### Installation
|
||||
|
||||
```
|
||||
$ go get github.com/Massad/gin-boilerplate
|
||||
```
|
||||
|
||||
```
|
||||
$ cd $GOPATH/src/github.com/Massad/gin-boilerplate
|
||||
```
|
||||
|
||||
```
|
||||
$ go get -t -v ./...
|
||||
```
|
||||
|
||||
> Sometimes you need to get this package manually
|
||||
```
|
||||
$ go get github.com/bmizerany/assert
|
||||
```
|
||||
|
||||
You will find the **database.sql** in `db/database.sql`
|
||||
|
||||
And you can import the postgres database using this command:
|
||||
```
|
||||
$ psql -U postgres -h localhost < ./db/database.sql
|
||||
```
|
||||
|
||||
## Running Your Application
|
||||
|
||||
```
|
||||
$ go run *.go
|
||||
```
|
||||
|
||||
## Building Your Application
|
||||
|
||||
```
|
||||
$ go build -v
|
||||
```
|
||||
|
||||
```
|
||||
$ ./gin-boilerplate
|
||||
```
|
||||
|
||||
## Testing Your Application
|
||||
|
||||
```
|
||||
$ go test -v ./tests/*
|
||||
```
|
||||
|
||||
|
||||
## Import Postman Collection (API's)
|
||||
You can import from this [link](https://www.getpostman.com/collections/ac0680f90961bafd5de7). If you don't have **Postman**, check this link [https://www.getpostman.com](https://www.getpostman.com/)
|
||||
|
||||
## Contribution
|
||||
|
||||
You are welcome to contribute to keep it up to date and always improving!
|
||||
|
||||
If you have any question or need help, drop a message at [https://gitter.im/Massad/gin-boilerplate](https://gitter.im/Massad/gin-boilerplate)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
(The MIT License)
|
||||
|
||||
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,36 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
|
||||
def report(c, lines):
|
||||
rtv = []
|
||||
for str in lines:
|
||||
str=re.sub("^[ \t]+",'', str)
|
||||
if str.find("Latency") == 0 or str.find("Requests/sec") == 0:
|
||||
token = re.split("[ \t]+", str)
|
||||
value = re.sub("[^0-9\.]", '', token[1])
|
||||
if re.match("[0-9\.]+us", token[1]):
|
||||
rtv.append(float(value)/1000)
|
||||
elif re.match("[0-9\.]+s", token[1]):
|
||||
rtv.append(float(value)*1000)
|
||||
else:
|
||||
rtv.append(float(value))
|
||||
print c,"\t", rtv[0],"\t",rtv[1]
|
||||
|
||||
def run(cmd):
|
||||
stdin, stdout, stderr = os.popen3(cmd)
|
||||
return stdout.readlines()
|
||||
|
||||
offset = 5
|
||||
maxConcurrency = offset * 61
|
||||
duration = 10
|
||||
for num in range(1, maxConcurrency/offset):
|
||||
concurrency = num * offset
|
||||
thread = 16 if concurrency > 16 else concurrency
|
||||
cmd = (
|
||||
'''wrk --timeout 7 -t %d -c %d -d %d http://localhost:8080/api/v1/media/admin/2/thumb'''
|
||||
% (thread, concurrency, duration))
|
||||
result =run(cmd)
|
||||
report(concurrency, result)
|
||||
time.sleep(5)
|
|
@ -0,0 +1,80 @@
|
|||
package enums
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type MemberStatus int8
|
||||
|
||||
const (
|
||||
MemberStatusInvalid MemberStatus = 0
|
||||
MemberStatusDisabled MemberStatus = 1
|
||||
MemberStatusLocked MemberStatus = 2
|
||||
MemberStatusEnabled MemberStatus = 99
|
||||
)
|
||||
|
||||
func MemberStatusNameOf(value string) MemberStatus {
|
||||
switch value {
|
||||
case "INVALID":
|
||||
return MemberStatusInvalid
|
||||
case "DISABLED":
|
||||
return MemberStatusDisabled
|
||||
case "LOCKED":
|
||||
return MemberStatusLocked
|
||||
case "ENABLED":
|
||||
return MemberStatusEnabled
|
||||
}
|
||||
return MemberStatusInvalid
|
||||
}
|
||||
|
||||
func (status MemberStatus) Name() string {
|
||||
switch status {
|
||||
case MemberStatusInvalid:
|
||||
return "INVALID"
|
||||
case MemberStatusDisabled:
|
||||
return "DISABLED"
|
||||
case MemberStatusLocked:
|
||||
return "LOCKED"
|
||||
case MemberStatusEnabled:
|
||||
return "ENABLED"
|
||||
}
|
||||
return "INVALID"
|
||||
}
|
||||
|
||||
func (status MemberStatus) String() string {
|
||||
switch status {
|
||||
case MemberStatusInvalid:
|
||||
return "알 수 없음"
|
||||
case MemberStatusDisabled:
|
||||
return "사용중지"
|
||||
case MemberStatusLocked:
|
||||
return "사용일시중지"
|
||||
case MemberStatusEnabled:
|
||||
return "사용중"
|
||||
}
|
||||
return "알 수 없음"
|
||||
}
|
||||
func (status *MemberStatus) UnmarshalJSON(from []byte) error {
|
||||
|
||||
quote := []byte("\"")
|
||||
quoteSize := len(quote)
|
||||
|
||||
if len(from) < quoteSize*2 {
|
||||
return errors.New("invalid quote notation")
|
||||
}
|
||||
|
||||
if !bytes.HasPrefix(from, quote) || !bytes.HasSuffix(from, quote) {
|
||||
return errors.New("invalid quote notation")
|
||||
}
|
||||
*status = MemberStatusNameOf(string(from[quoteSize:len(from)-quoteSize]))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (status MemberStatus) MarshalJSON() ([]byte, error) {
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteRune('"')
|
||||
buffer.WriteString(status.Name())
|
||||
buffer.WriteRune('"')
|
||||
return buffer.Bytes(), nil
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"amuz.es/gogs/infra/rooibos/subsys/http"
|
||||
"amuz.es/gogs/infra/rooibos/subsys/periodic"
|
||||
|
||||
"amuz.es/gogs/infra/rooibos/util"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
"sync"
|
||||
"context"
|
||||
)
|
||||
|
||||
var (
|
||||
app = kingpin.New(util.Config.Name(), "graceful API server").Author("tom.cat")
|
||||
verbose = app.Flag("verbose", "Enable verbose mode.").Short('v').Bool()
|
||||
logDir = app.Flag("log-dir", "logstore directory.").Short('L').String()
|
||||
configFile = app.Flag("config-file", "config file path").Default("settings.yml").HintOptions("FILENAME").Short('C').String()
|
||||
logger = util.NewLogger("main")
|
||||
)
|
||||
|
||||
func systemStart(ctx context.Context, errorSignal chan error, waiter *sync.WaitGroup) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
errorSignal <- err.(error)
|
||||
}
|
||||
}()
|
||||
start := time.Now()
|
||||
app.Version(util.Config.Version())
|
||||
if _, err := app.Parse(os.Args[1:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
setMaxProcs()
|
||||
|
||||
if err := util.LoadConfig(*configFile); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
util.InitLogger(*verbose, *logDir, &util.Config.Logging.Application)
|
||||
showBanner(util.Config.Phase, util.Config.Version(), util.Config.BuildDate())
|
||||
// init subsystem
|
||||
//db.InitDB(&util.Config.Db)
|
||||
//redis.InitRedis(&util.Config.Redis)
|
||||
//redis.AppTokenInitService(util.Config.Phase, util.Config.AppToken.Expires, util.Config.AppToken.RefreshExpires)
|
||||
//redis.UploadFileInitService(util.Config.Phase, util.Config.AppToken.UploadRetryExpires)
|
||||
periodic.Start(errorSignal, ctx, waiter)
|
||||
|
||||
http.Start(util.Config.Bind, &util.Config.Logging.Access, errorSignal, ctx, waiter)
|
||||
|
||||
logger.Info("bootstrapped application ", time.Since(start))
|
||||
util.NotifyDaemon(util.DaemonStarted)
|
||||
}
|
||||
|
||||
func systemReload() {
|
||||
util.RotateLogger()
|
||||
}
|
||||
|
||||
func systemTeardown(exitCode *int, waiter *sync.WaitGroup) {
|
||||
logger.Info("closing application")
|
||||
util.NotifyDaemon(util.DaemonStopping)
|
||||
waiter.Wait()
|
||||
//redis.CloseRedis()
|
||||
logger.Info("bye")
|
||||
os.Exit(*exitCode)
|
||||
}
|
||||
|
||||
func main() {
|
||||
var (
|
||||
exitCode = 0
|
||||
ctx, canceled = context.WithCancel(context.Background())
|
||||
waiter = &sync.WaitGroup{}
|
||||
exitSignal = make(chan os.Signal, 1)
|
||||
errorSignal = make(chan error, 10)
|
||||
)
|
||||
systemStart(ctx, errorSignal, waiter)
|
||||
defer systemTeardown(&exitCode, waiter)
|
||||
signal.Notify(exitSignal, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logger.Info("Service request to close this application")
|
||||
return
|
||||
case sysSignal := <-exitSignal:
|
||||
switch sysSignal {
|
||||
case syscall.SIGHUP:
|
||||
systemReload()
|
||||
default:
|
||||
canceled()
|
||||
logger.Info("SYSCALL! ", sysSignal.String())
|
||||
return
|
||||
}
|
||||
case err := <-errorSignal:
|
||||
canceled()
|
||||
logger.Error("exception raised! ", err)
|
||||
exitCode = -1
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setMaxProcs() {
|
||||
// TODO(vmarmol): Consider limiting if we have a CPU mask in effect.
|
||||
// Allow as many threads as we have cores unless the user specified a value.
|
||||
var numProcs int
|
||||
// if *maxProcs < 1 {
|
||||
numProcs = runtime.NumCPU()
|
||||
// } else {
|
||||
// numProcs = *maxProcs
|
||||
// }
|
||||
runtime.GOMAXPROCS(numProcs)
|
||||
|
||||
// Check if the setting was successful.
|
||||
actualNumProcs := runtime.GOMAXPROCS(0)
|
||||
if actualNumProcs != numProcs {
|
||||
logger.Warn("Specified max procs of %v but using %v", numProcs, actualNumProcs)
|
||||
}
|
||||
}
|
||||
|
||||
func showBanner(phase string, version string, buildDate string) {
|
||||
if util.LoggerIsStd() {
|
||||
logger.Info(`
|
||||
{
|
||||
{ }
|
||||
}_{ __{
|
||||
.-{ } }-. ` + util.Config.Name() + `
|
||||
( } { ) version: ` + version + `
|
||||
` + "|`-.._____..-'| buildDate: " + buildDate + `
|
||||
| ;--. phase: ` + phase + `
|
||||
| (__ \
|
||||
| | ) )
|
||||
| |/ /
|
||||
| / /
|
||||
| ( /
|
||||
\ y'
|
||||
` + "`-.._____..-'")
|
||||
} else {
|
||||
logger.
|
||||
WithField("version", version).
|
||||
WithField("buildDate", buildDate).
|
||||
WithField("phase", phase).
|
||||
Info("##", util.Config.Name())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
[Unit]
|
||||
Description=Rooibos API Daemon
|
||||
Documentation=https://amuz.es
|
||||
After=syslog.target network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
Restart=always
|
||||
RestartSec=15
|
||||
ExecStart=/opt/eden/service/rooibos/cur-dev/rooibos -C dev.yml -L /opt/eden/service/rooibos/logs/dev
|
||||
WorkingDirectory=/opt/eden/service/rooibos/cur-dev
|
||||
#ExecReload=/bin/kill -s HUP $MAINPID
|
||||
TimeoutStartSec=5
|
||||
|
||||
|
||||
# Disable timeout logic and wait until process is stopped
|
||||
TimeoutStopSec=0
|
||||
# SIGTERM signal is used to stop Minio
|
||||
KillSignal=SIGTERM
|
||||
SendSIGKILL=no
|
||||
|
||||
SuccessExitStatus=0
|
||||
# kill only the docker process, not all processes in the cgroup
|
||||
KillMode=process
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
|
@ -0,0 +1,29 @@
|
|||
phase: development
|
||||
db:
|
||||
network: tcp
|
||||
host: db.gear.amuz.es
|
||||
driver: mysql
|
||||
user: db-dev
|
||||
password: "ttt4t4"
|
||||
port: 3306
|
||||
db: yori-dev
|
||||
args: "readTimeout=3s&parseTime=true&collation=utf8mb4_unicode_ci&strict=true&sql_notes=false&time_zone=%27UTC%27&loc=UTC"
|
||||
redis:
|
||||
host: db.gear.amuz.es
|
||||
port: 6379
|
||||
password: fgfgdgf
|
||||
db: 1
|
||||
maxretries: 7
|
||||
poolsize: 30
|
||||
logging:
|
||||
application:
|
||||
filename: "Stdout"
|
||||
maxsizemb: 500
|
||||
maxbackup: 30
|
||||
maxday: 14
|
||||
access:
|
||||
filename: "access.log"
|
||||
maxsizemb: 500
|
||||
maxbackup: 30
|
||||
maxday: 14
|
||||
bind: "0.0.0.0:8082"
|
|
@ -0,0 +1,74 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"amuz.es/gogs/infra/rooibos/util"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/jmoiron/sqlx"
|
||||
errs "github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
db *sqlx.DB = nil
|
||||
logger = util.NewLogger("sql")
|
||||
)
|
||||
|
||||
func InitDB(config *util.DbConfig) {
|
||||
connectStr := fmt.Sprintf(
|
||||
"%s:%s@%s(%s:%d)/%s?%s",
|
||||
config.User,
|
||||
config.Password,
|
||||
config.Network,
|
||||
config.Host,
|
||||
config.Port,
|
||||
config.DB,
|
||||
config.Args)
|
||||
logger.Debugf("connect to %s@%s(%s:%d)/%s?%s",
|
||||
config.User,
|
||||
config.Network,
|
||||
config.Host,
|
||||
config.Port,
|
||||
config.DB,
|
||||
config.Args)
|
||||
if conn, err := sqlx.Connect(config.Driver, connectStr); err != nil {
|
||||
panic(errs.Wrap(err, "failed to connect db"))
|
||||
} else {
|
||||
db = conn
|
||||
//https://github.com/go-sql-driver/mysql/issues/257 무조건 0
|
||||
db.SetMaxIdleConns(0)
|
||||
db.SetMaxOpenConns(25)
|
||||
logger.Info("DB connected")
|
||||
prepareSqls()
|
||||
}
|
||||
}
|
||||
|
||||
func prepareSqls() {
|
||||
memberPrepare()
|
||||
}
|
||||
func Begin() (tx *sqlx.Tx, err error) {
|
||||
tx, err = db.Beginx()
|
||||
return
|
||||
}
|
||||
|
||||
func prepareSql(sql string) (*sqlx.Stmt, error) {
|
||||
if stmt, err := db.Preparex(sql); err != nil {
|
||||
return nil, errs.Wrapf(err, "failed to prepare sql %s", sql)
|
||||
} else {
|
||||
return stmt, nil
|
||||
}
|
||||
}
|
||||
func tablecheck(tablename string) error {
|
||||
var fetched int
|
||||
if db.Get(
|
||||
&fetched, `
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = ?`,
|
||||
tablename) != nil || fetched != 1 {
|
||||
return errors.New(fmt.Sprintf("table %s not exist", tablename))
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS member
|
||||
(
|
||||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NULL,
|
||||
contact VARCHAR(128) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
status TINYINT NOT NULL,
|
||||
company_seq BIGINT UNSIGNED NOT NULL,
|
||||
UNIQUE INDEX uk_member_01 (company_seq,contact),
|
||||
INDEX idx_member_01 (company_seq),
|
||||
INDEX idx_member_02 (contact),
|
||||
INDEX idx_member_03 (status)
|
||||
)
|
||||
ENGINE=InnoDB
|
||||
ROW_FORMAT=COMPRESSED
|
||||
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
|
||||
;
|
||||
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"amuz.es/gogs/infra/rooibos/enums"
|
||||
|
||||
"database/sql"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
var (
|
||||
memberInsertQuery *sqlx.Stmt
|
||||
memberSelectByContactQuery *sqlx.Stmt
|
||||
memberSelectByIdQuery *sqlx.Stmt
|
||||
)
|
||||
|
||||
type member struct {
|
||||
In_Id uint64 `db:"id"`
|
||||
In_CreatedAt time.Time `db:"created_at"`
|
||||
In_UpdatedAt mysql.NullTime `db:"updated_at"`
|
||||
In_Contact string `db:"contact"`
|
||||
In_Name string `db:"name"`
|
||||
In_Password string `db:"password"`
|
||||
In_Status enums.MemberStatus `db:"status"`
|
||||
In_CompanySeq uint64 `db:"company_seq"`
|
||||
}
|
||||
|
||||
func (member *member) Id() uint64 { return member.In_Id }
|
||||
func (member *member) CreatedAt() time.Time { return member.In_CreatedAt }
|
||||
func (member *member) UpdatedAt() *time.Time {
|
||||
if member.In_UpdatedAt.Valid {
|
||||
return &member.In_UpdatedAt.Time
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func (member *member) Contact() string { return member.In_Contact }
|
||||
func (member *member) Name() string { return member.In_Name }
|
||||
func (member *member) Status() enums.MemberStatus { return member.In_Status }
|
||||
func (member *member) CompanySeq() uint64 { return member.In_CompanySeq }
|
||||
func (member *member) String() string {
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString("Member(")
|
||||
if member != nil {
|
||||
buffer.WriteString(strconv.FormatUint(member.In_Id, 10))
|
||||
} else {
|
||||
buffer.WriteString("nil")
|
||||
}
|
||||
buffer.WriteString(")")
|
||||
return buffer.String()
|
||||
}
|
||||
func NewMember(
|
||||
id uint64,
|
||||
createdAt time.Time,
|
||||
updatedAt *time.Time,
|
||||
contact string,
|
||||
name string,
|
||||
password string,
|
||||
status enums.MemberStatus,
|
||||
companySeq uint64) (Member){
|
||||
var updatedAtWrapped mysql.NullTime
|
||||
if updatedAt != nil{
|
||||
updatedAtWrapped=mysql.NullTime{*updatedAt, true}
|
||||
}
|
||||
return &member{
|
||||
id,
|
||||
createdAt,
|
||||
updatedAtWrapped,
|
||||
contact,
|
||||
name,
|
||||
password,
|
||||
status,
|
||||
companySeq,
|
||||
}
|
||||
}
|
||||
|
||||
type Member interface {
|
||||
Id() uint64
|
||||
CreatedAt() time.Time
|
||||
UpdatedAt() *time.Time
|
||||
Contact() string
|
||||
Name() string
|
||||
Status() enums.MemberStatus
|
||||
CompanySeq() uint64
|
||||
String() string
|
||||
}
|
||||
|
||||
func memberPrepare() {
|
||||
if err := tablecheck("member"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if query, err := prepareSql(
|
||||
`INSERT INTO member (
|
||||
created_at, contact, name,
|
||||
password, status, company_seq
|
||||
)
|
||||
VALUES
|
||||
(NOW(), ?,?,?,?,?)
|
||||
`); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
memberInsertQuery = query
|
||||
}
|
||||
|
||||
if query, err := prepareSql(` select * from member where contact=? and status=? order by id desc limit 1`); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
memberSelectByContactQuery = query
|
||||
}
|
||||
|
||||
if query, err := prepareSql(`Select * from member where id=?`); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
memberSelectByIdQuery = query
|
||||
}
|
||||
logger.Debug("Member query prepared")
|
||||
}
|
||||
|
||||
func MemberFindByContactWithTx(tx *sqlx.Tx, contact string, status enums.MemberStatus) (Member, error) {
|
||||
obj := member{}
|
||||
if err := tx.Stmtx(memberSelectByContactQuery).Get(&obj, contact, status); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &obj, nil
|
||||
}
|
||||
func MemberFindByContactOne(contact string, status enums.MemberStatus) (Member, error) {
|
||||
obj := member{}
|
||||
if err := memberSelectByContactQuery.Get(&obj, contact, status); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &obj, nil
|
||||
}
|
||||
func MemberFindOneWithTx(tx *sqlx.Tx, pk uint64) (Member, error) {
|
||||
obj := member{}
|
||||
if err := tx.Stmtx(memberSelectByIdQuery).Get(&obj, pk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &obj, nil
|
||||
}
|
||||
func MemberFindOne(pk uint64) (Member, error) {
|
||||
obj := member{}
|
||||
if err := memberSelectByIdQuery.Get(&obj, pk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &obj, nil
|
||||
}
|
||||
func MemberInsertWithTx(tx *sqlx.Tx, contact string, name string, password string, status enums.MemberStatus, companySeq uint64) (uint64, error) {
|
||||
var (
|
||||
err error
|
||||
result sql.Result
|
||||
pk int64
|
||||
)
|
||||
if result, err = tx.Stmtx(memberInsertQuery).Exec(
|
||||
contact,
|
||||
name,
|
||||
password,
|
||||
status,
|
||||
companySeq); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if pk, err = result.LastInsertId(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return (uint64)(pk), nil
|
||||
}
|
||||
func MemberInsert(contact string, name string, password string, status enums.MemberStatus, companySeq uint64) (uint64, error) {
|
||||
var (
|
||||
err error
|
||||
result sql.Result
|
||||
pk int64
|
||||
)
|
||||
if result, err = memberInsertQuery.Exec(
|
||||
contact,
|
||||
name,
|
||||
password,
|
||||
status,
|
||||
companySeq); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if pk, err = result.LastInsertId(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return (uint64)(pk), nil
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// NullUint64 represents an uint64 that may be null.
|
||||
// NullUint64 implements the Scanner interface so
|
||||
// it can be used as a scan destination, similar to NullString.
|
||||
|
||||
// NullUint64 is a sql.Scanner for unsigned ints.
|
||||
type NullUint64 struct {
|
||||
Uint64 uint64
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (n NullUint64) Value() (driver.Value, error) {
|
||||
if !n.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return n.Uint64, nil
|
||||
}
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (n *NullUint64) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
n.Uint64, n.Valid = 0, false
|
||||
return nil
|
||||
}
|
||||
n.Valid = true
|
||||
s := asString(src)
|
||||
var err error
|
||||
n.Uint64, err = strconv.ParseUint(s, 10, 64)
|
||||
return err
|
||||
}
|
||||
|
||||
func asString(src interface{}) string {
|
||||
switch v := src.(type) {
|
||||
case string:
|
||||
return v
|
||||
case []byte:
|
||||
return string(v)
|
||||
}
|
||||
return fmt.Sprintf("%v", src)
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
package iface
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
|
||||
type KeyValuePair struct {
|
||||
Key string `json:"key" binding:"required"`
|
||||
Value interface{} `json:"value" binding:"required"`
|
||||
}
|
||||
|
||||
|
||||
type WrappedResponse struct {
|
||||
Message string `json:"message"`
|
||||
Info *PageInfo `json:"info,omitempty"`
|
||||
Content interface{} `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
type PageInfo struct {
|
||||
Offset uint32 `json:"offset" binding:"required"`
|
||||
Size uint16 `json:"size" binding:"required"`
|
||||
}
|
||||
|
||||
|
||||
type Tuple struct {
|
||||
Path string
|
||||
Code int
|
||||
}
|
||||
|
||||
|
||||
type CounterMetric struct {
|
||||
Inc chan Tuple `json:"-"`
|
||||
internalRequestsSum uint64
|
||||
internalRequests map[string]uint64
|
||||
internalRequestCodes map[string]uint64
|
||||
RequestsSum uint64 `json:"request_sum_per_minute"`
|
||||
Requests map[string]uint64 `json:"requests_per_minute"`
|
||||
RequestCodes map[string]uint64 `json:"request_codes_per_minute"`
|
||||
}
|
||||
|
||||
|
||||
type DurationMetricReadable struct {
|
||||
Min string `json:"min"`
|
||||
Max string `json:"max"`
|
||||
Mean string `json:"mean"`
|
||||
Stdev string `json:"stdev"`
|
||||
P90 string `json:"p90"`
|
||||
P95 string `json:"p95"`
|
||||
P99 string `json:"p99"`
|
||||
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
|
||||
type DurationMetric struct {
|
||||
Min uint64 `json:"min"`
|
||||
Max uint64 `json:"max"`
|
||||
Mean uint64 `json:"mean"`
|
||||
Stdev uint64 `json:"stdev"`
|
||||
P90 uint64 `json:"p90"`
|
||||
P95 uint64 `json:"p95"`
|
||||
P99 uint64 `json:"p99"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
|
||||
type RequestMetricReadable struct {
|
||||
Duration DurationMetricReadable `json:"duration"`
|
||||
Count CounterMetric `json:"count"`
|
||||
}
|
||||
|
||||
|
||||
type RequestMetric struct {
|
||||
Duration DurationMetric `json:"duration"`
|
||||
Count CounterMetric `json:"count"`
|
||||
}
|
||||
|
||||
|
||||
type RuntimeMetric struct {
|
||||
GoVersion string `json:"go_version"`
|
||||
GoOs string `json:"go_os"`
|
||||
GoArch string `json:"go_arch"`
|
||||
CpuNum int `json:"cpu_num"`
|
||||
GoroutineNum int `json:"goroutine_num"`
|
||||
Gomaxprocs int `json:"go_maxprocs"`
|
||||
CgoCallNum int64 `json:"cgo_call_num"`
|
||||
}
|
||||
|
||||
|
||||
type MemoryMetric struct {
|
||||
MemAllocated uint64 `json:"mem_allocated"`
|
||||
MemTotal uint64 `json:"mem_total"`
|
||||
MemSys uint64 `json:"mem_sys"`
|
||||
Lookups uint64 `json:"lookups"`
|
||||
MemMallocs uint64 `json:"mem_mallocs"`
|
||||
MemFrees uint64 `json:"mem_frees"`
|
||||
|
||||
HeapAlloc uint64 `json:"heap_alloc"`
|
||||
HeapSys uint64 `json:"heap_sys"`
|
||||
HeapIdle uint64 `json:"heap_idle"`
|
||||
HeapInuse uint64 `json:"heap_inuse"`
|
||||
HeapReleased uint64 `json:"heap_released"`
|
||||
HeapObjects uint64 `json:"heap_objects"`
|
||||
|
||||
StackInuse uint64 `json:"stack_inuse"`
|
||||
StackSys uint64 `json:"stack_sys"`
|
||||
MSpanInuse uint64 `json:"m_span_inuse"`
|
||||
MSpanSys uint64 `json:"m_span_sys"`
|
||||
MCacheInuse uint64 `json:"m_cache_inuse"`
|
||||
MCacheSys uint64 `json:"m_cache_sys"`
|
||||
BuckHashSys uint64 `json:"buck_hash_sys"`
|
||||
GCSys uint64 `json:"gc_sys"`
|
||||
OtherSys uint64 `json:"other_sys"`
|
||||
|
||||
NextGC uint64 `json:"next_gc"`
|
||||
LastGC uint64 `json:"last_gc"`
|
||||
PauseTotalNs uint64 `json:"pause_total_ns"`
|
||||
PauseNs uint64 `json:"pause_ns"`
|
||||
NumGC uint32 `json:"num_gc"`
|
||||
}
|
||||
|
||||
|
||||
type Metric struct {
|
||||
Cmdline []string `json:"cmd_line"`
|
||||
Uptime uint64 `json:"uptime"`
|
||||
Request RequestMetric `json:"request"`
|
||||
Runtime RuntimeMetric `json:"runtime"`
|
||||
Memory MemoryMetric `json:"memory"`
|
||||
}
|
||||
|
||||
|
||||
type MemoryMetricReadable struct {
|
||||
MemAllocated string `json:"mem_allocated"`
|
||||
MemTotal string `json:"mem_total"`
|
||||
MemSys string `json:"mem_sys"`
|
||||
Lookups uint64 `json:"lookups"`
|
||||
MemMallocs uint64 `json:"mem_mallocs"`
|
||||
MemFrees uint64 `json:"mem_frees"`
|
||||
|
||||
HeapAlloc string `json:"heap_alloc"`
|
||||
HeapSys string `json:"heap_sys"`
|
||||
HeapIdle string `json:"heap_idle"`
|
||||
HeapInuse string `json:"heap_inuse"`
|
||||
HeapReleased string `json:"heap_released"`
|
||||
HeapObjects uint64 `json:"heap_objects"`
|
||||
|
||||
StackInuse string `json:"stack_inuse"`
|
||||
StackSys string `json:"stack_sys"`
|
||||
MSpanInuse string `json:"m_span_inuse"`
|
||||
MSpanSys string `json:"m_span_sys"`
|
||||
MCacheInuse string `json:"m_cache_inuse"`
|
||||
MCacheSys string `json:"m_cache_sys"`
|
||||
BuckHashSys string `json:"buck_hash_sys"`
|
||||
GCSys string `json:"gc_sys"`
|
||||
OtherSys string `json:"other_sys"`
|
||||
|
||||
NextGC string `json:"next_gc"`
|
||||
LastGC string `json:"last_gc"`
|
||||
PauseTotalNs string `json:"pause_total_ns"`
|
||||
PauseNs string `json:"pause_ns"`
|
||||
NumGC uint32 `json:"num_gc"`
|
||||
}
|
||||
|
||||
|
||||
type MetricReadable struct {
|
||||
Cmdline []string `json:"cmd_line"`
|
||||
Uptime string `json:"uptime"`
|
||||
Request RequestMetricReadable `json:"request"`
|
||||
Runtime RuntimeMetric `json:"runtime"`
|
||||
Memory MemoryMetricReadable `json:"memory"`
|
||||
}
|
||||
|
||||
// -vars="mem:memory.mem_allocated,duration:request.duration.min,duration:request.duration.max,duration:request.duration.mean,duration:request.duration.stdev,duration:request.duration.p90,duration:request.duration.p95,duration:request.duration.p99,mem:memory.mem_allocated,mem:memory.heap_alloc,mem:memory.heap_inuse,mem:memory.stack_inuse,duration:memory.next_gc,duration:memory.last_gc,duration:memory.pause_total_ns,duration:memory.pause_ns"
|
|
@ -0,0 +1,64 @@
|
|||
package iface
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
//turns a new initialized CounterMetric object.
|
||||
func NewCounterMetric() *CounterMetric {
|
||||
ca := &CounterMetric{}
|
||||
ca.Inc = make(chan Tuple)
|
||||
ca.internalRequestsSum = 0
|
||||
ca.internalRequests = make(map[string]uint64, 0)
|
||||
ca.internalRequestCodes = make(map[string]uint64, 0)
|
||||
return ca
|
||||
}
|
||||
|
||||
// StartTimer will call a forever loop in a goroutine to calculate
|
||||
// metrics for measurements every d ticks. The parameter of this
|
||||
// function should normally be 1 * time.Minute, if not it will expose
|
||||
// unintuive JSON keys (requests_per_minute and
|
||||
// request_sum_per_minute).
|
||||
func (ca *CounterMetric) StartTimer(d time.Duration) {
|
||||
timer := time.Tick(d)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case tup := <-ca.Inc:
|
||||
ca.internalRequestsSum++
|
||||
ca.internalRequests[tup.Path]++
|
||||
ca.internalRequestCodes[strconv.FormatInt(int64(tup.Code), 10)]++
|
||||
case <-timer:
|
||||
ca.reset()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// GetStats to fulfill aspects.Aspect interface, it returns the data
|
||||
// that will be served as JSON.
|
||||
func (ca *CounterMetric) GetStats() interface{} {
|
||||
return *ca
|
||||
}
|
||||
|
||||
// Name to fulfill aspects.Aspect interface, it will return the name
|
||||
// of the JSON object that will be served.
|
||||
func (ca *CounterMetric) Name() string {
|
||||
return "Counter"
|
||||
}
|
||||
|
||||
// InRoot to fulfill aspects.Aspect interface, it will return where to
|
||||
// put the JSON object into the monitoring endpoint.
|
||||
func (ca *CounterMetric) InRoot() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (ca *CounterMetric) reset() {
|
||||
ca.RequestsSum = ca.internalRequestsSum
|
||||
ca.Requests = ca.internalRequests
|
||||
ca.RequestCodes = ca.internalRequestCodes
|
||||
ca.internalRequestsSum = 0
|
||||
ca.internalRequests = make(map[string]uint64, ca.RequestsSum)
|
||||
ca.internalRequestCodes = make(map[string]uint64, len(ca.RequestCodes))
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package iface
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RequestDurationAggr struct {
|
||||
lastMinuteRequestTimes []float64
|
||||
Min float64 `json:"min"`
|
||||
Max float64 `json:"max"`
|
||||
Mean float64 `json:"mean"`
|
||||
Stdev float64 `json:"stdev"`
|
||||
P90 float64 `json:"p90"`
|
||||
P95 float64 `json:"p95"`
|
||||
P99 float64 `json:"p99"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// NewRequestDurationAggr returns a new initialized RequestDurationAggr
|
||||
// object.
|
||||
func NewRequestDurationAggr() *RequestDurationAggr {
|
||||
rt := &RequestDurationAggr{}
|
||||
rt.lastMinuteRequestTimes = make([]float64, 0)
|
||||
rt.Timestamp = time.Now()
|
||||
return rt
|
||||
}
|
||||
|
||||
// StartTimer will call a forever loop in a goroutine to calculate
|
||||
// metrics for measurements every d ticks.
|
||||
func (rt *RequestDurationAggr) StartTimer(d time.Duration) {
|
||||
timer := time.Tick(d)
|
||||
go func() {
|
||||
for {
|
||||
<-timer
|
||||
rt.calculate()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// GetStats to fulfill Metrics.Metric interface, it returns the data
|
||||
// that will be served as JSON.
|
||||
func (rt *RequestDurationAggr) GetStats() interface{} {
|
||||
return rt
|
||||
}
|
||||
|
||||
// Name to fulfill Metrics.Metric interface, it will return the name
|
||||
// of the JSON object that will be served.
|
||||
func (rt *RequestDurationAggr) Name() string {
|
||||
return "RequestTime"
|
||||
}
|
||||
|
||||
// InRoot to fulfill Metrics.Metric interface, it will return where to
|
||||
// put the JSON object into the monitoring endpoint.
|
||||
func (rt *RequestDurationAggr) InRoot() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (rt *RequestDurationAggr) Add(n float64) {
|
||||
rt.lastMinuteRequestTimes = append(rt.lastMinuteRequestTimes, n)
|
||||
}
|
||||
|
||||
func (rt *RequestDurationAggr) calculate() {
|
||||
sortedSlice := rt.lastMinuteRequestTimes[:]
|
||||
rt.lastMinuteRequestTimes = make([]float64, 0)
|
||||
l := len(sortedSlice)
|
||||
if l <= 1 {
|
||||
return
|
||||
}
|
||||
sort.Float64s(sortedSlice)
|
||||
|
||||
rt.Timestamp = time.Now()
|
||||
rt.Min = sortedSlice[0]
|
||||
rt.Max = sortedSlice[l - 1]
|
||||
rt.Mean = mean(sortedSlice, l)
|
||||
rt.Stdev = correctedStdev(sortedSlice, rt.Mean, l)
|
||||
rt.P90 = p90(sortedSlice, l)
|
||||
rt.P95 = p95(sortedSlice, l)
|
||||
rt.P99 = p99(sortedSlice, l)
|
||||
}
|
||||
|
||||
func mean(orderedObservations []float64, l int) float64 {
|
||||
res := 0.0
|
||||
for i := 0; i < l; i++ {
|
||||
res += orderedObservations[i]
|
||||
}
|
||||
|
||||
return res / float64(l)
|
||||
}
|
||||
|
||||
func p90(orderedObservations []float64, l int) float64 {
|
||||
return percentile(orderedObservations, l, 0.9)
|
||||
}
|
||||
|
||||
func p95(orderedObservations []float64, l int) float64 {
|
||||
return percentile(orderedObservations, l, 0.95)
|
||||
}
|
||||
|
||||
func p99(orderedObservations []float64, l int) float64 {
|
||||
return percentile(orderedObservations, l, 0.99)
|
||||
}
|
||||
|
||||
// percentile with argument p \in (0,1), l is the length of given orderedObservations
|
||||
// It does a simple apporximation of an ordered list of observations.
|
||||
// Formula: sortedSlice[0.95*length(sortedSlice)]
|
||||
func percentile(orderedObservations []float64, l int, p float64) float64 {
|
||||
return orderedObservations[int(p * float64(l))]
|
||||
}
|
||||
|
||||
func correctedStdev(observations []float64, mean float64, l int) float64 {
|
||||
var omega float64
|
||||
for i := 0; i < l; i++ {
|
||||
omega += math.Pow(observations[i]-mean, 2)
|
||||
}
|
||||
stdev := math.Sqrt(1 / (float64(l) - 1) * omega)
|
||||
return stdev
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
// +build go1.8
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"time"
|
||||
"net/http"
|
||||
|
||||
"amuz.es/gogs/infra/rooibos/subsys/http/middleware/logging"
|
||||
"amuz.es/gogs/infra/rooibos/subsys/http/middleware/recovery"
|
||||
"amuz.es/gogs/infra/rooibos/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
errs "github.com/pkg/errors"
|
||||
|
||||
"amuz.es/gogs/infra/rooibos/subsys/http/route"
|
||||
"github.com/mkevac/debugcharts"
|
||||
"log"
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
logger = util.NewLogger("http")
|
||||
)
|
||||
|
||||
func Start(bindAddress string, accessLogConfig *util.LogConfig,
|
||||
errorSignal chan error, ctx context.Context, waiter *sync.WaitGroup) {
|
||||
server, err := initialize(bindAddress, accessLogConfig)
|
||||
if err != nil {
|
||||
errorSignal <- err
|
||||
return
|
||||
}
|
||||
go bind(server, errorSignal, waiter)
|
||||
go gracefulCloser(server, ctx, waiter)
|
||||
logger.Info(bindAddress + " bound")
|
||||
|
||||
}
|
||||
func initialize(bindAddress string, accessLogConfig *util.LogConfig) (server *http.Server, err error) {
|
||||
defer func() {
|
||||
if recov := recover(); recov != nil {
|
||||
err = recov.(error)
|
||||
}
|
||||
}()
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
engine := gin.New()
|
||||
|
||||
initMiddleware(engine, accessLogConfig)
|
||||
initRoutes(engine)
|
||||
|
||||
logWriter := logger.Writer()
|
||||
server = &http.Server{
|
||||
Addr: bindAddress,
|
||||
Handler: engine,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
ErrorLog: log.New(logWriter, "", 0),
|
||||
}
|
||||
return
|
||||
}
|
||||
func gracefulCloser(server *http.Server, ctx context.Context, waiter *sync.WaitGroup) {
|
||||
defer waiter.Done()
|
||||
<-ctx.Done()
|
||||
logger.Info("stopping HTTP")
|
||||
if err := server.Shutdown(context.Background()); err != nil {
|
||||
logger.Info("HTTP closed:", err)
|
||||
}
|
||||
logger.Info("HTTP shutdowned safely.")
|
||||
}
|
||||
|
||||
func bind(server *http.Server, errorSignal chan error, waiter *sync.WaitGroup) {
|
||||
waiter.Add(1)
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
errorSignal <- errs.Wrap(err, "failed to listen or serve http")
|
||||
}
|
||||
}
|
||||
|
||||
func initRoutes(engine *gin.Engine) {
|
||||
suburl := ""
|
||||
path := engine.Group(suburl)
|
||||
// handlers
|
||||
|
||||
//debug handler
|
||||
debug := path.Group("/debug")
|
||||
debug.GET("/metric", route.Metric)
|
||||
debugcharts.GinDebugRouter(engine)
|
||||
debug.GET("/pprof/", route.ProfileIndexHandler())
|
||||
debug.GET("/pprof/heap", route.ProfileHeapHandler())
|
||||
debug.GET("/pprof/goroutine", route.ProfileGoroutineHandler())
|
||||
debug.GET("/pprof/block", route.ProfileBlockHandler())
|
||||
debug.GET("/pprof/threadcreate", route.ProfileThreadCreateHandler())
|
||||
debug.GET("/pprof/cmdline", route.ProfileCmdlineHandler())
|
||||
debug.GET("/pprof/profile", route.ProfileProfileHandler())
|
||||
debug.GET("/pprof/symbol", route.ProfileSymbolHandler())
|
||||
debug.POST("/pprof/symbol", route.ProfileSymbolHandler())
|
||||
debug.GET("/pprof/trace", route.ProfileTraceHandler())
|
||||
|
||||
path.GET("/favicon.ico", func(c *gin.Context) {
|
||||
c.Redirect(http.StatusMovedPermanently, "/static/favicon/images/favicon.ico")
|
||||
})
|
||||
|
||||
path.GET("/manifest.json", func(c *gin.Context) {
|
||||
c.Redirect(http.StatusMovedPermanently, "/static/favicon/manifest.json")
|
||||
})
|
||||
|
||||
path.GET("/browserconfig.xml", func(c *gin.Context) {
|
||||
c.Redirect(http.StatusMovedPermanently, "/static/favicon/browserconfig.xml")
|
||||
})
|
||||
|
||||
path.GET("/", route.Index)
|
||||
}
|
||||
|
||||
func initMiddleware(engine *gin.Engine, accessLogConfig *util.LogConfig) {
|
||||
engine.Use(logging.AccessLog(accessLogConfig))
|
||||
engine.Use(recovery.RecoveryJSON())
|
||||
|
||||
// not found handler
|
||||
engine.NoRoute(recovery.RecoveryHttpError(http.StatusNotFound))
|
||||
engine.NoMethod(recovery.RecoveryHttpError(http.StatusMethodNotAllowed))
|
||||
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
// middleware
|
||||
package logging
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"amuz.es/gogs/infra/rooibos/subsys/http/iface"
|
||||
"amuz.es/gogs/infra/rooibos/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
dateFormat = "02/Jan/2006:15:04:05 -0700"
|
||||
)
|
||||
|
||||
var DurationAspect = iface.NewRequestDurationAggr()
|
||||
var CounterAspect = iface.NewCounterMetric()
|
||||
|
||||
type logItem struct {
|
||||
host string
|
||||
method string
|
||||
uri string
|
||||
proto string
|
||||
referer string
|
||||
userAgent string
|
||||
requestedHost string
|
||||
requestedPath string
|
||||
status int
|
||||
responseSize int
|
||||
requestTime time.Time
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
func AccessLog(config *util.LogConfig) gin.HandlerFunc {
|
||||
_, logWriter := util.NewLogWriter(config)
|
||||
DurationAspect.StartTimer(10 * time.Second)
|
||||
CounterAspect.StartTimer(1 * time.Minute)
|
||||
logChan := make(chan logItem)
|
||||
|
||||
go sendLogging(logWriter, logChan)
|
||||
return func(c *gin.Context) {
|
||||
now := time.Now()
|
||||
req := c.Request
|
||||
|
||||
defer func() {
|
||||
dur := time.Now().Sub(now)
|
||||
|
||||
logItemData := logItem{
|
||||
status: c.Writer.Status(),
|
||||
responseSize: c.Writer.Size(),
|
||||
requestTime: now,
|
||||
duration: dur,
|
||||
}
|
||||
|
||||
if req != nil {
|
||||
logItemData.host = remoteHost(req)
|
||||
logItemData.method = req.Method
|
||||
logItemData.uri = req.RequestURI
|
||||
logItemData.proto = req.Proto
|
||||
logItemData.referer = req.Referer()
|
||||
logItemData.userAgent = req.UserAgent()
|
||||
logItemData.requestedHost = req.Host
|
||||
logItemData.requestedPath = req.URL.Path
|
||||
} else {
|
||||
logItemData.host = "-"
|
||||
logItemData.method = ""
|
||||
logItemData.uri = ""
|
||||
logItemData.proto = ""
|
||||
logItemData.referer = ""
|
||||
logItemData.userAgent = ""
|
||||
logItemData.requestedHost = ""
|
||||
logItemData.requestedPath = ""
|
||||
|
||||
}
|
||||
|
||||
logChan <- logItemData
|
||||
}()
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
func sendLogging(accesslog io.Writer, logChan chan logItem) {
|
||||
for {
|
||||
select {
|
||||
case logItemData := <-logChan:
|
||||
DurationAspect.Add(float64(logItemData.duration.Nanoseconds()))
|
||||
CounterAspect.Inc <- iface.Tuple{
|
||||
Path: logItemData.requestedPath,
|
||||
Code: logItemData.status,
|
||||
}
|
||||
// Logs an access event in Apache combined log format (with a minor customization with the duration).
|
||||
|
||||
buf := bytes.NewBufferString("")
|
||||
buf.WriteString(logItemData.host)
|
||||
buf.WriteString(` - - [`)
|
||||
buf.WriteString(logItemData.requestTime.Format(dateFormat))
|
||||
buf.WriteString(`] "`)
|
||||
buf.WriteString(logItemData.method)
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(logItemData.uri)
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(logItemData.proto)
|
||||
buf.WriteString(`" `)
|
||||
buf.WriteString(strconv.Itoa(logItemData.status))
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(strconv.Itoa(logItemData.responseSize))
|
||||
buf.WriteString(` "`)
|
||||
buf.WriteString(logItemData.referer)
|
||||
buf.WriteString(`" "`)
|
||||
buf.WriteString(logItemData.userAgent)
|
||||
buf.WriteString(`" `)
|
||||
buf.WriteString(strconv.FormatInt((logItemData.duration.Nanoseconds() / time.Millisecond.Nanoseconds()), 10))
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(logItemData.requestedHost)
|
||||
buf.WriteByte('\n')
|
||||
accesslog.Write(buf.Bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// strip port from addresses with hostname, ipv4 or ipv6
|
||||
func stripPort(address string) string {
|
||||
if h, _, err := net.SplitHostPort(address); err == nil {
|
||||
return h
|
||||
}
|
||||
|
||||
return address
|
||||
}
|
||||
|
||||
// The remote address of the client. When the 'X-Forwarded-For'
|
||||
// header is set, then it is used instead.
|
||||
func remoteAddr(r *http.Request) string {
|
||||
ff := r.Header.Get("X-Forwarded-For")
|
||||
if ff != "" {
|
||||
return ff
|
||||
}
|
||||
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
func remoteHost(r *http.Request) string {
|
||||
a := remoteAddr(r)
|
||||
h := stripPort(a)
|
||||
if h != "" {
|
||||
return h
|
||||
}
|
||||
|
||||
return "-"
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package recovery
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"amuz.es/gogs/infra/rooibos/subsys/http/iface"
|
||||
"amuz.es/gogs/infra/rooibos/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
dunno = []byte("???")
|
||||
centerDot = []byte("·")
|
||||
dot = []byte(".")
|
||||
slash = []byte("/")
|
||||
logger = util.NewLogger("recovery")
|
||||
color = "danger"
|
||||
preText = "루이보스 API 오류 :bomb:"
|
||||
title = "API 요청을 처리하는 중에 의도하지 않은 오류가 발행하였습니다."
|
||||
footer = "확인해 주세요! :hugging_face:"
|
||||
)
|
||||
|
||||
func recoveryInternal(stack []byte, code int, req *http.Request, err *string) {
|
||||
httprequest, _ := httputil.DumpRequest(req, false)
|
||||
detailInfo := map[string]interface{}{
|
||||
"method": req.Method,
|
||||
"statusCode": code,
|
||||
"path": req.URL.Path,
|
||||
"message": err,
|
||||
"request": string(httprequest),
|
||||
"stack": string(stack),
|
||||
}
|
||||
logger.WithFields(logrus.Fields(detailInfo)).Error("[Recovery] panic recovered:\n")
|
||||
}
|
||||
|
||||
func recoveryError(code int, _ *gin.Context) *iface.WrappedResponse {
|
||||
return &iface.WrappedResponse{
|
||||
Message: http.StatusText(code),
|
||||
}
|
||||
}
|
||||
|
||||
func RecoveryHttpError(code int) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
info := recoveryError(code, c)
|
||||
util.DumpJSON(c, code, info)
|
||||
}
|
||||
}
|
||||
|
||||
func RecoveryJSON() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
code := http.StatusInternalServerError
|
||||
stack := stack(5)
|
||||
systemErrMessage := err.(error).Error()
|
||||
go recoveryInternal(stack, code, c.Copy().Request, &systemErrMessage)
|
||||
util.DumpJSON(c, code, &iface.WrappedResponse{
|
||||
Message: systemErrMessage,
|
||||
})
|
||||
c.Abort()
|
||||
}
|
||||
}()
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// stack returns a nicely formated stack frame, skipping skip frames
|
||||
func stack(skip int) []byte {
|
||||
buf := new(bytes.Buffer) // the returned data
|
||||
// As we loop, we open files and read them. These variables record the currently
|
||||
// loaded file.
|
||||
var lines [][]byte
|
||||
var lastFile string
|
||||
for i := skip; ; i++ {
|
||||
// Skip the expected number of frames
|
||||
pc, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if paths := strings.SplitN(file, "src/", 2); len(paths) == 1 {
|
||||
// Print this much at least. If we can't find the source, it won't show.
|
||||
fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
|
||||
} else if vendors := strings.SplitN(paths[1], "vendor/", 2); len(vendors) == 1 {
|
||||
// Print this much at least. If we can't find the source, it won't show.
|
||||
fmt.Fprintf(buf, "%s:%d (0x%x)\n", paths[1], line, pc)
|
||||
} else {
|
||||
// Print this much at least. If we can't find the source, it won't show.
|
||||
fmt.Fprintf(buf, "%s:%d (0x%x)\n", vendors[1], line, pc)
|
||||
}
|
||||
if file != lastFile {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
lines = bytes.Split(data, []byte{'\n'})
|
||||
lastFile = file
|
||||
}
|
||||
fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// source returns a space-trimmed slice of the n'th line.
|
||||
func source(lines [][]byte, n int) []byte {
|
||||
n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
|
||||
if n < 0 || n >= len(lines) {
|
||||
return dunno
|
||||
}
|
||||
return bytes.TrimSpace(lines[n])
|
||||
|
||||
}
|
||||
|
||||
// function returns, if possible, the name of the function containing the PC.
|
||||
func function(pc uintptr) []byte {
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
return dunno
|
||||
}
|
||||
name := []byte(fn.Name())
|
||||
// The name includes the path name to the package, which is unnecessary
|
||||
// since the file name is already included. Plus, it has center dots.
|
||||
// That is, we see
|
||||
// runtime/debug.*T·ptrmethod
|
||||
// and want
|
||||
// *T.ptrmethod
|
||||
// Also the package path might contains dot (e.g. code.google.com/...),
|
||||
// so first eliminate the path prefix
|
||||
if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
|
||||
name = name[lastslash+1:]
|
||||
}
|
||||
if period := bytes.Index(name, dot); period >= 0 {
|
||||
name = name[period+1:]
|
||||
}
|
||||
name = bytes.Replace(name, centerDot, dot, -1)
|
||||
return name
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package route
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"amuz.es/gogs/infra/rooibos/subsys/http/iface"
|
||||
"amuz.es/gogs/infra/rooibos/util"
|
||||
common_iface "amuz.es/gogs/infra/rooibos/util/iface"
|
||||
"github.com/gin-gonic/gin"
|
||||
"amuz.es/gogs/infra/rooibos/subsys/db"
|
||||
"time"
|
||||
"amuz.es/gogs/infra/rooibos/enums"
|
||||
)
|
||||
|
||||
func Index(c *gin.Context) {
|
||||
uuidTest,_ :=common_iface.NewUUIDV4()
|
||||
resp := &iface.WrappedResponse{
|
||||
Message: "Pong!",
|
||||
Content: []iface.KeyValuePair{
|
||||
{
|
||||
Key: "name",
|
||||
Value: util.Config.Name(),
|
||||
},
|
||||
{
|
||||
Key: "version",
|
||||
Value: util.Config.Version(),
|
||||
},
|
||||
{
|
||||
Key: "buildDate",
|
||||
Value: util.Config.BuildDate(),
|
||||
},
|
||||
{
|
||||
Key: "phase",
|
||||
Value: util.Config.Phase,
|
||||
},
|
||||
{
|
||||
Key:"uuid",
|
||||
Value: uuidTest,
|
||||
},
|
||||
{
|
||||
Key: "model",
|
||||
Value: db.NewMember(
|
||||
uint64(0),
|
||||
time.Now(),
|
||||
nil,
|
||||
"hello",
|
||||
"name",
|
||||
"hello",
|
||||
enums.MemberStatusEnabled,
|
||||
uint64(0),
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
util.DumpJSON(c, http.StatusOK, resp)
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
package route
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"amuz.es/gogs/infra/rooibos/subsys/http/iface"
|
||||
"amuz.es/gogs/infra/rooibos/subsys/http/middleware/logging"
|
||||
"amuz.es/gogs/infra/rooibos/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var (
|
||||
startTime = time.Now()
|
||||
)
|
||||
|
||||
func Metric(c *gin.Context) {
|
||||
_, readable := c.Request.URL.Query()["readable"]
|
||||
if readable {
|
||||
memory := serializeReadableStat()
|
||||
util.DumpJSON(c, 200, memory)
|
||||
} else {
|
||||
memory := serializeMemoryStat()
|
||||
util.DumpJSON(c, 200, memory)
|
||||
}
|
||||
}
|
||||
|
||||
func serializeMemoryStat() *iface.Metric {
|
||||
m := new(runtime.MemStats)
|
||||
runtime.ReadMemStats(m)
|
||||
return &iface.Metric{
|
||||
Cmdline: os.Args,
|
||||
Uptime: uint64(time.Now().UnixNano() - startTime.UnixNano()),
|
||||
Request:iface.RequestMetric{
|
||||
Duration:iface.DurationMetric{
|
||||
Min: uint64(logging.DurationAspect.Min),
|
||||
Max: uint64(logging.DurationAspect.Max),
|
||||
Mean: uint64(logging.DurationAspect.Mean),
|
||||
Stdev: uint64(logging.DurationAspect.Stdev),
|
||||
P90: uint64(logging.DurationAspect.P90),
|
||||
P95: uint64(logging.DurationAspect.P95),
|
||||
P99: uint64(logging.DurationAspect.P99),
|
||||
Timestamp: logging.DurationAspect.Timestamp,
|
||||
},
|
||||
Count:*logging.CounterAspect,
|
||||
},
|
||||
Runtime:iface.RuntimeMetric{
|
||||
GoVersion: runtime.Version(),
|
||||
GoOs: runtime.GOOS,
|
||||
GoArch: runtime.GOARCH,
|
||||
CpuNum: runtime.NumCPU(),
|
||||
GoroutineNum: runtime.NumGoroutine(),
|
||||
Gomaxprocs: runtime.GOMAXPROCS(0),
|
||||
CgoCallNum: runtime.NumCgoCall(),
|
||||
},
|
||||
Memory:iface.MemoryMetric{
|
||||
MemAllocated: m.Alloc,
|
||||
MemTotal: m.TotalAlloc,
|
||||
MemSys: m.Sys,
|
||||
Lookups: m.Lookups,
|
||||
MemMallocs: m.Mallocs,
|
||||
MemFrees: m.Frees,
|
||||
|
||||
HeapAlloc: m.HeapAlloc,
|
||||
HeapSys: m.HeapSys,
|
||||
HeapIdle: m.HeapIdle,
|
||||
HeapInuse: m.HeapInuse,
|
||||
HeapReleased: m.HeapReleased,
|
||||
HeapObjects: m.HeapObjects,
|
||||
|
||||
StackInuse: m.StackInuse,
|
||||
StackSys: m.StackSys,
|
||||
MSpanInuse: m.MSpanInuse,
|
||||
MSpanSys: m.MSpanSys,
|
||||
MCacheInuse: m.MCacheInuse,
|
||||
MCacheSys: m.MCacheSys,
|
||||
BuckHashSys: m.BuckHashSys,
|
||||
GCSys: m.GCSys,
|
||||
OtherSys: m.OtherSys,
|
||||
|
||||
NextGC: m.NextGC,
|
||||
LastGC: uint64(time.Now().UnixNano()) - m.LastGC,
|
||||
PauseTotalNs: m.PauseTotalNs,
|
||||
PauseNs: m.PauseNs[(m.NumGC + 255) % 256],
|
||||
NumGC: m.NumGC,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
func serializeReadableStat() *iface.MetricReadable {
|
||||
m := new(runtime.MemStats)
|
||||
runtime.ReadMemStats(m)
|
||||
return &iface.MetricReadable{
|
||||
Uptime: util.TimeSincePro(startTime),
|
||||
Request:iface.RequestMetricReadable{
|
||||
Duration:iface.DurationMetricReadable{
|
||||
Min: util.TimeSinceDuration(uint64(logging.DurationAspect.Min)),
|
||||
Max: util.TimeSinceDuration(uint64(logging.DurationAspect.Max)),
|
||||
Mean: util.TimeSinceDuration(uint64(logging.DurationAspect.Mean)),
|
||||
Stdev: util.TimeSinceDuration(uint64(logging.DurationAspect.Stdev)),
|
||||
P90: util.TimeSinceDuration(uint64(logging.DurationAspect.P90)),
|
||||
P95: util.TimeSinceDuration(uint64(logging.DurationAspect.P95)),
|
||||
P99: util.TimeSinceDuration(uint64(logging.DurationAspect.P99)),
|
||||
Timestamp: logging.DurationAspect.Timestamp,
|
||||
},
|
||||
Count:*logging.CounterAspect,
|
||||
},
|
||||
Runtime:iface.RuntimeMetric{
|
||||
GoVersion: runtime.Version(),
|
||||
GoOs: runtime.GOOS,
|
||||
GoArch: runtime.GOARCH,
|
||||
CpuNum: runtime.NumCPU(),
|
||||
GoroutineNum: runtime.NumGoroutine(),
|
||||
Gomaxprocs: runtime.GOMAXPROCS(0),
|
||||
CgoCallNum: runtime.NumCgoCall(),
|
||||
},
|
||||
Memory:iface.MemoryMetricReadable{
|
||||
MemAllocated: util.FileSize(m.Alloc),
|
||||
MemTotal: util.FileSize(m.TotalAlloc),
|
||||
MemSys: util.FileSize(m.Sys),
|
||||
Lookups: m.Lookups,
|
||||
MemMallocs: m.Mallocs,
|
||||
MemFrees: m.Frees,
|
||||
|
||||
HeapAlloc: util.FileSize(m.HeapAlloc),
|
||||
HeapSys: util.FileSize(m.HeapSys),
|
||||
HeapIdle: util.FileSize(m.HeapIdle),
|
||||
HeapInuse: util.FileSize(m.HeapInuse),
|
||||
HeapReleased: util.FileSize(m.HeapReleased),
|
||||
HeapObjects: m.HeapObjects,
|
||||
|
||||
StackInuse: util.FileSize(m.StackInuse),
|
||||
StackSys: util.FileSize(m.StackSys),
|
||||
MSpanInuse: util.FileSize(m.MSpanInuse),
|
||||
MSpanSys: util.FileSize(m.MSpanSys),
|
||||
MCacheInuse: util.FileSize(m.MCacheInuse),
|
||||
MCacheSys: util.FileSize(m.MCacheSys),
|
||||
BuckHashSys: util.FileSize(m.BuckHashSys),
|
||||
GCSys: util.FileSize(m.GCSys),
|
||||
OtherSys: util.FileSize(m.OtherSys),
|
||||
|
||||
NextGC: util.FileSize(m.NextGC),
|
||||
LastGC: util.TimeSinceDuration(uint64(time.Now().UnixNano()) - m.LastGC) + " ago",
|
||||
PauseTotalNs: util.TimeSinceDuration(m.PauseTotalNs),
|
||||
PauseNs: util.TimeSinceDuration(m.PauseNs[(m.NumGC + 255) % 256]),
|
||||
NumGC: m.NumGC,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package route
|
||||
|
||||
import (
|
||||
"net/http/pprof"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// IndexHandler will pass the call from /debug/pprof to pprof
|
||||
func ProfileIndexHandler() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
pprof.Index(ctx.Writer, ctx.Request)
|
||||
}
|
||||
}
|
||||
|
||||
// HeapHandler will pass the call from /debug/pprof/heap to pprof
|
||||
func ProfileHeapHandler() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
pprof.Handler("heap").ServeHTTP(ctx.Writer, ctx.Request)
|
||||
}
|
||||
}
|
||||
|
||||
// GoroutineHandler will pass the call from /debug/pprof/goroutine to pprof
|
||||
func ProfileGoroutineHandler() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
pprof.Handler("goroutine").ServeHTTP(ctx.Writer, ctx.Request)
|
||||
}
|
||||
}
|
||||
|
||||
// BlockHandler will pass the call from /debug/pprof/block to pprof
|
||||
func ProfileBlockHandler() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
pprof.Handler("block").ServeHTTP(ctx.Writer, ctx.Request)
|
||||
}
|
||||
}
|
||||
|
||||
// ThreadCreateHandler will pass the call from /debug/pprof/threadcreate to pprof
|
||||
func ProfileThreadCreateHandler() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
pprof.Handler("threadcreate").ServeHTTP(ctx.Writer, ctx.Request)
|
||||
}
|
||||
}
|
||||
|
||||
// CmdlineHandler will pass the call from /debug/pprof/cmdline to pprof
|
||||
func ProfileCmdlineHandler() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
pprof.Cmdline(ctx.Writer, ctx.Request)
|
||||
}
|
||||
}
|
||||
|
||||
// ProfileHandler will pass the call from /debug/pprof/profile to pprof
|
||||
func ProfileProfileHandler() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
pprof.Profile(ctx.Writer, ctx.Request)
|
||||
}
|
||||
}
|
||||
|
||||
// SymbolHandler will pass the call from /debug/pprof/symbol to pprof
|
||||
func ProfileSymbolHandler() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
pprof.Symbol(ctx.Writer, ctx.Request)
|
||||
}
|
||||
}
|
||||
|
||||
// TraceHandler will pass the call from /debug/pprof/trace to pprof
|
||||
func ProfileTraceHandler() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
pprof.Trace(ctx.Writer, ctx.Request)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package route
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"amuz.es/gogs/infra/rooibos/util"
|
||||
)
|
||||
|
||||
var logger = util.NewLogger("route")
|
||||
|
||||
func extractLastModified(hh http.Header) (modified *time.Time) {
|
||||
if modifiedStr := extractHeader("If-Modified-Since", hh); modifiedStr == nil {
|
||||
} else if modifiedTime, err := http.ParseTime(*modifiedStr); err != nil {
|
||||
} else {
|
||||
modified = &modifiedTime
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func extractHeader(key string, hh http.Header) (ret *string) {
|
||||
if hdrStr := hh.Get(key); hdrStr == "" {
|
||||
} else {
|
||||
ret = &hdrStr
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package periodic
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
cronLib "github.com/robfig/cron"
|
||||
|
||||
"sync"
|
||||
|
||||
"sync/atomic"
|
||||
|
||||
"amuz.es/gogs/infra/rooibos/util"
|
||||
"context"
|
||||
)
|
||||
|
||||
var (
|
||||
logger = util.NewLogger("periodic")
|
||||
runningJobGroup = sync.WaitGroup{}
|
||||
runningJobCount = int32(0)
|
||||
)
|
||||
|
||||
type Job struct {
|
||||
started time.Time
|
||||
Name string
|
||||
Execution func()
|
||||
isRunning int32
|
||||
}
|
||||
|
||||
func (j *Job) Run() {
|
||||
if ok := atomic.CompareAndSwapInt32(&j.isRunning, 0, -1); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// ENTER CRITICAL SECTION
|
||||
j.started = time.Now()
|
||||
runningJobGroup.Add(1)
|
||||
atomic.AddInt32(&runningJobCount, 1)
|
||||
defer func() {
|
||||
// EXIT CRITICAL SECTION
|
||||
atomic.AddInt32(&runningJobCount, -1)
|
||||
runningJobGroup.Done()
|
||||
atomic.StoreInt32(&j.isRunning, 0)
|
||||
logger.Debugf("Job %s Stopped in %s", j.Name, time.Now().Sub(j.started))
|
||||
}()
|
||||
j.Execution()
|
||||
}
|
||||
|
||||
func Start(errorSignal chan error, ctx context.Context, waiter *sync.WaitGroup) {
|
||||
cron, err := initialize(waiter)
|
||||
if err != nil {
|
||||
errorSignal <- err
|
||||
return
|
||||
}
|
||||
go gracefulCloser(cron, ctx, waiter)
|
||||
logger.Info("scheduler started")
|
||||
}
|
||||
|
||||
func initialize(waiter *sync.WaitGroup) (cron *cronLib.Cron, err error) {
|
||||
defer func() {
|
||||
if recov := recover(); recov != nil {
|
||||
err = recov.(error)
|
||||
}
|
||||
}()
|
||||
cron = cronLib.New()
|
||||
waiter.Add(1)
|
||||
logger.Debug("starting scheduler")
|
||||
defer cron.Start()
|
||||
return
|
||||
}
|
||||
|
||||
func gracefulCloser(cron *cronLib.Cron, ctx context.Context, waiter *sync.WaitGroup) {
|
||||
<-ctx.Done()
|
||||
defer waiter.Done()
|
||||
logger.Info("stopping scheduler")
|
||||
cron.Stop()
|
||||
|
||||
waitChannel := make(chan struct{}, 1)
|
||||
go func() {
|
||||
runningJobGroup.Wait()
|
||||
close(waitChannel)
|
||||
}()
|
||||
warningTicker := time.Tick(1 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-waitChannel:
|
||||
logger.Info("Periodic Jobs shutdowned safely.")
|
||||
return
|
||||
case <-warningTicker:
|
||||
logger.Infof(
|
||||
"Waiting Periodic Jobs (remains %d job(s))",
|
||||
runningJobCount,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func initJobs(closeSignal chan struct{}) {
|
||||
// cron.AddJob("* * * * * *", &Job{
|
||||
// Name: "job 1",
|
||||
// Execution: func() {
|
||||
// logger.Debugf("job 1 (executing %d job(s))", runningJobCount)
|
||||
// select {
|
||||
// case <-closeSignal:
|
||||
// logger.Debugf("job 1 quit signal received!")
|
||||
// case <-time.After(time.Millisecond * 2000):
|
||||
// }
|
||||
// },
|
||||
// })
|
||||
// todo upload 실패한건, 오래된건 관리.
|
||||
}
|
|
@ -0,0 +1,458 @@
|
|||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: subsys/redis/app_token_data.proto
|
||||
|
||||
/*
|
||||
Package redis is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
subsys/redis/app_token_data.proto
|
||||
|
||||
It has these top-level messages:
|
||||
TokenData
|
||||
*/
|
||||
package redis
|
||||
|
||||
import proto "github.com/gogo/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import _ "github.com/gogo/protobuf/gogoproto"
|
||||
import _ "github.com/golang/protobuf/ptypes/timestamp"
|
||||
|
||||
import amuz_es_gogs_infra_rooibos_util_iface "amuz.es/gogs/infra/rooibos/util/iface"
|
||||
import time "time"
|
||||
|
||||
import github_com_gogo_protobuf_types "github.com/gogo/protobuf/types"
|
||||
|
||||
import io "io"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
var _ = time.Kitchen
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type TokenData struct {
|
||||
ClientId amuz_es_gogs_infra_rooibos_util_iface.UUID `protobuf:"bytes,1,opt,name=ClientId,proto3,customtype=amuz.es/gogs/infra/rooibos/util/iface.UUID" json:"client_id"`
|
||||
RefreshToken amuz_es_gogs_infra_rooibos_util_iface.UUID `protobuf:"bytes,2,opt,name=RefreshToken,proto3,customtype=amuz.es/gogs/infra/rooibos/util/iface.UUID" json:"refresh_token"`
|
||||
MemberId uint64 `protobuf:"fixed64,3,opt,name=MemberId,proto3" json:"member_id"`
|
||||
CreatedAt time.Time `protobuf:"bytes,4,opt,name=CreatedAt,stdtime" json:"created_at"`
|
||||
}
|
||||
|
||||
func (m *TokenData) Reset() { *m = TokenData{} }
|
||||
func (m *TokenData) String() string { return proto.CompactTextString(m) }
|
||||
func (*TokenData) ProtoMessage() {}
|
||||
func (*TokenData) Descriptor() ([]byte, []int) { return fileDescriptorAppTokenData, []int{0} }
|
||||
|
||||
func (m *TokenData) GetMemberId() uint64 {
|
||||
if m != nil {
|
||||
return m.MemberId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *TokenData) GetCreatedAt() time.Time {
|
||||
if m != nil {
|
||||
return m.CreatedAt
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*TokenData)(nil), "redis.TokenData")
|
||||
}
|
||||
func (m *TokenData) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *TokenData) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
dAtA[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintAppTokenData(dAtA, i, uint64(m.ClientId.Size()))
|
||||
n1, err := m.ClientId.MarshalTo(dAtA[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n1
|
||||
dAtA[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintAppTokenData(dAtA, i, uint64(m.RefreshToken.Size()))
|
||||
n2, err := m.RefreshToken.MarshalTo(dAtA[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n2
|
||||
if m.MemberId != 0 {
|
||||
dAtA[i] = 0x19
|
||||
i++
|
||||
i = encodeFixed64AppTokenData(dAtA, i, uint64(m.MemberId))
|
||||
}
|
||||
dAtA[i] = 0x22
|
||||
i++
|
||||
i = encodeVarintAppTokenData(dAtA, i, uint64(github_com_gogo_protobuf_types.SizeOfStdTime(m.CreatedAt)))
|
||||
n3, err := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CreatedAt, dAtA[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n3
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func encodeFixed64AppTokenData(dAtA []byte, offset int, v uint64) int {
|
||||
dAtA[offset] = uint8(v)
|
||||
dAtA[offset+1] = uint8(v >> 8)
|
||||
dAtA[offset+2] = uint8(v >> 16)
|
||||
dAtA[offset+3] = uint8(v >> 24)
|
||||
dAtA[offset+4] = uint8(v >> 32)
|
||||
dAtA[offset+5] = uint8(v >> 40)
|
||||
dAtA[offset+6] = uint8(v >> 48)
|
||||
dAtA[offset+7] = uint8(v >> 56)
|
||||
return offset + 8
|
||||
}
|
||||
func encodeFixed32AppTokenData(dAtA []byte, offset int, v uint32) int {
|
||||
dAtA[offset] = uint8(v)
|
||||
dAtA[offset+1] = uint8(v >> 8)
|
||||
dAtA[offset+2] = uint8(v >> 16)
|
||||
dAtA[offset+3] = uint8(v >> 24)
|
||||
return offset + 4
|
||||
}
|
||||
func encodeVarintAppTokenData(dAtA []byte, offset int, v uint64) int {
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return offset + 1
|
||||
}
|
||||
func (m *TokenData) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
l = m.ClientId.Size()
|
||||
n += 1 + l + sovAppTokenData(uint64(l))
|
||||
l = m.RefreshToken.Size()
|
||||
n += 1 + l + sovAppTokenData(uint64(l))
|
||||
if m.MemberId != 0 {
|
||||
n += 9
|
||||
}
|
||||
l = github_com_gogo_protobuf_types.SizeOfStdTime(m.CreatedAt)
|
||||
n += 1 + l + sovAppTokenData(uint64(l))
|
||||
return n
|
||||
}
|
||||
|
||||
func sovAppTokenData(x uint64) (n int) {
|
||||
for {
|
||||
n++
|
||||
x >>= 7
|
||||
if x == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
func sozAppTokenData(x uint64) (n int) {
|
||||
return sovAppTokenData(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *TokenData) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAppTokenData
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: TokenData: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: TokenData: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAppTokenData
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthAppTokenData
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if err := m.ClientId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field RefreshToken", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAppTokenData
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthAppTokenData
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if err := m.RefreshToken.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
if wireType != 1 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field MemberId", wireType)
|
||||
}
|
||||
m.MemberId = 0
|
||||
if (iNdEx + 8) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += 8
|
||||
m.MemberId = uint64(dAtA[iNdEx-8])
|
||||
m.MemberId |= uint64(dAtA[iNdEx-7]) << 8
|
||||
m.MemberId |= uint64(dAtA[iNdEx-6]) << 16
|
||||
m.MemberId |= uint64(dAtA[iNdEx-5]) << 24
|
||||
m.MemberId |= uint64(dAtA[iNdEx-4]) << 32
|
||||
m.MemberId |= uint64(dAtA[iNdEx-3]) << 40
|
||||
m.MemberId |= uint64(dAtA[iNdEx-2]) << 48
|
||||
m.MemberId |= uint64(dAtA[iNdEx-1]) << 56
|
||||
case 4:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field CreatedAt", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAppTokenData
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthAppTokenData
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.CreatedAt, dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipAppTokenData(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthAppTokenData
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipAppTokenData(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowAppTokenData
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowAppTokenData
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if dAtA[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
return iNdEx, nil
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowAppTokenData
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
iNdEx += length
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthAppTokenData
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 3:
|
||||
for {
|
||||
var innerWire uint64
|
||||
var start int = iNdEx
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowAppTokenData
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
innerWire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
innerWireType := int(innerWire & 0x7)
|
||||
if innerWireType == 4 {
|
||||
break
|
||||
}
|
||||
next, err := skipAppTokenData(dAtA[start:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
iNdEx = start + next
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 4:
|
||||
return iNdEx, nil
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
return iNdEx, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthAppTokenData = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowAppTokenData = fmt.Errorf("proto: integer overflow")
|
||||
)
|
||||
|
||||
func init() { proto.RegisterFile("subsys/redis/app_token_data.proto", fileDescriptorAppTokenData) }
|
||||
|
||||
var fileDescriptorAppTokenData = []byte{
|
||||
// 336 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x90, 0xb1, 0x4e, 0xf3, 0x30,
|
||||
0x14, 0x85, 0xeb, 0xfe, 0x3f, 0x55, 0x6b, 0x5a, 0x09, 0x65, 0x40, 0x51, 0x87, 0xa4, 0x30, 0x15,
|
||||
0x24, 0x6c, 0x09, 0x1e, 0x00, 0x91, 0x76, 0xe9, 0x80, 0x84, 0xa2, 0x76, 0x62, 0x88, 0x9c, 0xe4,
|
||||
0x26, 0xb5, 0x68, 0xea, 0xc8, 0x76, 0x06, 0x78, 0x8a, 0x3e, 0x56, 0x27, 0xc4, 0xcc, 0x10, 0x50,
|
||||
0xd9, 0xfa, 0x14, 0x28, 0x8e, 0x42, 0xc5, 0xca, 0xe6, 0xeb, 0x73, 0xef, 0x77, 0x8e, 0x0e, 0x3e,
|
||||
0x53, 0x45, 0xa8, 0x9e, 0x15, 0x95, 0x10, 0x73, 0x45, 0x59, 0x9e, 0x07, 0x5a, 0x3c, 0xc1, 0x3a,
|
||||
0x88, 0x99, 0x66, 0x24, 0x97, 0x42, 0x0b, 0xeb, 0xc8, 0x68, 0xc3, 0xab, 0x94, 0xeb, 0x65, 0x11,
|
||||
0x92, 0x48, 0x64, 0x34, 0x15, 0xa9, 0xa0, 0x46, 0x0d, 0x8b, 0xc4, 0x4c, 0x66, 0x30, 0xaf, 0xfa,
|
||||
0x6a, 0xe8, 0xa6, 0x42, 0xa4, 0x2b, 0x38, 0x6c, 0x69, 0x9e, 0x81, 0xd2, 0x2c, 0xcb, 0xeb, 0x85,
|
||||
0xf3, 0xd7, 0x36, 0xee, 0xcd, 0x2b, 0xaf, 0x29, 0xd3, 0xcc, 0x7a, 0xc4, 0xdd, 0xc9, 0x8a, 0xc3,
|
||||
0x5a, 0xcf, 0x62, 0x1b, 0x8d, 0xd0, 0xb8, 0xef, 0xdd, 0x6e, 0x4b, 0xb7, 0xf5, 0x5e, 0xba, 0x97,
|
||||
0x2c, 0x2b, 0x5e, 0x08, 0xa8, 0x0a, 0xae, 0x28, 0x5f, 0x27, 0x92, 0x51, 0x29, 0x04, 0x0f, 0x85,
|
||||
0xa2, 0x85, 0xe6, 0x2b, 0xca, 0x13, 0x16, 0x01, 0x59, 0x2c, 0x66, 0xd3, 0x7d, 0xe9, 0xf6, 0x22,
|
||||
0x43, 0x09, 0x78, 0xec, 0xff, 0x00, 0xad, 0x14, 0xf7, 0x7d, 0x48, 0x24, 0xa8, 0xa5, 0x31, 0xb4,
|
||||
0xdb, 0xc6, 0x60, 0xf2, 0x27, 0x83, 0x81, 0xac, 0x49, 0x75, 0x4f, 0xfe, 0x2f, 0xb0, 0x75, 0x81,
|
||||
0xbb, 0xf7, 0x90, 0x85, 0x20, 0x67, 0xb1, 0xfd, 0x6f, 0x84, 0xc6, 0x1d, 0x6f, 0x50, 0x65, 0xca,
|
||||
0xcc, 0x9f, 0xc9, 0xd4, 0xc8, 0xd6, 0x03, 0xee, 0x4d, 0x24, 0x30, 0x0d, 0xf1, 0x9d, 0xb6, 0xff,
|
||||
0x8f, 0xd0, 0xf8, 0xf8, 0x7a, 0x48, 0xea, 0xce, 0x48, 0xd3, 0x19, 0x99, 0x37, 0x9d, 0x79, 0xa7,
|
||||
0x55, 0xd8, 0x7d, 0xe9, 0xe2, 0xa8, 0x3e, 0x0a, 0x98, 0xde, 0x7c, 0xb8, 0xc8, 0x3f, 0x40, 0xbc,
|
||||
0x93, 0xed, 0xce, 0x41, 0x6f, 0x3b, 0x07, 0x7d, 0xee, 0x1c, 0xb4, 0xf9, 0x72, 0x5a, 0x61, 0xc7,
|
||||
0x80, 0x6e, 0xbe, 0x03, 0x00, 0x00, 0xff, 0xff, 0x88, 0x6c, 0x3e, 0xdb, 0xe5, 0x01, 0x00, 0x00,
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
syntax = "proto3";
|
||||
package redis;
|
||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
message TokenData {
|
||||
bytes ClientId =1 [
|
||||
(gogoproto.customtype) = "amuz.es/gogs/infra/rooibos/util/iface.UUID",
|
||||
(gogoproto.jsontag) = "client_id",
|
||||
(gogoproto.nullable) = false
|
||||
];
|
||||
bytes RefreshToken =2 [
|
||||
(gogoproto.customtype) = "amuz.es/gogs/infra/rooibos/util/iface.UUID",
|
||||
(gogoproto.jsontag) = "refresh_token",
|
||||
(gogoproto.nullable) = false
|
||||
];
|
||||
fixed64 MemberId =3 [
|
||||
(gogoproto.jsontag) = "member_id"
|
||||
];
|
||||
|
||||
google.protobuf.Timestamp CreatedAt =4 [
|
||||
(gogoproto.jsontag) = "created_at",
|
||||
(gogoproto.stdtime) = true,
|
||||
(gogoproto.nullable) = false
|
||||
];
|
||||
|
||||
}
|
|
@ -0,0 +1,275 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"amuz.es/gogs/infra/rooibos/util/iface"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
var (
|
||||
tokenStoreBaseKey []byte
|
||||
refreshTokenStoreBaseKey []byte
|
||||
sessionStoreBaseKey []byte
|
||||
tokenExpireTime int64
|
||||
refreshTokenExpireTime int64
|
||||
)
|
||||
|
||||
func AppTokenInitService(phase string, tokenExpireTimeData, refreshTokenExpireTimeData int64) {
|
||||
tokenStoreBaseKey = []byte(fmt.Sprint(phase, "/RB", "/TK/"))
|
||||
refreshTokenStoreBaseKey = []byte(fmt.Sprint(phase, "/RB", "/RK/"))
|
||||
sessionStoreBaseKey = []byte(fmt.Sprint(phase, "/RB", "/SE/"))
|
||||
|
||||
tokenExpireTime = tokenExpireTimeData / 1000
|
||||
refreshTokenExpireTime = refreshTokenExpireTimeData / 1000
|
||||
}
|
||||
|
||||
func AppTokenCreateSession(clientId iface.UUID, memberId uint64) (out *SessionData, err error) {
|
||||
var client resourceConn
|
||||
if client, err = getConn(); err != nil {
|
||||
return
|
||||
}
|
||||
defer putConn(client)
|
||||
|
||||
var (
|
||||
refreshToken, _ = iface.NewUUIDV4()
|
||||
token, _ = iface.NewUUIDV4()
|
||||
|
||||
tokenStoreKey = append(tokenStoreBaseKey, token.String()...)
|
||||
refreshTokenStoreKey = append(refreshTokenStoreBaseKey, refreshToken.String()...)
|
||||
sessionStoreKey = strconv.AppendUint(sessionStoreBaseKey, memberId, 10)
|
||||
|
||||
refreshTokenData []byte
|
||||
tokenData []byte
|
||||
sessionData []byte
|
||||
|
||||
newSession = &SessionData{
|
||||
Token: token,
|
||||
RefreshToken: refreshToken,
|
||||
}
|
||||
)
|
||||
|
||||
if refreshTokenData, err = (&RefreshTokenData{
|
||||
ClientId: clientId,
|
||||
MemberId: memberId,
|
||||
}).Marshal(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if tokenData, err = (&TokenData{
|
||||
ClientId: clientId,
|
||||
MemberId: memberId,
|
||||
RefreshToken: refreshToken,
|
||||
}).Marshal(); err != nil {
|
||||
return
|
||||
}
|
||||
if sessionData, err = newSession.Marshal(); err != nil {
|
||||
return
|
||||
}
|
||||
var prevSession = new(SessionData)
|
||||
|
||||
var tx = func() (err error) {
|
||||
if _, err = client.Do("WATCH", sessionStoreKey); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if fetchErr := extractDataFromRedis(prevSession, sessionStoreKey, client); fetchErr != nil {
|
||||
prevSession = nil
|
||||
}
|
||||
client.Send("MULTI")
|
||||
|
||||
if prevSession != nil {
|
||||
previousTokenStoreKey := append(tokenStoreBaseKey, prevSession.Token.String()...)
|
||||
previousRefreshTokenStoreKey := append(refreshTokenStoreBaseKey, prevSession.RefreshToken.String()...)
|
||||
client.Send("DEL", previousTokenStoreKey, previousRefreshTokenStoreKey)
|
||||
}
|
||||
client.Send("SETEX", tokenStoreKey, tokenExpireTime, tokenData)
|
||||
client.Send("SETEX", refreshTokenStoreKey, refreshTokenExpireTime, refreshTokenData)
|
||||
client.Send("SETEX", sessionStoreKey, refreshTokenExpireTime, sessionData)
|
||||
|
||||
if _, err = redis.Values(client.Do("EXEC")); err != nil {
|
||||
return newTxError(err)
|
||||
}
|
||||
|
||||
out = newSession
|
||||
return
|
||||
}
|
||||
|
||||
err = retries(tx)
|
||||
return
|
||||
}
|
||||
|
||||
func AppTokenFindMemberIdByToken(clientId iface.UUID, token iface.UUID) (memberId uint64, err error) {
|
||||
var client resourceConn
|
||||
if client, err = getConn(); err != nil {
|
||||
return
|
||||
}
|
||||
defer putConn(client)
|
||||
var (
|
||||
tokenStoreKey = append(tokenStoreBaseKey, token.String()...)
|
||||
matchedTokenData = new(TokenData)
|
||||
)
|
||||
|
||||
if err = extractDataFromRedis(matchedTokenData, tokenStoreKey, client); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if clientId.Equal(matchedTokenData.ClientId) {
|
||||
memberId = matchedTokenData.MemberId
|
||||
} else {
|
||||
client.Send("DEL", tokenStoreKey)
|
||||
err = errors.New("matched token is invalid")
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func AppTokenFindMemberIdByRefreshToken(clientId iface.UUID, refreshToken iface.UUID) (memberId uint64, err error) {
|
||||
var client resourceConn
|
||||
if client, err = getConn(); err != nil {
|
||||
return
|
||||
}
|
||||
defer putConn(client)
|
||||
var (
|
||||
refreshTokenStoreKey = append(refreshTokenStoreBaseKey, refreshToken.String()...)
|
||||
matchedRefreshTokenData = new(RefreshTokenData)
|
||||
)
|
||||
|
||||
if err = extractDataFromRedis(matchedRefreshTokenData, refreshTokenStoreKey, client); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if clientId.Equal(matchedRefreshTokenData.ClientId) {
|
||||
memberId = matchedRefreshTokenData.MemberId
|
||||
} else {
|
||||
sessionStoreKey := strconv.AppendUint(sessionStoreBaseKey, matchedRefreshTokenData.MemberId, 10)
|
||||
sessionData := new(SessionData) //newSessionData
|
||||
if errFetchSession := extractDataFromRedis(sessionData, sessionStoreKey, client); errFetchSession != nil {
|
||||
logger.Info("failed to AppTokenFindMemberIdByRefreshToken", errFetchSession)
|
||||
client.Send("DEL", refreshTokenStoreKey, sessionStoreKey)
|
||||
} else {
|
||||
tokenStoreKey := append(tokenStoreBaseKey, sessionData.Token.String()...)
|
||||
client.Send("DEL", refreshTokenStoreKey, sessionStoreKey, tokenStoreKey)
|
||||
}
|
||||
err = errors.New("matched token is invalid")
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
func AppTokenRefreshSessionByRefreshToken(clientId iface.UUID, refreshToken iface.UUID) (out *SessionData, err error) {
|
||||
var client resourceConn
|
||||
if client, err = getConn(); err != nil {
|
||||
return
|
||||
}
|
||||
defer putConn(client)
|
||||
|
||||
var (
|
||||
refreshTokenStoreKey = append(refreshTokenStoreBaseKey, refreshToken.String()...)
|
||||
prevRefreshTokenData = new(RefreshTokenData) //refreshTokenData
|
||||
prevSessionData = new(SessionData) //previousSessionData
|
||||
prevTokenData = new(TokenData) //previousTokenData
|
||||
|
||||
memberId uint64
|
||||
sessionStoreKey []byte
|
||||
prevTokenStoreKey []byte
|
||||
prevAnotherRefreshTokenStoreKey []byte
|
||||
)
|
||||
|
||||
if fetchErr := extractDataFromRedis(prevRefreshTokenData, refreshTokenStoreKey, client); fetchErr != nil {
|
||||
err = errors.New("matched refreshToken not found")
|
||||
return
|
||||
} else {
|
||||
memberId = prevRefreshTokenData.MemberId
|
||||
sessionStoreKey = strconv.AppendUint(sessionStoreBaseKey, memberId, 10)
|
||||
}
|
||||
|
||||
var tx = func() (err error) {
|
||||
if _, err = client.Do("WATCH", sessionStoreKey); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = extractDataFromRedis(prevSessionData, sessionStoreKey, client); err != nil {
|
||||
client.Send("MULTI")
|
||||
client.Send("DEL", refreshTokenStoreKey)
|
||||
if _, err = redis.Values(client.Do("EXEC")); err != nil {
|
||||
logger.Info("failed to AppTokenRefreshSessionByRefreshToken", err)
|
||||
}
|
||||
err = errors.New("matched sessionData not found")
|
||||
return
|
||||
} else {
|
||||
prevTokenStoreKey = append(tokenStoreBaseKey, prevSessionData.Token.String()...)
|
||||
prevAnotherRefreshTokenStoreKey = append(refreshTokenStoreBaseKey, prevSessionData.RefreshToken.String()...)
|
||||
}
|
||||
|
||||
if fetchErr := extractDataFromRedis(prevTokenData, prevTokenStoreKey, client); fetchErr != nil {
|
||||
prevTokenData = nil
|
||||
}
|
||||
|
||||
if prevRefreshTokenData.ClientId.Equal(clientId) &&
|
||||
prevSessionData.RefreshToken.Equal(refreshToken) &&
|
||||
(prevTokenData == nil || refreshToken.Equal(prevTokenData.RefreshToken)) {
|
||||
// 검증성공!
|
||||
var (
|
||||
newToken, _ = iface.NewUUIDV4() //token
|
||||
newTokenStoreKey = append(tokenStoreBaseKey, newToken.String()...) //tokenData
|
||||
|
||||
newTokenData []byte //tokenData
|
||||
newSessionData []byte //newSessionData
|
||||
newSession = &SessionData{ //newSessionData
|
||||
Token: newToken,
|
||||
RefreshToken: refreshToken,
|
||||
}
|
||||
)
|
||||
//newSession
|
||||
if newTokenData, err = (&TokenData{
|
||||
ClientId: clientId,
|
||||
MemberId: memberId,
|
||||
RefreshToken: refreshToken,
|
||||
}).Marshal(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if newSessionData, err = newSession.Marshal(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
client.Send("MULTI")
|
||||
|
||||
if prevTokenData != nil {
|
||||
client.Send("DEL", prevTokenStoreKey)
|
||||
}
|
||||
|
||||
client.Send("SETEX", newTokenStoreKey, tokenExpireTime, newTokenData)
|
||||
client.Send("SETEX", sessionStoreKey, refreshTokenExpireTime, newSessionData)
|
||||
client.Send("EXPIRE", refreshTokenStoreKey, refreshTokenExpireTime)
|
||||
|
||||
if _, err = redis.Values(client.Do("EXEC")); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out = newSession
|
||||
return
|
||||
} else {
|
||||
// 부정사용의혹!
|
||||
|
||||
client.Send("MULTI")
|
||||
// 부정사용의혹!
|
||||
if prevTokenData != nil {
|
||||
client.Send("DEL", refreshTokenStoreKey, sessionStoreKey, prevTokenStoreKey, prevAnotherRefreshTokenStoreKey)
|
||||
} else {
|
||||
client.Send("DEL", refreshTokenStoreKey, sessionStoreKey, prevAnotherRefreshTokenStoreKey)
|
||||
}
|
||||
if _, err = redis.Values(client.Do("EXEC")); err != nil {
|
||||
logger.Info("failed to AppTokenRefreshSessionByRefreshToken", err)
|
||||
}
|
||||
|
||||
err = errors.New("cannot refresh token")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
err = retries(tx)
|
||||
return
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"amuz.es/gogs/infra/rooibos/util"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"github.com/youtube/vitess/go/pools"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
pool *pools.ResourcePool = nil
|
||||
logger = util.NewLogger("redis")
|
||||
)
|
||||
|
||||
type resourceConn struct {
|
||||
redis.Conn
|
||||
}
|
||||
|
||||
func (r resourceConn) Close() {
|
||||
r.Conn.Close()
|
||||
}
|
||||
|
||||
func InitRedis(config *util.RedisConfig) {
|
||||
connectStr := fmt.Sprintf("%s:%d",
|
||||
config.Host,
|
||||
config.Port)
|
||||
logger.Debugf("connect to redis://%s:%d/%d",
|
||||
config.Host,
|
||||
config.Port,
|
||||
config.DB)
|
||||
|
||||
pool = pools.NewResourcePool(func() (pools.Resource, error) {
|
||||
c, err := redis.Dial("tcp", connectStr,
|
||||
redis.DialDatabase(config.DB),
|
||||
redis.DialPassword(config.Password),
|
||||
)
|
||||
return resourceConn{c}, err
|
||||
}, 10, 10, time.Minute)
|
||||
|
||||
logger.Info("Redis connected")
|
||||
}
|
||||
|
||||
func getConn() (conn resourceConn, err error) {
|
||||
ctx := context.TODO()
|
||||
var resource pools.Resource
|
||||
if resource, err = pool.Get(ctx); err == nil {
|
||||
res := resource.(resourceConn)
|
||||
conn = res
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func putConn(conn resourceConn) {
|
||||
pool.Put(conn)
|
||||
}
|
||||
|
||||
func CloseRedis() {
|
||||
if pool != nil {
|
||||
pool.Close()
|
||||
logger.Info("Redis closed")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
retry "github.com/giantswarm/retry-go"
|
||||
errs "github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type txError error
|
||||
|
||||
func newTxError(err error) error {
|
||||
return newTxErrorWithMessage("key had changed during this tx", err)
|
||||
}
|
||||
|
||||
func newTxErrorWithMessage(message string, err error) error {
|
||||
var wrapped error
|
||||
if err == nil {
|
||||
wrapped = errs.New(message)
|
||||
} else {
|
||||
wrapped = errs.Wrap(err, message)
|
||||
}
|
||||
return wrapped
|
||||
}
|
||||
|
||||
func retries(tx func() error) error {
|
||||
return retry.Do(tx,
|
||||
retry.RetryChecker(func(err error) (ret bool) {
|
||||
switch err.(type) {
|
||||
case txError:
|
||||
ret = true
|
||||
}
|
||||
return
|
||||
}),
|
||||
retry.Timeout(400*time.Millisecond),
|
||||
retry.AfterRetry(func(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
sleeps := 37 + rand.Intn(51)
|
||||
time.Sleep(time.Duration(sleeps) * time.Millisecond)
|
||||
}),
|
||||
retry.MaxTries(5),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"amuz.es/gogs/infra/rooibos/util/iface"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
func extractDataFromRedis(target iface.Serializable, key []byte, fetcher resourceConn) error {
|
||||
if resp, err := redis.Bytes(fetcher.Do("GET", key)); err != nil {
|
||||
return err
|
||||
} else if err = target.Unmarshal(resp); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type LogConfig struct {
|
||||
FileName string
|
||||
MaxSizeMb int
|
||||
MaxBackup int
|
||||
MaxDay int
|
||||
}
|
||||
|
||||
type DbConfig struct {
|
||||
Network string
|
||||
Driver string
|
||||
Host string
|
||||
User string
|
||||
Password string
|
||||
Port uint16
|
||||
DB string
|
||||
Args string
|
||||
}
|
||||
|
||||
type RedisConfig struct {
|
||||
Host string
|
||||
Port uint16
|
||||
Password string
|
||||
DB int
|
||||
MaxRetries int
|
||||
PoolSize int
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Phase string
|
||||
Bind string
|
||||
Db DbConfig
|
||||
Redis RedisConfig
|
||||
Logging struct {
|
||||
Application LogConfig
|
||||
Access LogConfig
|
||||
}
|
||||
}
|
||||
|
||||
func (c config) Name() string {
|
||||
return name
|
||||
}
|
||||
|
||||
func (c config) Version() string {
|
||||
return version
|
||||
}
|
||||
|
||||
func (c config) BuildDate() string {
|
||||
return buildDate
|
||||
}
|
||||
|
||||
const name = "Rooibos"
|
||||
|
||||
var (
|
||||
buildDate string
|
||||
version string
|
||||
Config = config{}
|
||||
)
|
||||
|
||||
func LoadConfig(path string) error {
|
||||
var (
|
||||
source []byte
|
||||
err error
|
||||
)
|
||||
if source, err = ioutil.ReadFile(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = yaml.Unmarshal(source, &Config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package util
|
||||
|
||||
import "github.com/coreos/go-systemd/daemon"
|
||||
|
||||
type notifyType string
|
||||
|
||||
const (
|
||||
DaemonStarted notifyType = "READY=1"
|
||||
DaemonStopping notifyType = "STOPPING=1"
|
||||
)
|
||||
|
||||
var sockPath string
|
||||
|
||||
func NotifyDaemon(status notifyType) {
|
||||
daemon.SdNotify(false, string(status))
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package iface
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (b Bytes16) Marshal() ([]byte, error) {
|
||||
return b[:], nil
|
||||
}
|
||||
|
||||
func (b Bytes16) MarshalTo(buf []byte) (n int, err error) {
|
||||
copy(buf, b[:])
|
||||
return len(b), nil
|
||||
}
|
||||
func (b *Bytes16) Unmarshal(buf []byte) error {
|
||||
if len(buf) != Bytes16Length {
|
||||
return fmt.Errorf("invalid bytes16 (got %d bytes)", len(buf))
|
||||
}
|
||||
copy(b[:], buf)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bytes16) UnmarshalHex(buf []byte) (err error) {
|
||||
hexLen := hex.DecodedLen(len(buf))
|
||||
if hexLen != Bytes16Length {
|
||||
return fmt.Errorf("invalid bytes16 (got %d bytes)", len(buf))
|
||||
|
||||
}
|
||||
_, err = hex.Decode(b[:], buf)
|
||||
return
|
||||
}
|
||||
|
||||
func (b Bytes16) MarshalHex() ([]byte, error) {
|
||||
hexLen := hex.EncodedLen(Bytes16Length)
|
||||
hexBuf := make([]byte,hexLen)
|
||||
if encodedLen := hex.Encode(hexBuf, b[:]); encodedLen != hexLen {
|
||||
return nil, fmt.Errorf("invalid bytes16 length: %d", len(hexBuf))
|
||||
}
|
||||
return hexBuf, nil
|
||||
}
|
||||
|
||||
func (b Bytes16) MarshalJSON() (encoded []byte, err error) {
|
||||
encoded, err = b.MarshalHex()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteRune('"')
|
||||
buffer.Write(encoded)
|
||||
buffer.WriteRune('"')
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
func (b *Bytes16) UnmarshalJSON(from []byte) error {
|
||||
|
||||
quote := []byte("\"")
|
||||
quoteSize := len(quote)
|
||||
|
||||
if len(from) < quoteSize*2 {
|
||||
return errors.New("invalid quote notation")
|
||||
}
|
||||
|
||||
if !bytes.HasPrefix(from, quote) || !bytes.HasSuffix(from, quote) {
|
||||
return errors.New("invalid quote notation")
|
||||
}
|
||||
return b.UnmarshalHex(from[quoteSize:len(from)-quoteSize])
|
||||
}
|
||||
|
||||
func (b Bytes16) Compare(other Bytes16) int {
|
||||
return bytes.Compare(b[:], other[:])
|
||||
}
|
||||
|
||||
func (b Bytes16) Equal(other Bytes16) bool {
|
||||
return b.Compare(other) == 0
|
||||
}
|
||||
|
||||
func (b *Bytes16) Size() int {
|
||||
if b == nil {
|
||||
return 0
|
||||
}
|
||||
return Bytes16Length
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (b *Bytes16) Scan(src interface{}) error {
|
||||
switch src.(type) {
|
||||
case string:
|
||||
b.UnmarshalHex([]byte(src.(string)))
|
||||
default:
|
||||
return errors.New("Incompatible type for UUID")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (b Bytes16) Value() (driver.Value, error) {
|
||||
return b.MarshalHex()
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package iface
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (b Bytes32) Marshal() ([]byte, error) {
|
||||
return b[:], nil
|
||||
}
|
||||
|
||||
func (b Bytes32) MarshalTo(buf []byte) (n int, err error) {
|
||||
copy(buf, b[:])
|
||||
return len(b), nil
|
||||
}
|
||||
func (b *Bytes32) Unmarshal(buf []byte) error {
|
||||
if len(buf) != Bytes32Length {
|
||||
return fmt.Errorf("invalid bytes32 (got %d bytes)", len(buf))
|
||||
}
|
||||
copy(b[:], buf)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bytes32) UnmarshalHex(buf []byte) (err error) {
|
||||
hexLen := hex.DecodedLen(len(buf))
|
||||
if hexLen != Bytes32Length {
|
||||
return fmt.Errorf("invalid bytes32 (got %d bytes)", len(buf))
|
||||
|
||||
}
|
||||
_, err = hex.Decode(b[:], buf)
|
||||
return
|
||||
}
|
||||
|
||||
func (b Bytes32) MarshalHex() ([]byte, error) {
|
||||
hexLen := hex.EncodedLen(Bytes32Length)
|
||||
hexBuf := make([]byte,hexLen)
|
||||
if encodedLen := hex.Encode(hexBuf, b[:]); encodedLen != hexLen {
|
||||
return nil, fmt.Errorf("invalid bytes32 length: %d", len(hexBuf))
|
||||
}
|
||||
return hexBuf, nil
|
||||
}
|
||||
|
||||
func (b Bytes32) MarshalJSON() (encoded []byte, err error) {
|
||||
encoded, err = b.MarshalHex()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteRune('"')
|
||||
buffer.Write(encoded)
|
||||
buffer.WriteRune('"')
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
func (b *Bytes32) UnmarshalJSON(from []byte) error {
|
||||
|
||||
quote:=[]byte("\"")
|
||||
quoteSize:=len(quote)
|
||||
|
||||
if len(from) < quoteSize*2{
|
||||
return errors.New("invalid quote notation")
|
||||
}
|
||||
|
||||
if !bytes.HasPrefix(from,quote) ||!bytes.HasSuffix(from,quote){
|
||||
return errors.New("invalid quote notation")
|
||||
}
|
||||
return b.UnmarshalHex(from[quoteSize:len(from)-quoteSize])
|
||||
}
|
||||
|
||||
func (b Bytes32) Compare(other Bytes32) int {
|
||||
return bytes.Compare(b[:], other[:])
|
||||
}
|
||||
|
||||
func (b Bytes32) Equal(other Bytes32) bool {
|
||||
return b.Compare(other) == 0
|
||||
}
|
||||
|
||||
func (b *Bytes32) Size() int {
|
||||
if b == nil {
|
||||
return 0
|
||||
}
|
||||
return Bytes32Length
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (b *Bytes32) Scan(src interface{}) error {
|
||||
switch src.(type) {
|
||||
case string:
|
||||
b.UnmarshalHex([]byte(src.(string)))
|
||||
default:
|
||||
return errors.New("Incompatible type for UUID")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (b Bytes32) Value() (driver.Value, error) {
|
||||
return b.MarshalHex()
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package iface
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (b Bytes64) Marshal() ([]byte, error) {
|
||||
return b[:], nil
|
||||
}
|
||||
|
||||
func (b Bytes64) MarshalTo(buf []byte) (n int, err error) {
|
||||
copy(buf, b[:])
|
||||
return len(b), nil
|
||||
}
|
||||
func (b *Bytes64) Unmarshal(buf []byte) error {
|
||||
if len(buf) != Bytes64Length {
|
||||
return fmt.Errorf("invalid bytes64 (got %d bytes)", len(buf))
|
||||
}
|
||||
copy(b[:], buf)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bytes64) UnmarshalHex(buf []byte) (err error) {
|
||||
hexLen := hex.DecodedLen(len(buf))
|
||||
if hexLen != Bytes64Length {
|
||||
return fmt.Errorf("invalid bytes64 (got %d bytes)", len(buf))
|
||||
|
||||
}
|
||||
_, err = hex.Decode(b[:], buf)
|
||||
return
|
||||
}
|
||||
|
||||
func (b Bytes64) MarshalHex() ([]byte, error) {
|
||||
hexLen := hex.EncodedLen(Bytes64Length)
|
||||
hexBuf := make([]byte,hexLen)
|
||||
if encodedLen := hex.Encode(hexBuf, b[:]); encodedLen != hexLen {
|
||||
return nil, fmt.Errorf("invalid bytes64 length: %d", len(hexBuf))
|
||||
}
|
||||
return hexBuf, nil
|
||||
}
|
||||
|
||||
func (b Bytes64) MarshalJSON()(encoded []byte, err error) {
|
||||
encoded, err = b.MarshalHex()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteRune('"')
|
||||
buffer.Write(encoded)
|
||||
buffer.WriteRune('"')
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
func (b *Bytes64) UnmarshalJSON(from []byte) error {
|
||||
quote:=[]byte("\"")
|
||||
quoteSize:=len(quote)
|
||||
|
||||
if len(from) < quoteSize*2{
|
||||
return errors.New("invalid quote notation")
|
||||
}
|
||||
|
||||
if !bytes.HasPrefix(from,quote) ||!bytes.HasSuffix(from,quote){
|
||||
return errors.New("invalid quote notation")
|
||||
}
|
||||
return b.UnmarshalHex(from[quoteSize:len(from)-quoteSize])
|
||||
}
|
||||
|
||||
func (b Bytes64) Compare(other Bytes64) int {
|
||||
return bytes.Compare(b[:], other[:])
|
||||
}
|
||||
|
||||
func (b Bytes64) Equal(other Bytes64) bool {
|
||||
return b.Compare(other) == 0
|
||||
}
|
||||
|
||||
func (b *Bytes64) Size() int {
|
||||
if b == nil {
|
||||
return 0
|
||||
}
|
||||
return Bytes64Length
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (b *Bytes64) Scan(src interface{}) error {
|
||||
switch src.(type) {
|
||||
case string:
|
||||
b.UnmarshalHex([]byte(src.(string)))
|
||||
default:
|
||||
return errors.New("Incompatible type for UUID")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (b Bytes64) Value() (driver.Value, error) {
|
||||
return b.MarshalHex()
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package iface
|
||||
|
||||
import (
|
||||
"amuz.es/gogs/infra/rooibos/util"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const Bytes16Length = 16
|
||||
const Bytes32Length = 32
|
||||
const Bytes64Length = 64
|
||||
const UUIDLength = 16
|
||||
|
||||
type Bytes16 [Bytes16Length]byte
|
||||
type Bytes32 [Bytes32Length]byte
|
||||
type Bytes64 [Bytes64Length]byte
|
||||
type UUID struct {
|
||||
uuid.UUID
|
||||
}
|
||||
|
||||
var logger = util.NewLogger("common_iface")
|
||||
|
||||
type Serializable interface {
|
||||
Marshal() ([]byte, error)
|
||||
Unmarshal(buf []byte) error
|
||||
String() string
|
||||
}
|
||||
|
||||
|
||||
|
||||
type JSONSerializable interface {
|
||||
UnmarshalJSON(b []byte) error
|
||||
MarshalJSON() ([]byte, error)
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package iface
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"github.com/google/uuid"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (u UUID) Marshal() ([]byte, error) {
|
||||
return u.UUID[:], nil
|
||||
}
|
||||
|
||||
func (u UUID) MarshalTo(buf []byte) (n int, err error) {
|
||||
copy(buf, u.UUID[:])
|
||||
return len(u.UUID), nil
|
||||
}
|
||||
|
||||
func (u *UUID) Unmarshal(buf []byte) error {
|
||||
if len(buf) != UUIDLength {
|
||||
return fmt.Errorf("invalid bytes16 (got %d bytes)", len(buf))
|
||||
}
|
||||
copy(u.UUID[:], buf)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UUID) UnmarshalHex(buf []byte) (err error) {
|
||||
hexLen := hex.DecodedLen(len(buf))
|
||||
if hexLen != UUIDLength {
|
||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(buf))
|
||||
|
||||
}
|
||||
_, err = hex.Decode(u.UUID[:], buf)
|
||||
return
|
||||
}
|
||||
|
||||
func (u *UUID) MarshalHex() ([]byte, error) {
|
||||
hexLen := hex.EncodedLen(UUIDLength)
|
||||
hexBuf := make([]byte,hexLen)
|
||||
if encodedLen := hex.Encode(hexBuf, u.UUID[:]); encodedLen != hexLen {
|
||||
return nil, fmt.Errorf("invalid bytes16 length: %d", len(hexBuf))
|
||||
}
|
||||
return hexBuf, nil
|
||||
}
|
||||
|
||||
func (u UUID) MarshalJSON() (encoded []byte, err error) {
|
||||
encoded, err = u.MarshalHex()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteRune('"')
|
||||
buffer.Write(encoded)
|
||||
buffer.WriteRune('"')
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
func (u *UUID) UnmarshalJSON(from []byte) error {
|
||||
quote := []byte("\"")
|
||||
quoteSize := len(quote)
|
||||
|
||||
if len(from) < quoteSize*2 {
|
||||
return errors.New("invalid quote notation")
|
||||
}
|
||||
|
||||
if !bytes.HasPrefix(from, quote) || !bytes.HasSuffix(from, quote) {
|
||||
return errors.New("invalid quote notation")
|
||||
}
|
||||
return u.UnmarshalHex(from[quoteSize:len(from)-quoteSize])
|
||||
}
|
||||
|
||||
func (u UUID) Compare(other UUID) int {
|
||||
return bytes.Compare(u.UUID[:], other.UUID[:])
|
||||
}
|
||||
|
||||
func (u UUID) Equal(other UUID) bool {
|
||||
return u.Compare(other) == 0
|
||||
}
|
||||
|
||||
func (u *UUID) Size() int {
|
||||
if u == nil {
|
||||
return 0
|
||||
}
|
||||
return UUIDLength
|
||||
}
|
||||
|
||||
func NewUUIDV4() (u UUID, err error) {
|
||||
var ui uuid.UUID
|
||||
if ui, err = uuid.NewRandom(); err == nil {
|
||||
u.UUID = ui
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (u *UUID) Scan(src interface{}) error {
|
||||
switch src.(type) {
|
||||
case string:
|
||||
u.UnmarshalHex([]byte(src.(string)))
|
||||
default:
|
||||
return errors.New("Incompatible type for UUID")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (u UUID) Value() (driver.Value, error) {
|
||||
return u.MarshalHex()
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (u UUID) Random() {
|
||||
u.UUID = uuid.New()
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
var jsonContentType = []string{"application/json; charset=utf-8"}
|
||||
|
||||
func writeContentType(w http.ResponseWriter, value []string) {
|
||||
header := w.Header()
|
||||
if val := header["Content-Type"]; len(val) == 0 {
|
||||
header["Content-Type"] = value
|
||||
}
|
||||
}
|
||||
|
||||
func DumpJSONHeaderOnly(c *gin.Context, code int) {
|
||||
c.Status(code)
|
||||
|
||||
// Encode
|
||||
header := c.Writer.Header()
|
||||
if val := header["Content-Type"]; len(val) == 0 {
|
||||
header["Content-Type"] = jsonContentType
|
||||
}
|
||||
}
|
||||
|
||||
func DumpJSON(c *gin.Context, code int, serializable interface{}) {
|
||||
c.Status(code)
|
||||
|
||||
// Encode
|
||||
header := c.Writer.Header()
|
||||
if val := header["Content-Type"]; len(val) == 0 {
|
||||
header["Content-Type"] = jsonContentType
|
||||
}
|
||||
|
||||
if enc := json.NewEncoder(c.Writer); enc == nil {
|
||||
panic(errors.New("empty json encoder"))
|
||||
} else if err := enc.Encode(serializable); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func LoadJSON(data []byte, deserializable interface{}) (err error) {
|
||||
if reader := bytes.NewReader(data); reader == nil {
|
||||
err = errors.New("empty json data")
|
||||
} else {
|
||||
err = LoadJSONReader(reader, deserializable)
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func LoadJSONReader(r io.Reader, deserializable interface{}) (err error) {
|
||||
decoder := json.NewDecoder(r)
|
||||
|
||||
return decoder.Decode(deserializable)
|
||||
}
|
||||
|
||||
func BindJSON(c *gin.Context, deserializable interface{}) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.AbortWithError(400, err).SetType(gin.ErrorTypeBind)
|
||||
}
|
||||
}()
|
||||
if err = LoadJSONReader(c.Request.Body, deserializable); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// validate(obj)
|
||||
|
||||
if binding.Validator == nil {
|
||||
return nil
|
||||
}
|
||||
return binding.Validator.ValidateStruct(deserializable)
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
prefixed "github.com/x-cray/logrus-prefixed-formatter"
|
||||
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
logger = logrus.StandardLogger()
|
||||
rotaters []*lumberjack.Logger
|
||||
logDir string
|
||||
formatter = prefixed.TextFormatter{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
logger.Formatter = &formatter
|
||||
}
|
||||
func LoggerIsStd() bool {
|
||||
return !formatter.DisableColors
|
||||
}
|
||||
func InitLogger(verbose bool, logDirArg string, config *LogConfig) {
|
||||
logDir = logDirArg
|
||||
colorSupport, writer := NewLogWriter(config)
|
||||
formatter.DisableColors = !colorSupport
|
||||
logger.Out = writer
|
||||
if verbose {
|
||||
logger.Level = logrus.DebugLevel
|
||||
} else {
|
||||
logger.Level = logrus.InfoLevel
|
||||
}
|
||||
}
|
||||
|
||||
func NewLogger(prefix string) *logrus.Entry {
|
||||
return logrus.NewEntry(logger).WithField("prefix", prefix)
|
||||
}
|
||||
func NewLogWriter(config *LogConfig) (bool, io.Writer) {
|
||||
switch config.FileName {
|
||||
case "Stdout":
|
||||
return true, os.Stdout
|
||||
case "Stderr":
|
||||
return true, os.Stderr
|
||||
default:
|
||||
logpath := config.FileName
|
||||
if logDir != "" {
|
||||
logpath = path.Join(logDir, config.FileName)
|
||||
}
|
||||
logger.Info(" Attention!! log writes to ", config.FileName)
|
||||
rotater := &lumberjack.Logger{
|
||||
Filename: logpath,
|
||||
MaxSize: config.MaxSizeMb, // megabytes
|
||||
MaxBackups: config.MaxBackup,
|
||||
MaxAge: config.MaxDay, //days
|
||||
}
|
||||
rotaters = append(rotaters, rotater)
|
||||
return false, rotater
|
||||
}
|
||||
}
|
||||
|
||||
func RotateLogger() {
|
||||
if len(rotaters) == 0 {
|
||||
return
|
||||
}
|
||||
logger.Info("rotating logger")
|
||||
for _, rotater := range rotaters {
|
||||
rotater.Rotate()
|
||||
}
|
||||
logger.Info("rotated")
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func mustParseInt(value string, bits int) int64 {
|
||||
if parsed, err := strconv.ParseInt(value, 10, bits); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
|
||||
func mustParseUint(value string, bits int) uint64 {
|
||||
if parsed, err := strconv.ParseUint(value, 10, bits); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
|
||||
func ParseUint8(value string) (ret *uint8) {
|
||||
|
||||
if parsed, err := strconv.ParseUint(value, 10, 8); err == nil {
|
||||
ret = new(uint8)
|
||||
*ret = uint8(parsed)
|
||||
}
|
||||
return
|
||||
}
|
||||
func ParseUint16(value string) (ret *uint16) {
|
||||
|
||||
if parsed, err := strconv.ParseUint(value, 10, 16); err == nil {
|
||||
ret = new(uint16)
|
||||
*ret = uint16(parsed)
|
||||
}
|
||||
return
|
||||
}
|
||||
func ParseUint32(value string) (ret *uint32) {
|
||||
|
||||
if parsed, err := strconv.ParseUint(value, 10, 32); err == nil {
|
||||
ret = new(uint32)
|
||||
*ret = uint32(parsed)
|
||||
}
|
||||
return
|
||||
}
|
||||
func ParseUint64(value string) (ret *uint64) {
|
||||
|
||||
if parsed, err := strconv.ParseUint(value, 10, 64); err == nil {
|
||||
ret = new(uint64)
|
||||
*ret = uint64(parsed)
|
||||
}
|
||||
return
|
||||
}
|
||||
func ParseUint(value string) (ret *uint) {
|
||||
|
||||
if parsed, err := strconv.ParseUint(value, 10, 0); err == nil {
|
||||
ret = new(uint)
|
||||
*ret = uint(parsed)
|
||||
}
|
||||
return
|
||||
}
|
||||
func ParseInt8(value string) (ret *int8) {
|
||||
|
||||
if parsed, err := strconv.ParseInt(value, 10, 8); err == nil {
|
||||
ret = new(int8)
|
||||
*ret = int8(parsed)
|
||||
}
|
||||
return
|
||||
}
|
||||
func ParseInt16(value string) (ret *int16) {
|
||||
|
||||
if parsed, err := strconv.ParseInt(value, 10, 16); err == nil {
|
||||
ret = new(int16)
|
||||
*ret = int16(parsed)
|
||||
}
|
||||
return
|
||||
}
|
||||
func ParseInt32(value string) (ret *int32) {
|
||||
|
||||
if parsed, err := strconv.ParseInt(value, 10, 32); err == nil {
|
||||
ret = new(int32)
|
||||
*ret = int32(parsed)
|
||||
}
|
||||
return
|
||||
}
|
||||
func ParseInt64(value string) (ret *int64) {
|
||||
|
||||
if parsed, err := strconv.ParseInt(value, 10, 64); err == nil {
|
||||
ret = new(int64)
|
||||
*ret = int64(parsed)
|
||||
}
|
||||
return
|
||||
}
|
||||
func ParseInt(value string) (ret *int) {
|
||||
if parsed, err := strconv.ParseInt(value, 10, 0); err == nil {
|
||||
ret = new(int)
|
||||
*ret = int(parsed)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ParseUint8Must(value string) uint8 {
|
||||
return uint8(mustParseUint(value, 8))
|
||||
}
|
||||
func ParseUint16Must(value string) uint16 {
|
||||
return uint16(mustParseUint(value, 16))
|
||||
}
|
||||
func ParseUint32Must(value string) uint32 {
|
||||
return uint32(mustParseUint(value, 32))
|
||||
}
|
||||
func ParseUint64Must(value string) uint64 {
|
||||
return mustParseUint(value, 64)
|
||||
}
|
||||
|
||||
func ParseUintMust(value string) uint {
|
||||
return uint(mustParseUint(value, 0))
|
||||
}
|
||||
|
||||
func ParseInt8Must(value string) int8 {
|
||||
return int8(mustParseInt(value, 8))
|
||||
}
|
||||
|
||||
func ParseInt16Must(value string) int16 {
|
||||
return int16(mustParseInt(value, 16))
|
||||
}
|
||||
|
||||
func ParseInt32Must(value string) int32 {
|
||||
return int32(mustParseInt(value, 32))
|
||||
}
|
||||
|
||||
func ParseInt64Must(value string) int64 {
|
||||
return mustParseInt(value, 64)
|
||||
}
|
||||
|
||||
func ParseIntMust(value string) int {
|
||||
return int(mustParseInt(value, 0))
|
||||
}
|
|
@ -0,0 +1,313 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"image"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func Int64ToBytes(s int64) []byte {
|
||||
return []byte{
|
||||
byte(s >> 0),
|
||||
byte(s >> 8),
|
||||
byte(s >> 16),
|
||||
byte(s >> 24),
|
||||
byte(s >> 32),
|
||||
byte(s >> 40),
|
||||
byte(s >> 48),
|
||||
byte(s >> 56),
|
||||
}
|
||||
}
|
||||
|
||||
func BytesToInt64(s []byte) int64 {
|
||||
return int64(0) |
|
||||
(int64(s[0]) << 0) |
|
||||
(int64(s[1]) << 8) |
|
||||
(int64(s[2]) << 16) |
|
||||
(int64(s[3]) << 24) |
|
||||
(int64(s[4]) << 32) |
|
||||
(int64(s[5]) << 40) |
|
||||
(int64(s[6]) << 48) |
|
||||
(int64(s[7]) << 56)
|
||||
}
|
||||
|
||||
func BytesToString(b []byte) string {
|
||||
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
sh := reflect.StringHeader{
|
||||
Data: bh.Data,
|
||||
Len: bh.Len}
|
||||
return *(*string)(unsafe.Pointer(&sh))
|
||||
}
|
||||
|
||||
func StringToBytes(s string) []byte {
|
||||
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
bh := reflect.SliceHeader{
|
||||
Data: sh.Data,
|
||||
Len: sh.Len,
|
||||
Cap: 0}
|
||||
return *(*[]byte)(unsafe.Pointer(&bh))
|
||||
}
|
||||
|
||||
// GetRandomString generate random string by specify chars.
|
||||
func GetRandomString(n int, alphabets ...byte) string {
|
||||
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
var bytes = make([]byte, 0, n)
|
||||
rand.Read(bytes)
|
||||
for _, b := range bytes {
|
||||
if len(alphabets) == 0 {
|
||||
bytes = append(bytes, alphanum[b%byte(len(alphanum))])
|
||||
} else {
|
||||
bytes = append(bytes, alphabets[b%byte(len(alphabets))])
|
||||
}
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
// Seconds-based time units
|
||||
var (
|
||||
NanoSecond uint64 = 1
|
||||
MicroSecond = 1000 * NanoSecond
|
||||
MilliSecond = 1000 * MicroSecond
|
||||
Second = 1000 * MilliSecond
|
||||
Minute = 60 * Second
|
||||
Hour = 60 * Minute
|
||||
Day = 24 * Hour
|
||||
Week = 7 * Day
|
||||
Month = 30 * Day
|
||||
Year = 12 * Month
|
||||
)
|
||||
|
||||
func computeTimeDiff(buff *bytes.Buffer, diff uint64) uint64 {
|
||||
switch {
|
||||
case diff < 1*NanoSecond:
|
||||
diff = 0
|
||||
case diff < 2*NanoSecond:
|
||||
diff = 0
|
||||
buff.WriteString("001ns")
|
||||
case diff < 1*MicroSecond:
|
||||
buff.WriteString(fmt.Sprintf("%03dns", diff))
|
||||
diff -= diff / NanoSecond * NanoSecond
|
||||
case diff < 2*MicroSecond:
|
||||
diff -= 1 * MilliSecond
|
||||
buff.WriteString("001us")
|
||||
case diff < 1*MilliSecond:
|
||||
buff.WriteString(fmt.Sprintf("%03dus", diff/MicroSecond))
|
||||
diff -= diff / MicroSecond * MicroSecond
|
||||
case diff < 2*MilliSecond:
|
||||
diff -= 1 * Second
|
||||
buff.WriteString("001ms")
|
||||
case diff < 1*Second:
|
||||
buff.WriteString(fmt.Sprintf("%03dms", diff/MilliSecond))
|
||||
diff -= diff / MilliSecond * MilliSecond
|
||||
case diff < 2*Second:
|
||||
diff -= 1 * Minute
|
||||
buff.WriteString("01s")
|
||||
case diff < 1*Minute:
|
||||
buff.WriteString(fmt.Sprintf("%02ds", diff/Second))
|
||||
diff -= diff / Second * Second
|
||||
case diff < 2*Minute:
|
||||
diff -= 1 * Minute
|
||||
buff.WriteString("01m")
|
||||
case diff < 1*Hour:
|
||||
buff.WriteString(fmt.Sprintf("%02dm", diff/Minute))
|
||||
diff -= diff / Minute * Minute
|
||||
case diff < 2*Hour:
|
||||
diff -= 1 * Hour
|
||||
buff.WriteString("01h")
|
||||
case diff < 1*Day:
|
||||
buff.WriteString(fmt.Sprintf("%02dh", diff/Hour))
|
||||
diff -= diff / Hour * Hour
|
||||
case diff < 2*Day:
|
||||
diff -= 1 * Day
|
||||
buff.WriteString("01d")
|
||||
case diff < 1*Week:
|
||||
buff.WriteString(fmt.Sprintf("%02dd", diff/Day))
|
||||
diff -= diff / Day * Day
|
||||
case diff < 2*Week:
|
||||
diff -= 1 * Week
|
||||
buff.WriteString("1w")
|
||||
case diff < 1*Month:
|
||||
buff.WriteString(fmt.Sprintf("%01dw", diff/Week))
|
||||
diff -= diff / Week * Week
|
||||
|
||||
case diff < 2*Month:
|
||||
diff -= 1 * Month
|
||||
buff.WriteString("01M")
|
||||
case diff < 1*Year:
|
||||
buff.WriteString(fmt.Sprintf("%02dM", diff/Month))
|
||||
diff -= diff / Month * Month
|
||||
|
||||
case diff < 2*Year:
|
||||
diff -= 1 * Year
|
||||
buff.WriteString("01y")
|
||||
default:
|
||||
buff.WriteString(fmt.Sprintf("%02dy", diff/Year))
|
||||
diff = 0
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
// TimeSincePro calculates the time interval and generate full user-friendly string.
|
||||
func TimeSincePro(then time.Time) string {
|
||||
now := time.Now()
|
||||
diff := uint64(now.UnixNano() - then.UnixNano())
|
||||
|
||||
if then.After(now) {
|
||||
return "future"
|
||||
}
|
||||
|
||||
return TimeSinceDuration(diff)
|
||||
}
|
||||
|
||||
// TimeSincePro calculates the time interval and generate full user-friendly string.
|
||||
func TimeSinceDuration(then uint64) string {
|
||||
var (
|
||||
timeStr bytes.Buffer
|
||||
diff = then
|
||||
)
|
||||
|
||||
for {
|
||||
diff = computeTimeDiff(&timeStr, diff)
|
||||
if diff == 0 {
|
||||
break
|
||||
} else {
|
||||
timeStr.WriteRune(' ')
|
||||
}
|
||||
}
|
||||
if timeStr.Len() == 0 {
|
||||
return "instant!"
|
||||
|
||||
} else {
|
||||
return timeStr.String()
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
Byte = 1
|
||||
KByte = Byte * 1024
|
||||
MByte = KByte * 1024
|
||||
GByte = MByte * 1024
|
||||
TByte = GByte * 1024
|
||||
PByte = TByte * 1024
|
||||
EByte = PByte * 1024
|
||||
)
|
||||
|
||||
var bytesSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kb": KByte,
|
||||
"mb": MByte,
|
||||
"gb": GByte,
|
||||
"tb": TByte,
|
||||
"pb": PByte,
|
||||
"eb": EByte,
|
||||
}
|
||||
|
||||
func logn(n, b float64) float64 {
|
||||
return math.Log(n) / math.Log(b)
|
||||
}
|
||||
|
||||
func humanateBytes(s uint64, base float64, sizes []string) string {
|
||||
if s < 10 {
|
||||
return fmt.Sprintf("%dB", s)
|
||||
}
|
||||
e := math.Floor(logn(float64(s), base))
|
||||
suffix := sizes[int(e)]
|
||||
val := float64(s) / math.Pow(base, math.Floor(e))
|
||||
f := "%.0f"
|
||||
if val < 10 {
|
||||
f = "%.1f"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f+"%s", val, suffix)
|
||||
}
|
||||
|
||||
// FileSize calculates the file size and generate user-friendly string.
|
||||
func FileSize(s uint64) string {
|
||||
sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}
|
||||
return humanateBytes(s, 1024, sizes)
|
||||
}
|
||||
|
||||
// Subtract deals with subtraction of all types of number.
|
||||
func Subtract(left interface{}, right interface{}) interface{} {
|
||||
var rleft, rright int64
|
||||
var fleft, fright float64
|
||||
var isInt bool = true
|
||||
switch left.(type) {
|
||||
case int:
|
||||
rleft = int64(left.(int))
|
||||
case int8:
|
||||
rleft = int64(left.(int8))
|
||||
case int16:
|
||||
rleft = int64(left.(int16))
|
||||
case int32:
|
||||
rleft = int64(left.(int32))
|
||||
case int64:
|
||||
rleft = left.(int64)
|
||||
case float32:
|
||||
fleft = float64(left.(float32))
|
||||
isInt = false
|
||||
case float64:
|
||||
fleft = left.(float64)
|
||||
isInt = false
|
||||
}
|
||||
|
||||
switch right.(type) {
|
||||
case int:
|
||||
rright = int64(right.(int))
|
||||
case int8:
|
||||
rright = int64(right.(int8))
|
||||
case int16:
|
||||
rright = int64(right.(int16))
|
||||
case int32:
|
||||
rright = int64(right.(int32))
|
||||
case int64:
|
||||
rright = right.(int64)
|
||||
case float32:
|
||||
fright = float64(left.(float32))
|
||||
isInt = false
|
||||
case float64:
|
||||
fleft = left.(float64)
|
||||
isInt = false
|
||||
}
|
||||
|
||||
if isInt {
|
||||
return rleft - rright
|
||||
} else {
|
||||
return fleft + float64(rleft) - (fright + float64(rright))
|
||||
}
|
||||
}
|
||||
|
||||
// EllipsisString returns a truncated short string,
|
||||
// it appends '...' in the end of the length of string is too large.
|
||||
func EllipsisString(str string, length int) string {
|
||||
if len(str) < length {
|
||||
return str
|
||||
}
|
||||
return str[:length-3] + "..."
|
||||
}
|
||||
|
||||
func AspectRatio(srcRect image.Point, toResize uint64) image.Point {
|
||||
w, h := int(toResize), getRatioSize(int(toResize), srcRect.Y, srcRect.X)
|
||||
if srcRect.X < srcRect.Y {
|
||||
w, h = getRatioSize(int(toResize), srcRect.X, srcRect.Y), int(toResize)
|
||||
}
|
||||
return image.Point{w, h}
|
||||
}
|
||||
func getRatioSize(a, b, c int) int {
|
||||
d := a * b / c
|
||||
return (d + 1) & -1
|
||||
}
|
||||
|
||||
func Basename(s string) string {
|
||||
n := strings.LastIndexByte(s, '.')
|
||||
if n >= 0 {
|
||||
return s[:n]
|
||||
}
|
||||
return s
|
||||
}
|
Loading…
Reference in New Issue