Compare commits

...

80 Commits

Author SHA1 Message Date
deepzz0
ea375ea76c update travis.yml 2017-11-05 12:56:46 +08:00
deepzz0
275a6c0c31 update travis.yml 2017-11-05 12:46:01 +08:00
deepzz0
360204995d 使用github的七牛SDK,配置名称Kodo->Qiniu 2017-11-05 12:27:22 +08:00
deepzz0
c9fc0cc75a Merge branch 'master' of github.com:eiblog/eiblog 2017-10-19 20:23:45 +08:00
deepzz0
41daaa322e fix mod date panic 2017-10-19 20:23:36 +08:00
Deepzz
894535fbe5 Update README.md 2017-10-10 20:16:01 -05:00
Deepzz
6fc5af1b0f Update eiblog.conf 2017-09-26 22:42:49 -05:00
henry.chen
5ce806a7d7 挑战 acme.sh 文件验证路径 2017-08-25 18:01:37 +08:00
Deepzz
25cb23fdb3 Update README.md 2017-08-20 17:48:44 +08:00
deepzz0
a89a1a2bc9 update 2017-08-19 14:26:19 +08:00
deepzz0
93e170f9ac fix es/config/scripts 2017-08-19 14:25:28 +08:00
Deepzz
59d9a616aa Update README.md 2017-08-17 17:00:52 +08:00
Deepzz
2ff0934206 Update install.md 2017-08-15 21:31:34 +08:00
Deepzz
cde7cba2f0 Update README.md 2017-08-15 21:23:12 +08:00
Deepzz
2be7501afe Update README.md 2017-08-15 21:12:03 +08:00
deepzz0
487d35dae2 add comments 2017-08-08 20:59:45 +08:00
henry.chen
19af9376cb add comments 2017-08-08 12:45:58 +08:00
deepzz0
3ddd2a0b33 fix disqus 基础评论bug 2017-08-08 01:03:10 +08:00
Deepzz
ee7523b124 Update helper.go 2017-08-07 18:07:58 +08:00
Deepzz
cc1dbac1f0 clean eiblog.conf 2017-07-27 22:03:43 +08:00
deepzz0
04532ba8a6 fix conflict 2017-07-26 22:48:16 +08:00
deepzz0
0a2a132b11 rm some cod in domain.cnf 2017-07-26 22:45:54 +08:00
Deepzz
3ff712d407 Update eiblog.conf 2017-07-25 09:22:08 +08:00
deepzz0
27162d2205 fix unuse tag <!--more-->
intercept errors
2017-07-15 13:46:29 +08:00
deepzz0
f150974566 rm .travis.yml about glide 2017-07-13 21:29:23 +08:00
deepzz0
b94fc825b3 updage docs 2017-07-13 21:23:28 +08:00
deepzz0
d8f0e30285 update 2017-07-13 20:31:00 +08:00
deepzz0
e0a5f0ebca avatar img use cache 2017-07-13 01:37:35 +08:00
deepzz0
c18d9c0bef update vendor 2017-07-11 23:50:01 +08:00
deepzz0
e1ec5cd08a update vendor 2017-07-09 03:33:28 +08:00
deepzz0
5efdd72e58 update docs 2017-07-09 03:24:46 +08:00
deepzz0
b9470fa14c fix and update config 2017-07-09 01:18:07 +08:00
deepzz0
a932d2906d update .dockerignore 2017-07-09 00:27:46 +08:00
deepzz0
3ff5977941 update vendor and use single static 2017-07-08 21:54:39 +08:00
deepzz0
da7b726e8d rename mode.domain-> mode.domains 2017-07-08 16:29:47 +08:00
deepzz0
3923bc70f1 update vendor 2017-07-08 12:16:15 +08:00
deepzz0
8dc73fd67c replace cert pin-sha256: trustasai G6 -> trustasia G5 2017-07-06 23:28:04 +08:00
deepzz0
2825bbfeae 完善 disqus 评论 2017-06-26 23:51:13 +08:00
deepzz0
66811830b0 update Makefile 2017-06-25 15:22:33 +08:00
deepzz0
c4bf59ce5d update 2017-06-25 06:51:44 +08:00
deepzz0
6cea283f86 添加 Makefile,使用 acme.sh 自动更新证书 2017-06-25 06:48:19 +08:00
deepzz0
3992db49ba update 2017-06-24 20:58:21 +08:00
deepzz0
055c2307cb update config 2017-06-24 19:21:53 +08:00
Deepzz
11b0f486cd Delete goblog.conf 2017-06-24 16:56:42 +08:00
Deepzz
ed0a50b626 Update eiblog.conf 2017-06-24 16:34:22 +08:00
deepzz0
cf2b2d6d34 rename 2017-06-24 16:12:00 +08:00
deepzz0
00456806bb 添加双证书配置 2017-06-24 16:11:28 +08:00
deepzz0
54f5289d6b release v1.20 2017-06-14 21:25:18 +08:00
deepzz0
1634418a13 fix disqus bug 2017-06-12 01:44:28 +08:00
deepzz0
a84a474504 Merge branch 'master' of github.com:eiblog/eiblog 2017-06-12 00:20:10 +08:00
deepzz0
b64cf5985a fix disqus bug 2017-06-12 00:19:17 +08:00
Deepzz
4f9965b6bd Update README.md 2017-05-26 08:59:57 +08:00
Deepzz
daffa6c294 Update CHANGELOG.md 2017-05-12 22:07:05 +08:00
Deepzz
0bd738438e Update install.md 2017-05-12 22:03:19 +08:00
Deepzz
309339492c Update README.md 2017-05-05 18:10:43 +08:00
Deepzz
694036c65f Update README.md 2017-05-05 18:08:36 +08:00
Deepzz
cafdaac9f4 Update README.md 2017-05-05 18:07:20 +08:00
henry.chen
f6956f592f update 2017-04-28 18:01:19 +08:00
deepzz0
2382f76cf6 merge 2017-04-22 10:50:06 +08:00
deepzz0
a66a3c0198 update disqus.js->disqus_921d24.js 2017-04-22 10:47:01 +08:00
henry.chen
31c398700e 将配置分离出来 2017-04-03 10:43:40 +08:00
henry.chen
9296147a0f update 2017-04-03 01:18:46 +08:00
henry.chen
6932799cba update disqus_78bca4.js 2017-04-01 15:53:29 +08:00
Deepzz
b1ff8b59af Update README.md 2017-03-18 15:42:18 +08:00
henry.chen
5f047c2c27 update vendor directory 2017-03-11 00:54:27 +08:00
Deepzz
5d24af11e5 Update front.go 2017-03-10 09:27:34 +08:00
deepzz0
d03f327fb4 update 2017-03-08 20:45:47 +08:00
deepzz0
ef9e64469b use go1.8 2017-03-08 20:18:13 +08:00
deepzz0
86e7374997 update 2017-03-02 23:08:22 +08:00
deepzz0
4f24b80107 fix out of range about MapArticles 2017-03-02 23:07:36 +08:00
deepzz0
2c49a1ec8d Merge branch 'master' of github.com:eiblog/eiblog 2017-03-02 22:48:03 +08:00
deepzz0
3eaab0fb1f fix out of range about MapArticels 2017-03-02 22:47:44 +08:00
Deepzz
8e6404a90a Update writing.md 2017-02-25 22:47:53 +08:00
deepzz0
7775ea35a2 update docs 2017-02-25 22:42:28 +08:00
deepzz0
931d7b0683 document filing 2017-02-25 19:35:23 +08:00
deepzz0
562f4d86c6 add vendor 2017-02-18 15:23:57 +08:00
deepzz0
4ebbc38cc0 pretty *.xml 2017-02-17 23:53:07 +08:00
deepzz0
9509cd66e6 delete static/*.xml 2017-02-17 23:36:56 +08:00
deepzz0
48756a2810 generate by xml.go 2017-02-17 23:35:51 +08:00
deepzz0
ec8297c3f6 fix article desc error 2017-02-11 19:37:51 +08:00
1958 changed files with 494882 additions and 916 deletions

View File

@@ -1,18 +1,16 @@
.git
conf
vendor
setting
conf
static
views
docs
!static/tzdata
static/*.*
!static/favicon.ico
**/.DS_Store
Dockerfile
glide.yaml
glide.lock
*.yml
*.md
*.go
*.sh
*.DS_Store
.gitignore
.dockerignore

9
.gitignore vendored
View File

@@ -1,10 +1,5 @@
*.DS_Store
**/.DS_Store
*.exe
vendor
vendor/**
conf/ssl/domain.*
eiblog
static/feed.xml
static/opensearch.xml
static/sitemap.xml
static/*.*

View File

@@ -14,9 +14,8 @@ branches: # 限定项目分支
only:
- /^v[0-9](\.[0-9]){2}(-rc[1-9])?$/
before_install:
install:
- curl https://glide.sh/get | sh # 安装glide包管理
script:
- glide up
- GOOS=linux GOARCH=amd64 go build # 编译版本
@@ -27,7 +26,7 @@ after_success:
# docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" registry.cn-hangzhou.aliyuncs.com;
# docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog;
# fi
- docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
- docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog:$TRAVIS_TAG
before_deploy:
- ./dist.sh

View File

@@ -1,5 +1,29 @@
# Eiblog Changelog
## v1.3.0 (2017-07-13)
* 更改 app.yml 配置项,将大部分配置归在 general 常规配置下。注意,部署时请先更新 app.yml。
* 静态文件采用动态渲染,即用户不再需要管理 view、static 目录。
* 通过 acme.sh 使用双证书啦,可到 Makefile 查看相关信息。
* 使用 autocert 自动生成证书功能,从此再也不用担心证书过期,移步 [证书更新](https://github.com/eiblog/eiblog/blob/master/docs/autocert.md)。
* 开启配置项 enablehttps 将自动重定向 http 到 https 啦。
* disqus.js 文件由配置指定,请看 app.yml 下的 disqus 相关配置。
## v1.2.0 (2017-06-14)
* 更新评论功能,基础评论 0 回复也可评论了。
* disqus.js 文件由博主自行更新。
* 更正描述 README.md 描述错误 [#4f996](https://github.com/eiblog/eiblog/commit/4f9965b6bdefe087dd0805c1840afcb2752cd155)。
* docker 镜像版本化。
## v1.1.3 (2017-05-12)
* 更新 disqus_78bca4.js 到 disqus_921d24.js具体请参考 docs/install.md
* 更新 vendor
## v1.1.2 (2017-03-08)
* 解决添加文章描述错误的bug
* 添加vendor目录
* 添加文档docs目录
* 删除多余注释
## v1.1.1 (2017-02-07)
* 添加文章描述功能。
* 修复评论`jQuery`文件引用错误。

View File

@@ -1,11 +1,10 @@
FROM alpine
MAINTAINER deepzz <deepzz.qi@gmail.com>
RUN apk update
RUN apk add ca-certificates
RUN apk add --update --no-cache ca-certificates
ADD static/tzdata/Shanghai /etc/localtime
COPY . /eiblog
EXPOSE 9000
WORKDIR /eiblog
ENTRYPOINT ["./eiblog"]
CMD ["./eiblog"]

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014 Manuel Martínez-Almeida
Copyright (c) 2017 deepzz deepzz.qi@gmail.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

69
Makefile Normal file
View File

@@ -0,0 +1,69 @@
.PHONY: test build deploy dist gencert dhparams ssticket makedir clean
# use aliyun dns api to auto renew cert.
# env:
# export Ali_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
# export Ali_Secret="jlsdflanljkljlfdsaklkjflsa"
docker_registry?=registry.cn-hangzhou.aliyuncs.com
acme?=~/.acme.sh
acme.sh?=$(acme)/acme.sh
config?=/data/eiblog/conf
test:
build:
@echo "go build..."
@CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build && \
docker build -t $(docker_registry)/deepzz/eiblog:latest .
deploy:build
@docker push $(docker_registry)/deepzz/eiblog:latest
dist:
@./dist.sh
gencert:makedir
@if [ ! -n "$(sans)" ]; then \
printf "Need one argument [sans=params]\n"; \
printf "example: sans=\"-d domain -d domain\"\n"; \
exit 1; \
fi; \
if [ ! -n "$(cn)" ]; then \
printf "Need one argument [cn=params]\n"; \
printf "example: cn=domain\n"; \
exit 1; \
fi
@if [ ! -f $(acme.sh) ]; then \
curl https://get.acme.sh | sh; \
fi
@echo "generate rsa cert..."
@$(acme.sh) --force --issue --dns dns_ali $(sans) --log \
--renew-hook "ct-submit ctlog.api.venafi.com < $(config)/ssl/domain.rsa.pem > $(config)/scts/rsa/venafi.sct \
&& ct-submit ctlog.wosign.com < $(config)/ssl/domain.rsa.pem > $(config)/scts/rsa/wosign.sct"
@$(acme.sh) --install-cert -d $(cn) \
--key-file $(config)/ssl/domain.rsa.key \
--fullchain-file $(config)/ssl/domain.rsa.pem \
--reloadcmd "service nginx force-reload"
@echo "generate ecc cert..."
@$(acme.sh) --force --issue --dns dns_ali $(sans) -k ec-256 --log \
--renew-hook "ct-submit ctlog.api.venafi.com < $(config)/ssl/domain.ecc.pem > $(config)/scts/ecc/venafi.sct \
&& ct-submit ctlog.wosign.com < $(config)/ssl/domain.ecc.pem > $(config)/scts/ecc/wosign.sct"
@$(acme.sh) --install-cert -d $(cn) --ecc \
--key-file $(config)/ssl/domain.ecc.key \
--fullchain-file $(config)/ssl/domain.ecc.pem \
--reloadcmd "service nginx force-reload"
dhparams:
@openssl dhparam -out $(config)/ssl/dhparams.pem 2048
ssticket:
@openssl rand 48 > $(config)/ssl/session_ticket.key
makedir:
@mkdir -p $(config)/ssl $(config)/scts/rsa $(config)/scts/ecc
clean:

320
README.md
View File

@@ -1,57 +1,28 @@
# EiBlog [![Build Status](https://travis-ci.org/eiblog/eiblog.svg?branch=master)](https://travis-ci.org/eiblog/eiblog)
# EiBlog [![Build Status](https://travis-ci.org/eiblog/eiblog.svg?branch=v1.3.0)](https://travis-ci.org/eiblog/eiblog) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) [![Versuib](https://img.shields.io/github/tag/eiblog/eiblog.svg)](https://github.com/eiblog/eiblog/releases)
> 系统根据[https://imququ.com](https://imququ.com)一系列文章和方向进行搭建期间获得了QuQu的很大帮助在此表示感谢。
用过其它博客系统,不喜欢,不够轻,不够快!自己做过共两款博客系统,完美主义的我(毕竟处女座)也实在是不想再在这件事情上过多纠结了。`Eiblog`应该是一个比较稳定的博客系统,且是博主以后使用的博客系统,稳定性和维护你是不用担心的,唯独该系统部署过程太过复杂,并且不推荐没有计算机知识的朋友搭建,欢迎咨询。该博客的个中优点(明显快,安全),等你体验。
用过其它博客系统,不喜欢,不够轻,不够快!自己做过共两款博客系统,完美主义的我(毕竟处女座)也实在是不想再在这件事情上过多纠结了。`Eiblog` 应该是一个比较稳定的博客系统,且是博主以后使用的博客系统,稳定性和维护你是不用担心的,唯独该系统部署过程太过复杂,并且不推荐没有计算机知识的朋友搭建,欢迎咨询。该博客的个中优点(明显快,安全),等你体验。
<!--more-->
### 介绍
整个博客系统涉及到模块如下:
* 自动更新证书:
* 接入 [acme/autocert](https://github.com/golang/crypto/tree/master/acme/autocert),在 TLS 层开启全自动更新证书,从此证书的更新再也不用惦记了,不过 Go 的 HTTPS 兼容性不够好(不想兼容),在如部分 IE 和 UC 之类的浏览器不能访问,请悉知。
* 如果你采用如 Nginx 代理,推荐使用 [acme.sh](https://github.com/Neilpang/acme.sh) 实现证书的自动部署。博主实现 aliyun dns 的自动验证方式,详见 [Makefile/gencert](https://github.com/eiblog/eiblog/blob/master/Makefile)。
* `MongoDB`,博客采用 mongodb 作为存储数据库。
* `Elasticsearch`,采用`elasticsearch`作为博客的站内搜索,尽管占用内存稍高。
* `Elasticsearch`,采用 `elasticsearch` 作为博客的站内搜索,尽管占用内存稍高。
* `Disqus`,作为博客评论系统,国内大部分被墙,故实现两种评论方式。
* `Nginx`,作为反向代理服务器,并做相关`http header`和证书的设置。
* `Nginx`,作为反向代理服务器,并做相关 `http header` 和证书的设置。
* `Google Analytics`,作为博客系统的数据分析统计工具。
* `七牛 CDN`,作为博客系统的静态文件存储,博文的图片附件什么上传至这里。
相关技术有:
### 图片展示
* `Golang`博客系统后端采用golang编写并开源至[Eiblog](https://github.com/eiblog/eiblog)
* `HTML Javascript CSS`,博客系统的前端采用`html``jquery`编写,样式采用`CSS`
* `Glide` golang 编写。作为博客系统的包依赖管理器,其开源地址是[Glide](https://github.com/Masterminds/glide)。
* `Docker`,博客系统可 docker 部署,方便,快捷。
* `Docker Compose`,博客系统可完全 docker 运行compose起到很好管理作用。
* `SSL 证书``https`是未来的趋势,整个博客系统都将围绕着`证书`进行,请事先准备好一张有效的 ssl 证书。
* `Travis`作为博客系统的自动构建工具自动构建docker镜像并推送到镜像仓库。
* `Yaml`,博客系统的配置文件使用`yaml`,请悉知。
作为博主之心血之作,`Eiblog`实现了什么功能,有什么特点,做了什么优化呢?
1. 系统目前只有`首页``专题``归档``友链``关于``搜索`界面。相信已经可以满足大部分用户的需求。
2. `.js``.css`等静态文件本地存储,小图片 base64 内置到 css 中,不会产生网络所带来的延迟,加速网页访问。版本控制方式,动态更新静态文件。
3. 采用谷歌统计,并实现异步(将访问信息发给后端,后端提交给谷歌)统计,加速访问速度。
4. 采用直接缓存 markdown 转过的 html 文档的方式,加速后端处理。响应速度均在 3ms 以内,真正极速。
5. 通过 Nginx 的配置,开启压缩缩小传输量,服务器传输证书链、开启`Session Resumption``Session Ticket``OCSP Stapling`等加速证书握手,再次提高速度。
6. 文章评论数量(不重要)后端跑定时脚本,定时更新,所以有时评论数是不对的。这样减少了 api 调用,又再次达到加速访问的目的。
7. 针对`disqus`被墙原因,实现[Jerry Qu](https://imququ.com)的另类评论方式,保证评论的流畅。
8. 开源`Typecho`完整后台系统,全功能`markdown`编辑器,让你体验什么是简洁清爽。
9. 博客后台直接对接`七牛 SDK`,实现后台上传文件和删除文件的简单功能。
10. 采用`elasticsearch`作为站内搜索,添加`google opensearch`功能,搜索更加自然。
当然,在信息安全方面也没少下功夫,虽然我们只是一个小小的博客系统。
1. `CDN`使用七牛融合CDN`https`化,实现全站`https`。七牛可申请免费证书了。
2. `CT`,证书透明度检测,提供一个开放的审计和监控系统。可以让任何域名所有者或者 CA 确定证书是否被错误签发或者被恶意使用,从而提高 HTTPS 网站的安全性。
3. `OSCP`,在线证书状态协议。用来检验证书合法性的在线查询服务.
4. `HSTS`,强制客户端(如浏览器)使用 HTTPS 与服务器创建连接。可以很好的解决 HTTPS 降级攻击。
5. `HPKP`HTTP公钥固定扩展防范由「伪造或不正当手段获得网站证书」造成的中间人攻击。该功能让我们选择信任哪些`CA`
5. `SSL Protocols`,罗列支持的`TLS`协议SSLv3被证实是不安全的。
6. `SSL dhparam`,迪菲赫尔曼密钥交换。
7. `Cipher suite`,罗列服务器支持加密套件。
可以容易的看到[httpsecurityreport](https://httpsecurityreport.com/?report=deepzz.com)评分`96`[ssllabs](https://www.ssllabs.com/ssltest/analyze.html?d=deepzz.com&latest)评分`A+`,堪称完美。这些安全的相关配置会在后面的部署过程中接触到。
可以容易的看到 [httpsecurityreport](https://httpsecurityreport.com/?report=deepzz.com) 评分`96`[ssllabs](https://www.ssllabs.com/ssltest/analyze.html?d=deepzz.com&latest) 评分`A+`[myssl](https://myssl.com/deepzz.com) 评分`A+`,堪称完美。这些安全的相关配置会在后面的部署过程中接触到
相关图片展示:
![show-home](http://7xokm2.com1.z0.glb.clouddn.com/static/img/show-home1.png)
@@ -60,251 +31,60 @@
![show-admin](http://7xokm2.com1.z0.glb.clouddn.com/static/img/show-admin.png)
![show-time](http://7xokm2.com1.z0.glb.clouddn.com/static/img/eiblog-time.png)
![eiblog-mem](http://7xokm2.com1.z0.glb.clouddn.com/img/eiblog-mem.png)
> `注`图片1图片2是博客界面图片3是后台界面图片4是性能展示。
好了,说了那么多,吹了那么多,我们实际来动手搭建一个`Eiblog`吧。
### 极速体验
1. 到 [这里](https://github.com/eiblog/eiblog/releases) 下载对应平台 `.tar.gz` 文件。
### 安装
1、`Eiblog`提供多个平台的压缩包下载,可到[Eiblog release](https://github.com/eiblog/eiblog/releases)选择相应版本和平台下载。也可通过:
``` sh
$ curl -L https://github.com/eiblog/eiblog/releases/download/v1.0.0/eiblog-v1.0.0.`uname -s | tr '[A-Z]' '[a-z]'`-amd64.tar.gz > eiblog-v1.0.0.`uname -s | tr '[A-Z]' '[a-z]'`-amd64.tar.gz
2. 搭建 `MongoDB`(必须)和 `Elasticsearch`(可选)服务,正式部署需要。
3. 修改 `/etc/hosts` 文件,添加 `MongoDB` 数据库 IP 地址,如:`127.0.0.1 mongodb`
4. 执行 `./eiblog`,运行博客系统。看到:
```
2、如果有幸你也是`Gopher`,相信你会亲自动手,你可以通过:
``` sh
$ go get https://github.com/eiblog/eiblog
...
...
[GIN-debug] Listening and serving HTTP on :9000
```
进行源码编译二进制文件运行
代表运行成功了
3、如果你对`docker`技术也有研究的话,你也可以通过`docker`来安装:
``` sh
$ docker pull registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
默认监听 `HTTP 9000` 端口,后台 `/admin/login`,默认账号密码均为 `deepzz`。更多详细请查阅 [安装部署](https://github.com/eiblog/eiblog/blob/master/docs/install.md) 文档。
```
镜像内部只提供了`eiblog`的二进制文件,因为其它内容定制化的需求过高。所以需要将`conf`、`static`、`views`目录映射出来,后面会具体说到。
### 特色功能
### 本地测试
在我们下载好可执行程序之后,我们可以开始本地测试的工作了。
作为博主之心血之作,`Eiblog` 实现了什么功能,有什么特点,做了什么优化呢?
本地测试需要搭建两个服务`mongodb`和`elasticsearch2.4.1`(可选,搜索服务不可用)
1. 系统目前只有 `首页``专题``归档``友链``关于``搜索` 界面。相信已经可以满足大部分用户的需求
2. `.js``.css` 等静态文件本地存储,小图片 base64 内置到 css 中,不会产生网络所带来的延迟,加速网页访问。版本控制方式,动态更新静态文件。
3. 采用谷歌统计,并实现异步(将访问信息发给后端,后端提交给谷歌)统计,加速访问速度。
4. 采用直接缓存 markdown 转过的 html 文档的方式,加速后端处理。响应速度均在 3ms 以内,真正极速。
5. 通过 Nginx 的配置,开启压缩缩小传输量,服务器传输证书链、开启 `Session Resumption``Session Ticket``OCSP Stapling `等加速证书握手,再次提高速度。
* `CDN`使用七牛融合CDN`https` 化,实现全站 `https`。七牛可申请免费证书了。
* `CT`,证书透明度检测,提供一个开放的审计和监控系统。可以让任何域名所有者或者 CA 确定证书是否被错误签发或者被恶意使用,从而提高 HTTPS 网站的安全性。
* `OSCP`,在线证书状态协议。用来检验证书合法性的在线查询服务.
* `HSTS`,强制客户端(如浏览器)使用 HTTPS 与服务器创建连接。可以很好的解决 HTTPS 降级攻击。
 * `HPKP`HTTP 公钥固定扩展,防范由「伪造或不正当手段获得网站证书」造成的中间人攻击。该功能让我们选择信任哪些`CA`。请不要轻易尝试 Nginx 线上运行,因为该配置目前只指定了 Letsencrypt X3 和 TrustAsia G5 证书 pin-sha256。
 * `SSL Protocols`,罗列支持的 `TLS` 协议SSLv3 被证实是不安全的。
* `SSL dhparam`,迪菲赫尔曼密钥交换。
* `Cipher suite`,罗列服务器支持加密套件。
6. 文章评论数量(不重要)后端跑定时脚本,定时更新,所以有时评论数是不对的。这样减少了 api 调用,又再次达到加速访问的目的。
7. 针对 `disqus` 被墙原因,实现 [Jerry Qu](https://imququ.com) 的另类评论方式,保证评论的流畅。
8. 开源 `Typecho` 完整后台系统,全功能 `markdown` 编辑器,让你体验什么是简洁清爽。
9. 博客后台直接对接 `七牛 SDK`,实现后台上传文件和删除文件的简单功能。
10. 采用 `elasticsearch` 作为站内搜索,添加 `google opensearch` 功能,搜索更加自然。
`Eiblog`默认会连接`hostname`为`eidb`和`eisearch`,因此你需要将信息填入`/etc/hosts`下。假如你搭建的`mongodb`地址为`127.0.0.1:27017``elasticsearch`地址为`192.168.99.100:9200`,如:
``` sh
$ sudo vi /etc/hosts
# 在末尾加上两行
127.0.0.1 eidb
192.168.99.100 eisearch
```
#### MongoDB 搭建
1、`MongoDB`搭建Mac 可通过`brew install mongo`进行安装,其它平台请查询资料。
#### Elasticsearch 搭建
2、`Elasticsearch`搭建,它的搭建要些许复杂。博主尚未接触如何直接安装,因此建议通过`docker`搭建。需要注意的是 es 自带的分析器对中文分词是不友好的,这里采用了`elasticsearch-analysis-ik`分词器。如果你想了解更多[Github](https://github.com/medcl/elasticsearch-analysis-ik)或则如何实现[博客站内搜索](https://imququ.com/post/elasticsearch.html)。
* pull 镜像`docker pull elasticsearch:2.4.1`,必需使用该版本。
* 添加环境变量`ES_JAVA_OPTS: "-Xms512m -Xmx512m"`,除非你想让你的服务器爆掉。
* 映射相关目录:
```
conf/es/config:/usr/share/elasticsearch/config
conf/es/plugins:/usr/share/elasticsearch/plugins
conf/es/data:/usr/share/elasticsearch/data
conf/es/logs:/usr/share/elasticsearch/logs
```
 请将这四个目录映射至`eiblog`下的`conf`目录。如果你想查看更多,请查看`docker-compose.yml`文件。
总结一下,`docker`运行 es 的命令为:
``` sh
$ docker run -d --name eisearch \
-p 9200:9200 \
-e ES_JAVA_OPTS: "-Xms512m -Xmx512m" \
-v conf/es/config:/usr/share/elasticsearch/config \
-v conf/es/plugins:/usr/share/elasticsearch/plugins \
-v conf/es/data:/usr/share/elasticsearch/data \
-v conf/es/logs:/usr/share/elasticsearch/logs \
elasticsearch:2.4.1
```
之后执行`./eiblog`,咱们的`eiblog`就可以运行起来了。
通过`127.0.0.1:9000`可以进入博客首页,`127.0.0.1:9000/admin/login`进入后台登陆,账号密码为`eiblog/conf/app.yml`下的`username`和`password`。也就是初始账号密码`deepz`、`deepzz`。
> `注意`,因为配置`conf/app.yml`均是博主自用配置。有些操作可能(如评论)会评论到我的博客,还请尽量避免,谢谢。
### 准备部署
如果你在感受了该博客的魅力了之后,仍然坚持想要搭建它。那么,恭喜你,获得的一款不想再更换的博客系统。下面,我们跟随步骤对部署流程进一步说明。
这里只提供`Docker`的相关部署说明。你如果需要其它方式部署,请参考该方式。
#### 前提准备
这里需要准备一些必要的东西,如果你已准备好。请跳过。
* `一台服务器`。
* `一个域名`,国内服务器需备案。
* `有效的证书`。一般使用免费的就可以。如:`Lets Encrypt`,另外`qcloud`、`七牛`也提供了免费证书的申请,均是全球可信。
* `七牛CDN`。博客只设计接入了七牛cdn相信该CDN服务商不会让你失望。
* `Disqus`。作为博客评论系统,你得有翻墙的能力注册到该账号,具体配置我想又可以写一片博客了。简单说需要`shorname`和`public key`。
* `Google Analystic`。数据统计分析工具。
* `Superfeedr`。加速 RSS 订阅。
* `Twitter`。希望你能够有一个twitter账号。
是不是这么多要求,很费解。其实当初该博客系统只是为个人而设计的,是自己心中想要的那一款。博主些这篇文章不是想要多少人来用该博客,而是希望对那些追求至极的朋友说:你需要这款博客系统。
#### 文件准备
尽管大多数文件已经准备好。但有些默认的文件需要特别指出来,需要你在 CDN 上写特殊的路径。
假如你的 CDN 域名为`st.example.com`,那么:
* `favicon.ico`,其 URL 应该是`st.example.com/static/img/favicon.ico`。故你在 CDN 中的文件名为`static/img/favicon.ico`,以下如是。
* `左侧背景图片``500*1200`左右CDN 中文件名:`static/img/bg04.jpg`。如需更改,请在`eiblog/view/st_blog.css`中替换该名称。
* `头像``160*160~256*256`之间CDN 文件名:`static/img/avatar.jpg`。另外你需要将该图片 `Base64` 编码后替换掉`eiblog/views/st_blog.css`中合适位置的图片。
* `blank.gif`CDN 文件名:`static/img/blank.gif`。该图片请从[这里](https://st.deepzz.com/static/img/blank.gif)下载并上传至你的 CDN。
* `default_avatar.png`CDN 文件名:`static/img/default_avatar.png`,请从[这里](https://st.deepzz.com/static/img/default_avatar.png)下载并上传至你的 CDN。
* `disqus.js`,该文件名是会变的,每次更新如果没有提及就没有改变,更新说明在[这里](https://github.com/eiblog/eiblog/blob/master/CHANGELOG.md)。CDN 文件名格式是:`static/js/name.js`。在我写这篇文章是使用的是:`static/js/disqus_a9d3fd.js`,请从[这里](https://st.deepzz.com/static/js/disqus_a9d3fd.js)下载并上传至你的 CDN。
> `注意`:本人 CDN 做了防盗链处理,故请将这些资源上传至您的 CDN ,以免静态资源不能访问,请悉知。
#### 配置说明
走到这里,我相信只走到`60%`的路程。放弃还来得及。
这里会对`eiblog/conf`下的所有文件做说明,希望你做好准备。
```
├── app.yml # 博客配置文件
├── blackip.yml # 博客ip黑名单
├── es # elasticsearch配置
│   ├── config # 配置文件
│   │   ├── analysis # 同义词
│   │   ├── elasticsearch.yml # 具体配置
│   │   ├── logging.yml # 日志配置
│   │   └── scripts # 脚本文件夹
│   └── plugins # 插件文件夹
│   └── ik1.10.1 # ik分词器
├── nginx # nginx配置
│   ├── domain # 域名配置nginx会读区改文件夹下的.conf文件
│   │   └── deepzz.conf
│   ├── ip.blacklist # nginx ip黑名单
│   └── nginx.conf # nginx配置请替换原有配置
├── scts # ct文件
│   ├── aviator.sct
│   └── digicert.sct
├── ssl # 证书文件具体请看deepzz.conf
│   ├── dhparams.pem
│   ├── domain.key
│   ├── domain.pem
│   ├── full_chained.pem
│   └── session_ticket.key
└── tpl # 模版文件
├── feedTpl.xml
├── opensearchTpl.xml
└── sitemapTpl.xml
```
1、app.yml整个程序的配置文件里面已经列出了所有配置项的说明这里不再阐述。
2、blackip.yml如果没有使用`Nginx`,博客内置`ip`过滤系统。
3、`es`全名`elasticsearch`,非常强大的分布式搜索引擎,`github`用的就是它。里面的配置基本不用修改,但`es/analysis/synonym.txt`是同义词,你可以照着已有的随意增加。
```
├── es
│   ├── config
│   │   ├── analysis
│   │   │   └── synonym.txt #同义词配置
│   │   ├── elasticsearch.yml #分词器配置
│   │   ├── logging.yml #日志配置
│   │   └── scripts #脚本
│   └── plugins #中文分词插件
│   └── ik1.10.0
```
> `注意`scripts文件夹虽然是空的但必需存在不然elasticsearch报错。
4、`nginx`,系统采用`nginx`作为代理(相信博客系统也不会独占一台服务器~)。请使用`nginx.conf`替换原`nginx`的配置。博客系统的配置文件是`domain/deepzz.conf`,或则重命名(只要是满足`*.conf`)。`deepzz.conf`文件里面学问是最多的。或许你想一一弄懂,或许…。
> 注意本配置需要更新nginx到最新版openssl更新到1.0.2j,具体请到 Jerry Qu 的[本博客 Nginx 配置之完整篇](https://imququ.com/post/my-nginx-conf.html)查看,了解详情。
5、`scts`,存放 ct 文件。
6、`ssl`,这里存放了所有证书相关的内容。
```
├── dhparams.pem #参见eiblog/conf/nginx/domain/deepzz.conf
├── domain.key #证书私钥,一般颁发者处下载
├── domain.pem #证书链,一般从证书颁发者那可以直接下载到
├── full_chained.pem #参见eiblog/conf/nginx/domain/deepzz.conf
└── session_ticket.key #参见eiblog/conf/nginx/domain/deepzz.conf
```
7、`tpl`模版相关,不用修改。
### 开始部署
#### docker
请确定你已经完成了上面所说的所有步骤,在本地已经测试成功。服务器上`MognoDB`和`Elasticsearch`已经安装并已经运行成功。
首先,请将本地测试好的`conf``static``views`文件夹上传至服务器,建议存储到服务器`/data/eiblog`下。
``` sh
$ tree /data/eiblog -L 1
├── conf
├── static
├── views
```
然后,将镜像 PULL 到服务器本地。
``` sh
# PULL下Eiblog镜像
$ docker pull registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
```
最后,执行`docker run`命令,希望你能成功。
``` sh
$ docker run -d --name eiblog --restart=always \
--add-host disqus.com:23.235.33.134 \
--link eidb --link eisearch \
-p 9000:9000 \
-e GODEBUG=netdns=cgo \
-v /data/eiblog/logdata:/eiblog/logdata \
-v /data/eiblog/conf:/eiblog/conf \
-v /data/eiblog/static:/eiblog/static \
-v /data/eiblog/views:/eiblog/views \
registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
```
这里默认`MongDB`和`Elasticsearch`均为`docker`部署,且名称为`eidb``eisearch`。
#### nginx + docker
通过`Nginx+docker`部署,是博主推荐的方式。这里采用`Docker Compose`管理我们整个博客系统。
请确认你已经成功安装好`Nginx`、`docker`、`docker-compose`。Nginx 请一定参照 Jerry Qu 的[Nginx 配置完整篇](https://imququ.com/post/my-nginx-conf.html)。
首先,请将本地测试好的`conf``static``views``docker-compose.yml`文件夹和文件上传至服务器。前三个文件夹建议存储到服务器`/data/eiblog`下,`docker-compose.yml`存放在你使用方便的地方。
> 注意`conf/es/config/scripts`空文件夹是否存在
``` sh
$ tree /data/eiblog -L 1
├── conf
├── static
├── views
$ ls ~/
docker-compose.yml
```
然后,执行:
``` sh
$ cd ~
$ docker-compose up -d
```
等待些许时间,成功运行。
### 文档
* [证书更新](https://github.com/eiblog/eiblog/blob/master/docs/autocert.md)
* [安装部署](https://github.com/eiblog/eiblog/blob/master/docs/install.md)
* [写作需知](https://github.com/eiblog/eiblog/blob/master/docs/writing.md)
* [好玩的功能](https://github.com/eiblog/eiblog/blob/master/docs/amusing.md)
### 成功搭建者博客
* [https://razeencheng.com/](https://razeencheng.com/) - Razeen's Blog
* [https://razeen.me](https://razeen.me) - Razeen's Blog
* [https://mxthd.me](https://mxthd.me) - 梦醒逃荒岛
如果你的博客使用`Eiblog`搭建,你可以在[这里](https://github.com/eiblog/eiblog/issues/1)提交网址。
如果你的博客使用`Eiblog`搭建,你可以在 [这里](https://github.com/eiblog/eiblog/issues/1) 提交网址。

15
api.go
View File

@@ -16,11 +16,15 @@ import (
)
const (
// 成功
NOTICE_SUCCESS = "success"
NOTICE_NOTICE = "notice"
NOTICE_ERROR = "error"
// 注意
NOTICE_NOTICE = "notice"
// 错误
NOTICE_ERROR = "error"
)
// 全局 API
var APIs = make(map[string]func(c *gin.Context))
func init() {
@@ -61,6 +65,7 @@ func apiAccount(c *gin.Context) {
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
return
}
err := UpdateAccountField(bson.M{"$set": bson.M{"email": e, "phonen": pn, "address": ad}})
if err != nil {
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
@@ -83,6 +88,7 @@ func apiBlog(c *gin.Context) {
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
return
}
err := UpdateAccountField(bson.M{"$set": bson.M{"blogger.blogname": bn, "blogger.btitle": bt, "blogger.beian": ba, "blogger.subtitle": st, "blogger.seriessay": ss, "blogger.archivessay": as}})
if err != nil {
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
@@ -117,6 +123,7 @@ func apiPassword(c *gin.Context) {
return
}
newPwd := EncryptPasswd(Ei.Username, nw)
err := UpdateAccountField(bson.M{"$set": bson.M{"password": newPwd}})
if err != nil {
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
@@ -136,6 +143,7 @@ func apiPostDelete(c *gin.Context) {
}
responseNotice(c, NOTICE_SUCCESS, "删除成功", "")
}()
err = c.Request.ParseForm()
if err != nil {
return
@@ -187,6 +195,7 @@ func apiPostAdd(c *gin.Context) {
c.Redirect(http.StatusFound, "/admin/manage-posts")
}
}()
do = c.PostForm("do") // auto or save or publish
slug := c.PostForm("slug")
title := c.PostForm("title")
@@ -258,7 +267,7 @@ func apiPostAdd(c *gin.Context) {
// elasticsearch 索引
ElasticIndex(artc)
DoPings(slug)
if artc.ID >= setting.Conf.StartID {
if artc.ID >= setting.Conf.General.StartID {
ManageTagsArticle(artc, true, ADD)
ManageSeriesArticle(artc, true, ADD)
ManageArchivesArticle(artc, true, ADD)

12
back.go
View File

@@ -16,6 +16,7 @@ import (
"gopkg.in/mgo.v2/bson"
)
// 是否登录
func isLogin(c *gin.Context) bool {
session := sessions.Default(c)
v := session.Get("username")
@@ -25,6 +26,7 @@ func isLogin(c *gin.Context) bool {
return true
}
// 登陆过滤
func AuthFilter() gin.HandlerFunc {
return func(c *gin.Context) {
if !isLogin(c) {
@@ -51,6 +53,7 @@ func HandleLogin(c *gin.Context) {
RenderHTMLBack(c, "login.html", gin.H{"BTitle": Ei.BTitle})
}
// 登陆接口
func HandleLoginPost(c *gin.Context) {
user := c.PostForm("user")
pwd := c.PostForm("password")
@@ -61,7 +64,7 @@ func HandleLoginPost(c *gin.Context) {
return
}
if Ei.Username != user || !VerifyPasswd(Ei.Password, user, pwd) {
logd.Printf("账号或密码错误 %s, %s", user, pwd)
logd.Printf("账号或密码错误 %s, %s\n", user, pwd)
c.Redirect(http.StatusFound, "/admin/login")
return
}
@@ -75,7 +78,7 @@ func HandleLoginPost(c *gin.Context) {
}
func GetBack() gin.H {
return gin.H{"Author": Ei.Username, "Kodo": setting.Conf.Kodo}
return gin.H{"Author": Ei.Username, "Qiniu": setting.Conf.Qiniu}
}
// 个人配置
@@ -120,6 +123,7 @@ func HandlePost(c *gin.Context) {
RenderHTMLBack(c, "admin-post", h)
}
// 删除草稿
func HandleDraftDelete(c *gin.Context) {
id, err := strconv.Atoi(c.Query("cid"))
if err != nil || id < 1 {
@@ -154,7 +158,7 @@ func HandlePosts(c *gin.Context) {
h["Serie"] = se
h["KW"] = kw
var max int
max, h["List"] = PageListBack(se, kw, false, false, pg, setting.Conf.PageSize)
max, h["List"] = PageListBack(se, kw, false, false, pg, setting.Conf.General.PageSize)
if pg < max {
vals.Set("page", fmt.Sprint(pg+1))
h["Next"] = vals.Encode()
@@ -184,6 +188,7 @@ func HandleSeries(c *gin.Context) {
RenderHTMLBack(c, "admin-series", h)
}
// 编辑专题
func HandleSerie(c *gin.Context) {
h := GetBack()
id, err := strconv.Atoi(c.Query("mid"))
@@ -276,6 +281,7 @@ func HandleAPI(c *gin.Context) {
api(c)
}
// 渲染 html
func RenderHTMLBack(c *gin.Context, name string, data gin.H) {
if name == "login.html" {
err := Tmpl.ExecuteTemplate(c.Writer, name, data)

View File

@@ -1,10 +0,0 @@
#!/bin/bash
echo "go build..."
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build && \
domain="registry.cn-hangzhou.aliyuncs.com" && \
docker build -t $domain/deepzz/eiblog . && \
read -p "是否上传到服务器(y/n):" word && \
if [ $word = "y" ] ;then
docker push $domain/deepzz/eiblog
fi

View File

@@ -6,25 +6,30 @@ import (
"time"
)
// 检查 email
func CheckEmail(e string) bool {
reg := regexp.MustCompile(`^(\w)+([\.\-]\w+)*@(\w)+((\.\w+)+)$`)
return reg.MatchString(e)
}
// 检查 domain
func CheckDomain(domain string) bool {
reg := regexp.MustCompile(`^(http://|https://)?[0-9a-zA-Z]+[0-9a-zA-Z\.-]*\.[a-zA-Z]{2,4}$`)
return reg.MatchString(domain)
}
// 检查 sms
func CheckSMS(sms string) bool {
reg := regexp.MustCompile(`^\+\d+$`)
return reg.MatchString(sms)
}
// 检查 password
func CheckPwd(pwd string) bool {
return len(pwd) > 5 && len(pwd) < 19
}
// 检查日期
func CheckDate(date string) time.Time {
if t, err := time.ParseInLocation("2006-01-02 15:04", date, time.Local); err == nil {
return t
@@ -32,6 +37,7 @@ func CheckDate(date string) time.Time {
return time.Now()
}
// 检查 id
func CheckSerieID(sid string) int32 {
if id, err := strconv.Atoi(sid); err == nil {
return int32(id)
@@ -39,6 +45,7 @@ func CheckSerieID(sid string) int32 {
return 0
}
// bool
func CheckBool(str string) bool {
return str == "true" || str == "1"
}

View File

@@ -2,28 +2,40 @@ package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCheckEmail(t *testing.T) {
e := "xx@email.com"
e1 := "xxxxemail.com"
e2 := "xxx#email.com"
emails := []string{
"xx@email.com",
"xxxxemail.com",
"xxx#email.com",
}
t.Log(CheckEmail(e))
t.Log(CheckEmail(e1))
t.Log(CheckEmail(e2))
for i, v := range emails {
if i == 0 {
assert.True(t, CheckEmail(v))
} else {
assert.False(t, CheckEmail(v))
}
}
}
func TestCheckDomain(t *testing.T) {
d := "123.com"
d1 := "http://123.com"
d2 := "https://123.com"
d3 := "123#.com"
d4 := "123.coooom"
domains := []string{
"123.com",
"http://123.com",
"https://123.com",
"123#.com",
"123.coooom",
}
t.Log(CheckDomain(d))
t.Log(CheckDomain(d1))
t.Log(CheckDomain(d2))
t.Log(CheckDomain(d3))
t.Log(CheckDomain(d4))
for i, v := range domains {
if i > 2 {
assert.False(t, CheckDomain(v))
} else {
assert.True(t, CheckDomain(v))
}
}
}

View File

@@ -1,56 +1,74 @@
# 运行模式 dev or prod
runmode: dev
# 回收箱保留48小时
trash: -48
# 定时清理回收箱,%d小时
clean: 1
# 首页展示文章数量
pagenum: 10
# 管理界面
pagesize: 20
# 自动截取预览, 字符数
length: 400
# 截取预览标识
identifier: <!--more-->
# 文章描述前缀
description: "Desc:"
# 起始ID预留id不时之需, 不用管
startid: 11
# elasticsearch url
searchurl: http://elasticsearch:9200
# 静态文件版本
staticversion: 1
# superfeedr url
feedrurl: https://deepzz.superfeedr.com/
# 热搜词配置
hotwords:
- docker
- mongodb
- curl
- dns
# ping rpcs 地址
pingrpcs:
- http://ping.baidu.com/ping/RPC2
- http://blogsearch.google.com/ping/RPC2
- http://rpc.pingomatic.com/
# 常规配置
general:
# 首页展示文章数量
pagenum: 10
# 管理界面
pagesize: 20
# 起始ID预留id不时之需, 不用管
startid: 11
# 文章描述前缀
descprefix: "Desc:"
# 截取预览标识
identifier: <!--more-->
# 自动截取预览, 字符数
length: 400
# 回收箱保留48小时
trash: -48
# 定时清理回收箱,每 %d 小时
clean: 1
# 评论相关
disqus:
shortname: deepzz
publickey: wdSgxRm9rdGAlLKFcFdToBe3GT4SibmV7Y8EjJQ0r4GWXeKtxpopMAeIeoI2dTEg
accesstoken: 50023908f39f4607957e909b495326af
postscount: https://disqus.com/api/3.0/threads/set.json
postslist: https://disqus.com/api/3.0/threads/listPosts.json
postcreate: https://disqus.com/api/3.0/posts/create.json
postapprove: https://disqus.com/api/3.0/posts/approve.json
# disqus.js 文件名
embed: disqus_7d3cf2.js
# 获取评论数量间隔
interval: 5
# 热搜词配置
hotwords:
- docker
# 谷歌统计
google:
url: https://www.google-analytics.com/collect
tid: UA-77251712-1
v: "1"
t: pageview
# 静态文件版本
staticversion: 1
# 七牛CDN
kodo:
name: eiblog
qiniu:
bucket: eiblog
domain: st.deepzz.com
accesskey: MB6AXl_Sj_mmFsL-Lt59Dml2Vmy2o8XMmiCbbSeC
secretkey: BIrMy0fsZ0_SHNceNXk3eDuo7WmVYzj2-zrmd5Tf
# 运行模式
mode:
# you can fix certfile, keyfile, domain
# http server
enablehttp: true
httpport: 9000
# https server
enablehttps: false
httpsport: 443
certfile: conf/certs/domain.pem
keyfile: conf/certs/domain.key
autocert: false
httpsport: 9001
certfile: conf/ssl/domain.rsa.pem
keyfile: conf/ssl/domain.rsa.key
domain: deepzz.com
# twitter地址: twitter.com/chenqijing2
twitter:
@@ -58,15 +76,8 @@ twitter:
site: chenqijing2
image: st.deepzz.com/static/img/avatar.jpg
address: twitter.com/chenqijing2
# superfeedr url
feedrurl: https://deepzz.superfeedr.com/
# ping rpcs 地址
pingrpcs:
- http://ping.baidu.com/ping/RPC2
- http://blogsearch.google.com/ping/RPC2
- http://rpc.pingomatic.com/
# 以下数据初始化用,可在后台修改
# 数据初始化操作,可到博客后台修改
account:
# *后台登录用户名
username: deepzz
@@ -87,5 +98,5 @@ blogger:
beian: 蜀 ICP 备 16021362 号
# footer显示名称及tab标题: Deepzz's Blog
btitle: Deepzz's Blog
# 版权声明: 本站使用「<a href="//creativecommons.org/licenses/by/4.0/">署名 4.0 国际</a>」创作共享协议,转载请注明作者及原网址。
# 版权声明
copyright: 本站使用「<a href="//creativecommons.org/licenses/by/4.0/">署名 4.0 国际</a>」创作共享协议,转载请注明作者及原网址。

View File

@@ -0,0 +1 @@
# like 192.168.99.100:true

View File

View File

@@ -1,181 +0,0 @@
server {
listen 443 ssl http2 fastopen=3 reuseport;
server_name www.deepzz.com deepzz.com;
server_tokens off;
include /data/eiblog/conf/nginx/ip.blacklist;
# 现在一般证书是内置的。可以注释该项
# https://imququ.com/post/certificate-transparency.html#toc-2
# ssl_ct on;
# ssl_ct_static_scts /data/eiblog/conf/scts;
# 中间证书 + 站点证书
ssl_certificate /data/eiblog/conf/ssl/domain.pem;
# 创建 CSR 文件时用的密钥
ssl_certificate_key /data/eiblog/conf/ssl/domain.key;
# openssl dhparam -out dhparams.pem 2048
# https://weakdh.org/sysadmin.html
ssl_dhparam /data/eiblog/conf/ssl/dhparams.pem;
# https://github.com/cloudflare/sslconfig/blob/master/conf
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
# 如果启用了 RSA + ECDSA 双证书Cipher Suite 可以参考以下配置:
# ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets on;
# openssl rand 48 > session_ticket.key
# 单机部署可以不指定 ssl_session_ticket_key
# ssl_session_ticket_key /data/eiblog/conf/ssl/session_ticket.key;
ssl_stapling on;
ssl_stapling_verify on;
# 根证书 + 中间证书
# https://imququ.com/post/why-can-not-turn-on-ocsp-stapling.html
ssl_trusted_certificate /data/eiblog/conf/ssl/full_chained.pem;
resolver 8.8.8.8 114.114.114.114 valid=300s;
resolver_timeout 10s;
access_log /data/eiblog/logdata/nginx.log;
if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$ ) {
return 444;
}
if ($host != 'deepzz.com' ) {
rewrite ^/(.*)$ https://deepzz.com/$1 permanent;
}
# webmaster 站点验证相关
location ~* (robots\.txt|favicon\.ico|crossdomain\.xml|google4c90d18e696bdcf8\.html|BingSiteAuth\.xml)$ {
root /data/eiblog/static;
expires 1d;
}
location ^~ /static/uploads/ {
root /home/jerry/www/imququ.com/www;
add_header Access-Control-Allow-Origin *;
set $expires_time max;
valid_referers blocked none server_names *.qgy18.com *.inoreader.com feedly.com *.feedly.com www.udpwork.com theoldreader.com digg.com *.feiworks.com *.newszeit.com r.mail.qq.com yuedu.163.com *.w3ctech.com;
if ($invalid_referer) {
set $expires_time -1;
return 403;
}
expires $expires_time;
}
location ^~ /static/ {
root /data/eiblog;
add_header Access-Control-Allow-Origin *;
expires max;
}
location ^~ /admin/ {
proxy_http_version 1.1;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
# deny 将完全不允许页面被嵌套,可能会导致一些异常。如果遇到这样的问题,建议改成 SAMEORIGIN
# https://imququ.com/post/web-security-and-response-header.html#toc-1
add_header X-Frame-Options deny;
add_header X-Content-Type-Options nosniff;
proxy_set_header Connection "";
proxy_set_header Host deepzz.com;
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:9000;
}
location / {
proxy_http_version 1.1;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
add_header X-Frame-Options deny;
add_header X-Content-Type-Options nosniff;
# 改deepzz相关的
add_header Content-Security-Policy "default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' blob: https:; img-src data: https: https://st.deepzz.com; style-src 'unsafe-inline' https:; child-src https:; connect-src 'self' https://translate.googleapis.com; frame-src https://disqus.com https://www.slideshare.net";
# 中间证书证书指纹
# https://imququ.com/post/http-public-key-pinning.html
add_header Public-Key-Pins 'pin-sha256="lnsM2T/O9/J84sJFdnrpsFp3awZJ+ZZbYpCWhGloaHI="; pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; max-age=2592000; includeSubDomains';
add_header Cache-Control no-cache;
add_header X-Via Aliyun.QingDao;
add_header X-XSS-Protection "1; mode=block";
proxy_ignore_headers Set-Cookie;
proxy_hide_header Vary;
proxy_hide_header X-Powered-By;
proxy_set_header Connection "";
proxy_set_header Host deepzz.com;
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:9000;
}
}
server {
server_name www.deepzz.com deepzz.com;
server_tokens off;
access_log /dev/null;
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
return 444;
}
location ^~ /.well-known/acme-challenge/ {
alias /home/jerry/www/challenges/;
try_files $uri =404;
}
location / {
rewrite ^/(.*)$ https://deepzz.com/$1 permanent;
}
}
# 原博客,内部做的转发
server {
server_name blog.deepzz.com;
access_log /dev/null;
location / {
rewrite ^/(.*)$ https://blog.deepzz.com/$1 permanent;
}
}
server {
listen 443;
server_name blog.deepzz.com;
add_header Strict-Transport-Security "max-age=31536000";
add_header X-Frame-Options deny;
add_header X-Content-Type-Options nosniff;
add_header X-Xss-Protection "1; mode=block;";
location / {
proxy_pass https://127.0.0.1:9010;
}
}

View File

@@ -0,0 +1,134 @@
server {
listen 443 ssl http2 fastopen=3 reuseport;
server_name www.deepzz.com deepzz.com;
server_tokens off;
access_log /data/eiblog/logdata/nginx.log;
# ip 黑名单
include /data/eiblog/conf/nginx/ip.blacklist;
# 现在一般证书是内置的。letsencrypt 暂未
# https://imququ.com/post/certificate-transparency.html#toc-2
ssl_ct on;
# 中间证书 + 根证书
# https://imququ.com/post/why-can-not-turn-on-ocsp-stapling.html
ssl_trusted_certificate /data/eiblog/conf/ssl/full_chained.pem;
# 站点证书 + 中间证书,私钥
ssl_certificate /data/eiblog/conf/ssl/domain.rsa.pem;
ssl_certificate_key /data/eiblog/conf/ssl/domain.rsa.key;
ssl_ct_static_scts /data/eiblog/conf/scts/rsa/;
# ssl_certificate /data/eiblog/conf/ssl/domain.ecc.pem;
# ssl_certificate_key /data/eiblog/conf/ssl/domain.ecc.key;
# ssl_ct_static_scts /data/eiblog/conf/scts/ecc/;
# openssl dhparam -out dhparams.pem 2048
# https://weakdh.org/sysadmin.html
ssl_dhparam /data/eiblog/conf/ssl/dhparams.pem;
# openssl rand 48 > session_ticket.key
# 单机部署可以不指定 ssl_session_ticket_key
# ssl_session_ticket_key /data/eiblog/conf/ssl/session_ticket.key;
# https://github.com/cloudflare/sslconfig/blob/master/conf
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
# 如果启用了 RSA + ECDSA 双证书Cipher Suite 可以参考以下配置:
# ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets on;
# ssl stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 114.114.114.114 8.8.8.8 valid=300s;
resolver_timeout 10s;
if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$ ) {
return 444;
}
if ($host != 'deepzz.com' ) {
rewrite ^/(.*)$ https://deepzz.com/$1 permanent;
}
# webmaster 站点验证相关
location ~* (google4c90d18e696bdcf8\.html|BingSiteAuth\.xml)$ {
root /data/eiblog/static;
expires 1d;
}
location ^~ /admin/ {
proxy_http_version 1.1;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
# deny 将完全不允许页面被嵌套,可能会导致一些异常。如果遇到这样的问题,建议改成 SAMEORIGIN
# https://imququ.com/post/web-security-and-response-header.html#toc-1
add_header X-Frame-Options deny;
add_header X-Powered-By eiblog/1.3.0;
add_header X-Content-Type-Options nosniff;
proxy_set_header Connection "";
proxy_set_header Host deepzz.com;
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:9000;
}
location / {
proxy_http_version 1.1;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
add_header X-Frame-Options deny;
add_header X-Content-Type-Options nosniff;
# 改deepzz相关的
add_header Content-Security-Policy "default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' blob: https:; img-src data: https: https://st.deepzz.com; media-src https://st.deepzz.com; style-src 'unsafe-inline' https:; child-src https:; connect-src 'self' https://translate.googleapis.com; frame-src https://disqus.com https://www.slideshare.net";
# 中间证书证书指纹
# https://imququ.com/post/http-public-key-pinning.html
add_header Public-Key-Pins 'pin-sha256="IiSbZ4pMDEyXvtl7Lg8K3FNmJcTAhKUTrB2FQOaAO/s="; pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; max-age=2592000;';
add_header Cache-Control no-cache;
add_header X-Via Aliyun.QingDao;
add_header X-XSS-Protection "1; mode=block";
add_header X-Powered-By eiblog/1.3.0;
proxy_ignore_headers Set-Cookie;
proxy_hide_header Vary;
proxy_set_header Connection "";
proxy_set_header Host deepzz.com;
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:9000;
}
}
server {
server_name www.deepzz.com deepzz.com;
server_tokens off;
access_log /dev/null;
if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$ ) {
return 444;
}
# letsencrypt file verify
location ^~ /.well-known/acme-challenge/ {
alias /data/eiblog/challenges/;
try_files $uri =404;
}
location / {
rewrite ^/(.*)$ https://deepzz.com/$1 permanent;
}
}

View File

@@ -0,0 +1,2 @@
# example black list
#deny 195.154.211.220;

View File

@@ -10,19 +10,20 @@ worker_processes 1;
events {
worker_connections 1024;
worker_connections 10240;
}
http {
include mime.types;
default_type application/octet-stream;
charset UTF-8;
include mime.types;
default_type application/octet-stream;
charset UTF-8;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
access_log off;
sendfile on;
tcp_nopush on;
@@ -58,74 +59,13 @@ http {
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
include /data/eiblog/conf/nginx/domain/*.conf;
}

BIN
conf/scts/rsa/aviator.sct Normal file

Binary file not shown.

BIN
conf/scts/rsa/digicert.sct Normal file

Binary file not shown.

View File

@@ -0,0 +1,4 @@
<?xml version="1.0"?>
<cross-domain-policy>
<allow-access-from domain="*.{{.Domain}}" />
</cross-domain-policy>

View File

@@ -1,21 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{.Title}}</title>
<link>https://{{.Domain}}</link>
<description>{{.SubTitle}}</description>
<atom:link href="https://{{.Domain}}/rss.html" rel="self"/>
<atom:link href="{{.FeedrURL}}" rel="hub"/>
<language>zh-CN</language>
<lastBuildDate>{{.BuildDate}}</lastBuildDate>
{{range .Artcs}}
<item>
<title>{{.Title}}</title>
<link>https://{{$.Domain}}/post/{{.Slug}}.html</link>
<comments>https://{{$.Domain}}/post/{{.Slug}}.html#comments</comments>
<guid>https://{{$.Domain}}/post/{{.Slug}}.html</guid>
<description><![CDATA[<blockquote>{{.Content}}<p>本文链接:<a href="https://{{$.Domain}}/post/{{.Slug}}.html">https://{{$.Domain}}/post/{{.Slug}}.html</a><a href="https://{{$.Domain}}/post/{{.Slug}}.html#comments">参与评论 »</a></p>]]></description>
</item>
{{end}}
</channel>
<channel>
<title>{{.Title}}</title>
<link>https://{{.Domain}}</link>
<description>{{.SubTitle}}</description>
<atom:link href="https://{{.Domain}}/rss.html" rel="self" />
<atom:link href="{{.FeedrURL}}" rel="hub" />
<language>zh-CN</language>
<lastBuildDate>{{.BuildDate}}</lastBuildDate>
{{range .Artcs}}
<item>
<title>{{.Title}}</title>
<link>https://{{$.Domain}}/post/{{.Slug}}.html</link>
<comments>https://{{$.Domain}}/post/{{.Slug}}.html#comments</comments>
<guid>https://{{$.Domain}}/post/{{.Slug}}.html</guid>
<description>
<![CDATA[<blockquote>{{.Content}}<p>本文链接:<a href="https://{{$.Domain}}/post/{{.Slug}}.html">https://{{$.Domain}}/post/{{.Slug}}.html</a><a href="https://{{$.Domain}}/post/{{.Slug}}.html#comments">参与评论 »</a></p>]]>
</description>
</item>
{{end}}
</channel>
</rss>

3
conf/tpl/robotsTpl.xml Normal file
View File

@@ -0,0 +1,3 @@
User-agent: *
Allow: /
Sitemap: https://{{.Domain}}/sitemap.xml

46
db.go
View File

@@ -18,6 +18,7 @@ import (
"gopkg.in/mgo.v2/bson"
)
// 数据库及表名
const (
DB = "eiblog"
COLLECTION_ACCOUNT = "account"
@@ -30,6 +31,7 @@ const (
DELETE = "delete"
)
// blackfriday 配置
const (
commonHtmlFlags = 0 |
blackfriday.HTML_TOC |
@@ -96,7 +98,7 @@ func init() {
ms.Close()
// 读取帐号信息
Ei = loadAccount()
// 获取文章
// 获取文章数据
Ei.Articles = loadArticles()
// 生成markdown文档
go generateMarkdown()
@@ -147,13 +149,13 @@ func loadArticles() (artcs SortArticles) {
GenerateExcerptAndRender(v)
Ei.MapArticles[v.Slug] = v
// 分析文章
if v.ID < setting.Conf.StartID {
if v.ID < setting.Conf.General.StartID {
continue
}
if i > 0 {
v.Prev = artcs[i-1]
}
if artcs[i+1].ID >= setting.Conf.StartID {
if artcs[i+1].ID >= setting.Conf.General.StartID {
v.Next = artcs[i+1]
}
ManageTagsArticle(v, false, ADD)
@@ -211,16 +213,16 @@ func generateTopic() {
Author: setting.Conf.Account.Username,
Title: "关于",
Slug: "about",
CreateTime: time.Now(),
UpdateTime: time.Now(),
CreateTime: time.Time{},
UpdateTime: time.Time{},
}
blogroll := &Article{
ID: db.NextVal(DB, COUNTER_ARTICLE),
Author: setting.Conf.Account.Username,
Title: "友情链接",
Slug: "blogroll",
UpdateTime: time.Now(),
CreateTime: time.Now(),
CreateTime: time.Time{},
UpdateTime: time.Time{},
}
err := db.Insert(DB, COLLECTION_ARTICLE, blogroll)
if err != nil {
@@ -242,7 +244,7 @@ func renderPage(md []byte) []byte {
func PageList(p, n int) (prev int, next int, artcs []*Article) {
var l int
for l = len(Ei.Articles); l > 0; l-- {
if Ei.Articles[l-1].ID >= setting.Conf.StartID {
if Ei.Articles[l-1].ID >= setting.Conf.General.StartID {
break
}
}
@@ -271,6 +273,7 @@ func PageList(p, n int) (prev int, next int, artcs []*Article) {
return
}
// 管理 tag
func ManageTagsArticle(artc *Article, s bool, do string) {
switch do {
case ADD:
@@ -295,6 +298,7 @@ func ManageTagsArticle(artc *Article, s bool, do string) {
}
}
// 管理专题
func ManageSeriesArticle(artc *Article, s bool, do string) {
switch do {
case ADD:
@@ -323,6 +327,7 @@ func ManageSeriesArticle(artc *Article, s bool, do string) {
}
}
// 管理归档
func ManageArchivesArticle(artc *Article, s bool, do string) {
switch do {
case ADD:
@@ -360,17 +365,19 @@ func ManageArchivesArticle(artc *Article, s bool, do string) {
}
// 渲染markdown操作和截取摘要操作
var reg = regexp.MustCompile(setting.Conf.Identifier)
var reg = regexp.MustCompile(setting.Conf.General.Identifier)
// header
var regH = regexp.MustCompile("</nav></div>")
func GenerateExcerptAndRender(artc *Article) {
if strings.HasPrefix(artc.Content, setting.Conf.Description) {
if strings.HasPrefix(artc.Content, setting.Conf.General.DescPrefix) {
index := strings.Index(artc.Content, "\r\n")
artc.Desc = IgnoreHtmlTag(artc.Content[len(setting.Conf.Description):index])
artc.Desc = IgnoreHtmlTag(artc.Content[len(setting.Conf.General.DescPrefix):index])
artc.Content = artc.Content[index:]
}
// 查找目录
content := renderPage([]byte(artc.Content))
index := regH.FindIndex(content)
if index != nil {
@@ -384,7 +391,7 @@ func GenerateExcerptAndRender(artc *Article) {
artc.Excerpt = IgnoreHtmlTag(artc.Content[0:index[0]])
} else {
uc := []rune(artc.Content)
length := setting.Conf.Length
length := setting.Conf.General.Length
if len(uc) < length {
length = len(uc)
}
@@ -410,14 +417,16 @@ func LoadTrash() (artcs SortArticles, err error) {
func AddArticle(artc *Article) error {
// 分配ID, 占位至起始id
for {
if id := db.NextVal(DB, COUNTER_ARTICLE); id < setting.Conf.StartID {
if id := db.NextVal(DB, COUNTER_ARTICLE); id < setting.Conf.General.StartID {
continue
} else {
artc.ID = id
break
}
}
if !artc.IsDraft {
// 正式发布文章
defer GenerateExcerptAndRender(artc)
Ei.MapArticles[artc.Slug] = artc
Ei.Articles = append([]*Article{artc}, Ei.Articles...)
@@ -457,6 +466,7 @@ func DelArticles(ids ...int32) error {
return nil
}
// 从链表里删除文章
func DelFromLinkedList(artc *Article) {
if artc.Prev == nil && artc.Next != nil {
artc.Next.Prev = nil
@@ -468,12 +478,13 @@ func DelFromLinkedList(artc *Article) {
}
}
// 将文章添加到链表
func AddToLinkedList(id int32) {
i, artc := GetArticle(id)
if i == 0 && Ei.Articles[i+1].ID >= setting.Conf.StartID {
if i == 0 && Ei.Articles[i+1].ID >= setting.Conf.General.StartID {
artc.Next = Ei.Articles[i+1]
Ei.Articles[i+1].Prev = artc
} else if i > 0 && Ei.Articles[i-1].ID >= setting.Conf.StartID {
} else if i > 0 && Ei.Articles[i-1].ID >= setting.Conf.General.StartID {
artc.Prev = Ei.Articles[i-1]
if Ei.Articles[i-1].Next != nil {
artc.Next = Ei.Articles[i-1].Next
@@ -495,10 +506,10 @@ func GetArticle(id int32) (int, *Article) {
// 定时清除回收箱文章
func timer() {
delT := time.NewTicker(time.Duration(setting.Conf.Clean) * time.Hour)
delT := time.NewTicker(time.Duration(setting.Conf.General.Clean) * time.Hour)
for {
<-delT.C
db.Remove(DB, COLLECTION_ARTICLE, bson.M{"deletetime": bson.M{"$gt": time.Time{}, "$lt": time.Now().Add(time.Duration(setting.Conf.Trash) * time.Hour)}})
db.Remove(DB, COLLECTION_ARTICLE, bson.M{"deletetime": bson.M{"$gt": time.Time{}, "$lt": time.Now().Add(time.Duration(setting.Conf.General.Trash) * time.Hour)}})
}
}
@@ -575,6 +586,7 @@ func QuerySerie(id int32) *Serie {
return nil
}
// 后台分页
func PageListBack(se int, kw string, draft, del bool, p, n int) (max int, artcs []*Article) {
M := bson.M{}
if draft {

View File

@@ -1,6 +1,7 @@
package main
import (
"html/template"
"testing"
)
@@ -42,3 +43,20 @@ func TestAddSerie(t *testing.T) {
t.Error(err)
}
}
func TestRenderPage(t *testing.T) {
data := []byte(`<ul class="links ssl">
<li><a href="https://yryz.net/">一人游走</a><span class="date">「不错的小伙子」</span></li>
<li><a href="https://hsulei.com/">Leo同学</a><span class="date">「小伙子,该干活了」</span></li>
<li><a href="https://razeencheng.com/">razeen同学</a><span class="date">「Stay hungry. Stay foolish.」</span></li>
</ul>
<ul class="links">
<li><a href="http://blog.mirreal.net/">Mirreal Ellison</a><span class="date">「kissing the fire」</span></li>
</ul>`)
t.Log(IgnoreHtmlTag(string(data)))
data = renderPage(data)
t.Log(template.HTML(string(data)))
}

201
disqus.go
View File

@@ -4,34 +4,48 @@ package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
"github.com/deepzz0/logd"
"github.com/eiblog/eiblog/setting"
"github.com/eiblog/utils/logd"
)
type result struct {
var ErrDisqusConfig = errors.New("disqus config incorrect")
// 定时获取所有文章评论数量
type postsCountResp struct {
Code int
Response []struct {
Id string
Posts int
Identifiers []string
}
}
func PostsCount() {
if setting.Conf.Disqus.PostsCount == "" || setting.Conf.Disqus.PublicKey == "" || setting.Conf.Disqus.ShortName == "" {
return
func PostsCount() error {
if setting.Conf.Disqus.PostsCount == "" ||
setting.Conf.Disqus.PublicKey == "" ||
setting.Conf.Disqus.ShortName == "" {
return ErrDisqusConfig
}
time.AfterFunc(time.Duration(setting.Conf.Disqus.Interval)*time.Hour, func() {
err := PostsCount()
if err != nil {
logd.Error(err)
}
})
baseUrl := setting.Conf.Disqus.PostsCount +
"?api_key=" + setting.Conf.Disqus.PublicKey +
"&forum=" + setting.Conf.Disqus.ShortName + "&"
var count, index int
for index < len(Ei.Articles) {
logd.Debugf("count=====%d, index=======%d, length=======%d, bool=========%t\n", count, index, len(Ei.Articles), index < len(Ei.Articles) && count < 50)
var threads []string
for ; index < len(Ei.Articles) && count < 50; index++ {
artc := Ei.Articles[index]
@@ -42,87 +56,92 @@ func PostsCount() {
url := baseUrl + strings.Join(threads, "&")
resp, err := http.Get(url)
if err != nil {
logd.Error(err)
break
return err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
logd.Error(err)
break
return err
}
if resp.StatusCode != http.StatusOK {
logd.Error(string(b))
break
return errors.New(string(b))
}
rst := result{}
err = json.Unmarshal(b, &rst)
result := &postsCountResp{}
err = json.Unmarshal(b, result)
if err != nil {
logd.Error(err)
break
return err
}
for _, v := range rst.Response {
for _, v := range result.Response {
i := strings.Index(v.Identifiers[0], "-")
artc := Ei.MapArticles[v.Identifiers[0][i+1:]]
if artc != nil {
artc.Count = v.Posts
artc.Thread = v.Id
}
}
}
time.AfterFunc(time.Duration(setting.Conf.Disqus.Interval)*time.Hour, PostsCount)
return nil
}
type postsList struct {
// 获取文章评论列表
type postsListResp struct {
Cursor struct {
HasNext bool
Next string
}
Code int
Response []struct {
Parent int
Id string
CreatedAt string
Message string
Author struct {
Name string
ProfileUrl string
Avatar struct {
Cache string
}
}
Thread string
}
Response []postDetail
}
func PostsList(slug, cursor string) *postsList {
if setting.Conf.Disqus.PostsList == "" || setting.Conf.Disqus.PublicKey == "" || setting.Conf.Disqus.ShortName == "" {
return nil
type postDetail struct {
Parent int
Id string
CreatedAt string
Message string
IsDeleted bool
Author struct {
Name string
ProfileUrl string
Avatar struct {
Cache string
}
}
Thread string
}
func PostsList(slug, cursor string) (*postsListResp, error) {
if setting.Conf.Disqus.PostsList == "" ||
setting.Conf.Disqus.PublicKey == "" ||
setting.Conf.Disqus.ShortName == "" {
return nil, ErrDisqusConfig
}
url := setting.Conf.Disqus.PostsList + "?limit=50&api_key=" +
setting.Conf.Disqus.PublicKey + "&forum=" + setting.Conf.Disqus.ShortName +
"&cursor=" + cursor + "&thread:ident=post-" + slug
resp, err := http.Get(url)
if err != nil {
logd.Error(err)
return nil
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
logd.Error(err)
return nil
return nil, err
}
if resp.StatusCode != http.StatusOK {
logd.Error(string(b))
return nil
return nil, errors.New(string(b))
}
pl := &postsList{}
err = json.Unmarshal(b, pl)
result := &postsListResp{}
err = json.Unmarshal(b, result)
if err != nil {
logd.Error(err)
return nil
return nil, err
}
return pl
return result, nil
}
type PostCreate struct {
@@ -136,16 +155,17 @@ type PostCreate struct {
UserAgent string `json:"user_agent"`
}
type PostResponse struct {
Code int `json:"code"`
Response struct {
Id string `json:"id"`
} `json:"response"`
type postCreateResp struct {
Code int
Response postDetail
}
func PostComment(pc *PostCreate) string {
if setting.Conf.Disqus.PostsList == "" || setting.Conf.Disqus.PublicKey == "" || setting.Conf.Disqus.ShortName == "" {
return ""
// 评论文章
func PostComment(pc *PostCreate) (*postCreateResp, error) {
if setting.Conf.Disqus.PostsList == "" ||
setting.Conf.Disqus.PublicKey == "" ||
setting.Conf.Disqus.ShortName == "" {
return nil, ErrDisqusConfig
}
url := setting.Conf.Disqus.PostCreate +
"?api_key=E8Uh5l5fHZ6gD8U3KycjAIAk46f68Zw7C6eW8WSjZvCLXebZ7p0r1yrYDrLilk2F" +
@@ -155,31 +175,74 @@ func PostComment(pc *PostCreate) string {
request, err := http.NewRequest("POST", url, nil)
if err != nil {
logd.Error(err)
return ""
return nil, err
}
request.Header.Set("Referer", "https://disqus.com")
resp, err := http.DefaultClient.Do(request)
if err != nil {
logd.Error(err)
return ""
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
logd.Error(err)
return ""
return nil, err
}
if resp.StatusCode != http.StatusOK {
logd.Error(string(b))
return ""
return nil, errors.New(string(b))
}
pr := &PostResponse{}
err = json.Unmarshal(b, pr)
result := &postCreateResp{}
err = json.Unmarshal(b, result)
if err != nil {
logd.Error(err)
return ""
return nil, err
}
logd.Print(pr.Response.Id)
return pr.Response.Id
return result, nil
}
// 批准评论通过
type approvedResp struct {
Code int `json:"code"`
Response []struct {
Id string `json:"id"`
} `json:"response"`
}
func PostApprove(post string) error {
if setting.Conf.Disqus.PostsList == "" ||
setting.Conf.Disqus.PublicKey == "" ||
setting.Conf.Disqus.ShortName == "" {
return ErrDisqusConfig
}
url := setting.Conf.Disqus.PostApprove +
"?api_key=" + setting.Conf.Disqus.PublicKey +
"&access_token=" + setting.Conf.Disqus.AccessToken +
"&post=" + post
request, err := http.NewRequest("POST", url, nil)
if err != nil {
return err
}
request.Header.Set("Referer", "https://disqus.com")
resp, err := http.DefaultClient.Do(request)
if err != nil {
return err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.New(string(b))
}
result := &approvedResp{}
err = json.Unmarshal(b, result)
if err != nil {
return err
}
return nil
}

View File

@@ -16,10 +16,10 @@ func TestPostComment(t *testing.T) {
AuthorName: "deepzz",
}
id := PostComment(pc)
if id == "" {
t.Error("post failed")
id, err := PostComment(pc)
if err != nil {
t.Error(err)
return
}
t.Log("post success")
t.Log("post success", id)
}

View File

@@ -6,6 +6,8 @@ services:
volumes:
- /data/eiblog/mgodb:/data/db
restart: always
ports:
- 27017:27017
elasticsearch:
image: elasticsearch:2.4.1
container_name: eisearch
@@ -25,8 +27,6 @@ services:
volumes:
- /data/eiblog/logdata:/eiblog/logdata
- /data/eiblog/conf:/eiblog/conf
- /data/eiblog/static:/eiblog/static
- /data/eiblog/views:/eiblog/views
links:
- elasticsearch
- mongodb

20
docs/amusing.md Normal file
View File

@@ -0,0 +1,20 @@
### Twitter Card
相信很多人不明白为什么会这样专注twitter。首先twitter是一个社交网站国际性的。其次我们可以使用它的Twitter Card功能非常的酷。
当你配置好Twitter相关的参数后`conf/app.yml`
```
# twitter地址: twitter.com/chenqijing2
twitter:
card: summary
site: chenqijing2
image: st.deepzz.com/static/img/avatar.jpg
address: twitter.com/chenqijing2
```
每当你发部一个推文,你如果带上你的网址,它会自动给你展示成卡片的形式
![twitter-card](http://7xokm2.com1.z0.glb.clouddn.com/img/twitter-pub.png)
![twitter-card2](http://7xokm2.com1.z0.glb.clouddn.com/img/twitter-pub2.png)
可以看到``之前是没有内容的,该内容是我们文章的描述。

69
docs/autocert.md Normal file
View File

@@ -0,0 +1,69 @@
### 证书自动更新
本博客证书自动更新有两种方式:
* [acme/autocert](https://github.com/golang/crypto/tree/master/acme/autocert),博客内部集成,通过 tls-sni 验证,实现全自动更新证书,一键开启关闭。请在裸服务器的情况下使用(不要使用代理)。单证书
* [acme.sh](https://github.com/Neilpang/acme.sh),强大的 acme 脚本,多种自动更新证书方式,满足你各方面的需求。双证书
#### 方式一
什么是 autocert简单点你只需要两步操作
1. 将域名解析到你的服务器。
2. 在服务器上运行开启 autocert 功能的程序(这里不需要配置证书),需要占用 443 端口。
其它过程你不需要过问,即会完成自动申请证书,自动更新证书的功能(默认 30 天)。这个是在 tcp/ip 层的操作,对用户完全透明,非常棒。
一键开启 autocert 功能,只需修改 `conf/app.yml` 文件内容:
```
# 运行模式
mode:
# http server
enablehttp: true
httpport: 9000
# https server
enablehttps: true # 必须开启
autocert: false # autocert 功能开关
httpsport: 9001
certfile:
keyfile:
domain: deepzz.com # 申请证书的域名,也是博客的域名
```
首先,使用 HTTPS 必须启用 `enablehttps`,它有两个作用:
* 如果 `enablehttp` 开启,会自动 301 重定向到 https。
* 作为开启 autocert 的前提条件。
其次, `autocert` 是否开启也有两个作用:
* false服务器将使用 `httpsport``certfile``keyfile` 作为参数启动 https 服务器。
* true服务器直接使用 443 端口启动 https 服务器,并且自动申请证书,且在证书只有 30 天有效期时自动更新证书。域名为 *运行模式* 下的 mode->domain。
#### 方式二
使用方式二,你需要了解 acme.sh 的具体使用方式,非常简单。选择适合自己的方式实现自动更新证书。
博主,这里实现了 aliyun dns 的自动验证,自动更新证书。详情参见 Makefile->gencert。这里实现了自动申请 ecc、rsa 双证书,并且自动申请 scts自动安装自动更新。
基本流程如下:
1. 创建相关目录:`/data/eiblog/conf/ssl``/data/eiblog/conf/scts/rsa``/data/eiblog/conf/scts/ecc`
2. 自动下载安装 acme.sh 脚本。
3. 自动申请 RSA 证书并且自动获取 scts并且自动安装到指定位置。
4. 自动申请 ECC 证书并且自动获取 scts并且自动安装到指定位置。
##### 使用方式
导出环境变量Aliyun dns 的环境变量为:
```
export Ali_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
export Ali_Secret="jlsdflanljkljlfdsaklkjflsa"
```
执行命令:
```
$ make gencert -cn=common_name -sans="-d example.com -d example1.com"
```

207
docs/install.md Normal file
View File

@@ -0,0 +1,207 @@
### 安装
1、`Eiblog` 提供多个平台的压缩包下载,可到 [Eiblog release](https://github.com/eiblog/eiblog/releases) 选择相应版本和平台下载。也可通过:
``` sh
$ curl -L https://github.com/eiblog/eiblog/releases/download/v1.0.0/eiblog-v1.0.0.`uname -s | tr '[A-Z]' '[a-z]'`-amd64.tar.gz > eiblog-v1.0.0.`uname -s | tr '[A-Z]' '[a-z]'`-amd64.tar.gz
```
2、如果有幸你也是 `Gopher`,相信你会亲自动手,你可以通过:
``` sh
$ git clone https://github.com/eiblog/eiblog.git
```
进行源码编译二进制文件运行。
3、如果你对 `docker` 技术也有研究的话,你也可以通过 `docker` 来安装:
``` sh
$ docker pull registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog:v1.2.0
```
`注意`,镜像内部没有提供 conf 文件夹内的配置内容,因为该内容定制化的需求过高。所以需要将 `conf` 目录映射出来,后面会具体说到。
### 本地测试
采用二进制包进行测试,在下载好可执行程序之后,我们可以开始本地测试的工作了。本地测试需要搭建两个服务 `mongodb` (必须)和 `elasticsearch2.4.1`(可选,搜索服务不可用)。
`Eiblog ` 默认会连接 `hostname` 为 `mongodb` 和 `elasticsearch` 的地址,因此你需要将信息填入 `/etc/hosts` 下。假如你搭建的 `mongodb` 地址为 `127.0.0.1:27017``elasticsearch` 地址为 `192.168.99.100:9200`,如:
``` sh
$ sudo vi /etc/hosts
# 在末尾加上两行
172.42.0.1 mongodb
192.168.99.100 elasticsearch
```
下面先看两个服务的搭建。
#### MongoDB 搭建
`MongoDB` 搭建Mac 可通过 `brew install mongo` 进行安装,其它平台请查询资料。
#### Elasticsearch 搭建
`Elasticsearch `搭建,它的搭建要些许复杂。建议通过 `docker` 搭建。需要注意的是 es 自带的分析器对中文分词是不友好的,这里采用了 `elasticsearch-analysis-ik` 分词器。如果你想了解更多 [Github](https://github.com/medcl/elasticsearch-analysis-ik) 或则如何实现 [博客站内搜索](https://imququ.com/post/elasticsearch.html)。
1. pull 镜像 `docker pull elasticsearch:2.4.1`。
2. 添加环境变量 `ES_JAVA_OPTS: "-Xms512m -Xmx512m"`,除非你想让你的服务器爆掉。
3. 映射相关目录:
```
$PWD/conf/es/config:/usr/share/elasticsearch/config
$PWD/conf/es/plugins:/usr/share/elasticsearch/plugins
```
博主已经准备好了必要的 es 配置文件,请将这四个目录映射至 `eiblog` 下的 `conf` 目录。如果你想查看更多,请查看 `docker-compose.yml` 文件。
总结一下,`docker` 运行 es 的命令为:
``` sh
$ docker run -d --name eisearch \
-p 9200:9200 \
-e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
-v $PWD/conf/es/config:/usr/share/elasticsearch/config \
-v $PWD/conf/es/plugins:/usr/share/elasticsearch/plugins \
elasticsearch:2.4.1
```
之后执行 `./eiblog`,咱们的 `eiblog` 就可以运行起来了。
通过 `127.0.0.1:9000` 可以进入博客首页,`127.0.0.1:9000/admin/login` 进入后台登陆,账号密码为 `eiblog/conf/app.yml` 下的 `username` 和 `password`。初始账号密码 `deepz`、`deepzz`。
> `注意`,因为配置 `conf/app.yml` 均是博主自用配置。有些操作可能(如评论)会评论到我的博客,还请尽量避免,谢谢。
### 准备部署
如果你在感受了该博客的魅力了之后,仍然坚持想要搭建它。那么,恭喜你,获得的一款不想再更换的博客系统。下面,我们跟随步骤对部署流程进一步说明。
这里只提供 `Docker` 的相关部署说明。你如果需要其它方式部署,请参考该方式。
#### 前提准备
这里需要准备一些必要的东西,如果你已准备好。请跳过。
* `一台服务器`。
* `一个域名`,国内服务器需备案。
* `有效的证书`。通过开启 autocert 可自动申请更新证书。也可去七牛、qcloud 申请一年有效证书。
* `七牛CDN`。博客只设计接入了 七牛cdn相信该 CDN 服务商不会让你失望。
* `Disqus`。作为博客评论系统,你得有翻墙的能力注册到该账号,具体配置我想又可以写一片博客了。简单说需要 `shorname` 和 `public key`。
* `Google Analystic`。数据统计分析工具。
* `Superfeedr`。加速 RSS 订阅。
* `Twitter`。希望你能够有一个 twitter 账号。
是不是这么多要求,很费解。其实当初该博客系统只是为个人而设计的,是自己心中想要的那一款。博主些这篇文章不是想要多少人来用该博客,而是希望对那些追求至极的朋友说:你需要这款博客系统。
#### 文件准备
博主是一个有强迫症的人,一些文件的路径我使用了固定的路径,请大家见谅。假如你的 cdn 域名为 `st.example.com`,你需要确定这些文件已经在你的 cdn 中,它们路径分别是:
| 文件 | 地址 | 描述 |
| ------------------ | ---------------------------------------- | ---------------------------------------- |
| favicon.ico | st.example.com/static/img/favicon.ico | cdn 中的文件名为 `static/img/favicon.ico`。你也可以复制 favicon.ico 到 static 文件夹下,通过 example.com/favicon.ico 也是能够访问到。docker 用户可能需要重新打包镜像。 |
| bg04.jpg | st.example.com/static/img/bg04.jpg | 首页左侧的大背景图,需要更名请到 views/st_blog.css 修改。 |
| avatar.jpg | st.example.com/static/img/avatar.jpg | 头像 |
| blank.gif | st.example.com/static/img/blank.gif | 空白图片,[下载](https://st.deepzz.com/static/img/blank.gif) |
| default_avatar.png | st.example.com/static/img/default_avatar.png | disqus 默认图片,[下载](https://st.deepzz.com/static/img/default_avatar.png) |
| disqus.js | st.example.com/static/js/disqus_xxx.js | disqus 文件,你可以通过 https://short_name.disqus.com/embed.js 下载你的专属文件,并上传到七牛。更新配置文件 app.yml。 |
> 注意cdn 提到的文件下载,请复制链接进行下载,因为博主使用了防盗链功能,还有:
 1、每次修改 app.yml 文件(如:更换 cdn 域名或更新头像),如果你不知道是否应该提高 staticversion 一个版本,那么最好提高一个 +1。
2、每次手动修改 views 内的以 `st_` 开头的文件,请将 `app.yml` 中的 staticversion 提高一个版本。
#### 配置说明
走到这里,我相信只走到 `60%` 的路程。放弃还来得及。
这里会对 `eiblog/conf` 下的所有文件做说明,希望你做好准备。
```
├── app.yml # 博客配置文件
├── blackip.yml # 博客 ip 黑名单
├── es # elasticsearch 配置
│   ├── config # 配置文件
│   │   ├── analysis # 同义词
│   │   ├── elasticsearch.yml # 具体配置
│   │   ├── logging.yml # 日志配置
│   │   └── scripts # 脚本文件夹
│   └── plugins # 插件文件夹
│   └── ik1.10.1 # ik 分词器
├── nginx # nginx 配置
│   ├── domain # 域名配置nginx 会读区改文件夹下的 .conf 文件
│   │   └── eiblog.conf
│   ├── ip.blacklist # nginx ip黑名单
│   └── nginx.conf # nginx 配置,请替换 nginx 原有配置
├── scts # ct 透明
│   ├── ecc
│   │   ├── aviator.sct
│   │   └── digicert.sct
│   └── rsa
│   ├── aviator.sct
│   └── digicert.sct
├── ssl # 证书相关文件,可参考 eiblog.conf 生成
│   ├── dhparams.pem
│   ├── domain.rsa.key
│   ├── domain.rsa.pem
│   ├── full_chained.pem
│   └── session_ticket.key
└── tpl # 模版文件
├── crossdomainTpl.xml
├── feedTpl.xml
├── opensearchTpl.xml
├── robotsTpl.xml
└── sitemapTpl.xml
```
| 名称 | 描述 |
| ----------- | ---------------------------------------- |
| app.yml | 整个程序的配置文件,里面已经列出了所有配置项的说明,这里不再阐述。 |
| blackip.yml | 如果没有使用 `Nginx`,博客内置 `ip` 过滤系统。 |
| es | elasticsearch非常强大的分布式搜索引擎`github` 用的就是它。里面的配置基本不用修改,但 `es/analysis/synonym.txt` 是同义词你可以照着已有的随意增加。scripts 是 es 的脚本文件夹 |
| nginx | 系统采用 `nginx` 作为代理(相信博客系统也不会独占一台服务器~)。请使用 `nginx.conf` 替换原 `nginx` 的配置。博客系统的配置文件是 `domain/eiblog.conf`,或则重命名(只要是满足`*.conf`)。`eiblog.conf`文件里面学问是最多的。或许你想一一弄懂,或许…。注意本配置需要更新 nginx 到最新版openssl 更新到1.0.2j,具体请到 Jerry Qu 的 [本博客 Nginx 配置之完整篇](https://imququ.com/post/my-nginx-conf.html) 查看,了解详情。 |
| scts | 存放 ct 文件。 |
| ssl | 这里存放了所有证书相关的内容。 |
| tpl | 模版相关,不用修改。 |
### 开始部署
#### docker
请确定你已经完成了上面所说的所有步骤,在本地已经测试成功。服务器上 `MognoDB` 和`Elasticsearch` 已经安装并已经运行成功。
首先,请将本地测试好的 `conf` 文件夹上传至服务器,建议存储到服务器 `/data/eiblog` 下。
``` sh
$ tree /data/eiblog -L 1
├── conf
```
然后,将镜像 PULL 到服务器本地。
``` sh
# PULL下Eiblog镜像
$ docker pull registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
```
最后,执行 `docker run` 命令,希望你能成功。
``` sh
$ docker run -d --name eiblog --restart=always \
--add-host disqus.com:23.235.33.134 \
--add-host mongodb:172.42.0.1 \
--add-host elasticsearch:192.168.99.100 \
-p 9000:9000 \
-e GODEBUG=netdns=cgo \
-v /data/eiblog/logdata:/eiblog/logdata \
-v /data/eiblog/conf:/eiblog/conf \
registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
```
这里默认 `MongDB` 和 `Elasticsearch` 均为 `docker` 部署,且名称为`eidb``eisearch`。
#### nginx + docker
通过 `Nginx+docker` 部署,是博主推荐的方式。这里采用 `Docker Compose` 管理我们整个博客系统。
请确认你已经成功安装好 `Nginx`、`docker`、`docker-compose`。Nginx 请一定参照 Jerry Qu 的[Nginx 配置完整篇](https://imququ.com/post/my-nginx-conf.html)。
首先,请将本地测试好的 `conf``docker-compose.yml` 文件夹和文件上传至服务器。`conf` 建议存储到服务器 `/data/eiblog` 下,`docker-compose.yml` 存放在你使用方便的地方。
``` sh
$ tree /data/eiblog -L 1
├── conf
$ ls ~/
docker-compose.yml
```
然后,执行:
``` sh
$ cd ~
$ docker-compose up -d
```
等待些许时间,成功运行。

74
docs/writing.md Normal file
View File

@@ -0,0 +1,74 @@
### 郑重提醒
**标题**、**slug**、**内容**。在你点击保存的时候一定确保三者不能为空,否则页面刷新内容就没了。所以,养成一个良好的写作习惯很重要。
当然,博客的自动保存功能也非常的好。在你不确定是否发布前,你可以将之保存到草稿,以便下次继续编辑。
### 文章标题
文章标题,这个可能要看个人习惯。我习惯从三级标题开始(###),依次往下四级标题,五级标题...。要注意的是一定不能跳级:
```
### 标题一
#### 标题1.1
#### 标题1.2
##### 标题1.2.1
##### 标题1.2.2
### 标题二
##### 标题2.1
##### 标题2.2
###### 标题2.2.1
###### 标题2.2.2
```
结果是:
![article-title](http://7xokm2.com1.z0.glb.clouddn.com/article-title.png)
### 文章描述
文章描述,主要是给`html->head->meta`中的 name 为 description 用的。现采用了一个临时的办法:在文章的第一行通过前缀识别(只看第一行)。
该前缀可到`conf/app.yml`设置,默认为`Desc:`,如:
![article-description](http://7xokm2.com1.z0.glb.clouddn.com/img/article-description.png)
### 图片懒加载
博客系统提供图片懒加载功能(浏览到某个位置,图片才会加载),以此来提高页面加载速度。我们可根据需要是否使用。
当然由此带来的坏处就是rss不能够正确加载图片。后续看是否解决这个问题或朋友提PR。
首先看下图片的`markdown`标准写法:
```
![alt](img_addres)
```
如:
```
![sublime-dialog](https://st.deepzz.com/blog/img/dialog-box-without-all-contols.png)
```
![sublime-dialog](https://st.deepzz.com/blog/img/dialog-box-without-all-contols.png)
懒加载,需要为该图片指定大小(长高):
```
![alt](img_addres =widthxheight)
```
x 为小写字母x,y,z中的 x。使页面未加载时也占了相应的位置大小这样设计是为了让读者在浏览页面时不会感到抖动。
如:
```
![sublime-dialog](https://st.deepzz.com/blog/img/dialog-box-without-all-contols.png =640x301)
```
### 摘要截取
摘要截取主要是提供给首页显示,如:
![home-page](http://7xokm2.com1.z0.glb.clouddn.com/img/deepzz_home_page.jpg)
红框中圈出来的就是截取出来的内容。在 `conf/app.yml` 的配置项有两个:
```
# 自动截取预览, 字符数
length: 400
# 截取预览标识
identifier: <!--more-->
```
当程序不能检查到 identifier 的标识符时,会采用长度的方式进行截取。

View File

@@ -11,7 +11,6 @@ import (
"strings"
"time"
"github.com/eiblog/eiblog/setting"
"github.com/eiblog/utils/logd"
)
@@ -26,11 +25,13 @@ const (
var es *ElasticService
// 初始化 Elasticsearch 服务器
func init() {
es = &ElasticService{url: setting.Conf.SearchURL, c: new(http.Client)}
es = &ElasticService{url: "http://elasticsearch:9200", c: new(http.Client)}
initIndex()
}
// 创建索引
func initIndex() {
mappings := fmt.Sprintf(`{"mappings":{"%s":{"properties":{"content":{"analyzer":"ik_syno","search_analyzer":"ik_syno","term_vector":"with_positions_offsets","type":"string"},"date":{"index":"not_analyzed","type":"date"},"slug":{"type":"string"},"tag":{"index":"not_analyzed","type":"string"},"title":{"analyzer":"ik_syno","search_analyzer":"ik_syno","term_vector":"with_positions_offsets","type":"string"}}}}}`, TYPE)
err := CreateIndexAndMappings(INDEX, TYPE, []byte(mappings))
@@ -39,6 +40,7 @@ func initIndex() {
}
}
// 查询
func Elasticsearch(qStr string, size, from int) *ESSearchResult {
// 分析查询字符串
reg := regexp.MustCompile(`(tag|slug|date):`)
@@ -96,6 +98,7 @@ func Elasticsearch(qStr string, size, from int) *ESSearchResult {
return docs
}
// 添加或更新索引
func ElasticIndex(artc *Article) error {
img := PickFirstImage(artc.Content)
mapping := map[string]interface{}{
@@ -110,6 +113,7 @@ func ElasticIndex(artc *Article) error {
return IndexOrUpdateDocument(INDEX, TYPE, artc.ID, b)
}
// 删除索引
func ElasticDelIndex(ids []int32) error {
var target []string
for _, id := range ids {
@@ -128,10 +132,12 @@ type IndicesCreateResult struct {
Acknowledged bool `json:"acknowledged"`
}
// 返回 url
func (s *ElasticService) ParseURL(format string, params ...interface{}) string {
return fmt.Sprintf(s.url+format, params...)
}
// Elastic 相关操作请求
func (s *ElasticService) Do(req *http.Request) (interface{}, error) {
resp, err := s.c.Do(req)
if err != nil {
@@ -153,11 +159,8 @@ func (s *ElasticService) Do(req *http.Request) (interface{}, error) {
return b, nil
case "HEAD":
return resp.StatusCode, nil
default:
return nil, errors.New("unknown methods")
}
return nil, nil
return nil, errors.New("unknown methods")
}
func CreateIndexAndMappings(index, typ string, mappings []byte) (err error) {
@@ -188,6 +191,7 @@ func CreateIndexAndMappings(index, typ string, mappings []byte) (err error) {
return nil
}
// 创建或更新索引
func IndexOrUpdateDocument(index, typ string, id int32, doc []byte) (err error) {
req, err := http.NewRequest("PUT", es.ParseURL("/%s/%s/%d", index, typ, id), bytes.NewReader(doc))
if err != nil {
@@ -214,6 +218,7 @@ type ESDeleteResult struct {
} `json:"iterms"`
}
// 删除文档
func DeleteDocument(index, typ string, ids []string) error {
var buff bytes.Buffer
for _, id := range ids {
@@ -248,6 +253,7 @@ func DeleteDocument(index, typ string, ids []string) error {
return nil
}
// 查询结果
type ESSearchResult struct {
Took float32 `json:"took"`
Hits struct {
@@ -269,6 +275,7 @@ type ESSearchResult struct {
} `json:"hits"`
}
// DSL 语句查询文档
func IndexQueryDSL(index, typ string, size, from int, dsl []byte) (*ESSearchResult, error) {
req, err := http.NewRequest("POST", es.ParseURL("/%s/%s/_search?size=%d&from=%d", index, typ, size, from), bytes.NewReader(dsl))
if err != nil {

156
front.go
View File

@@ -20,13 +20,46 @@ import (
func Filter() gin.HandlerFunc {
return func(c *gin.Context) {
// 过滤黑名单
BlackFilter(c)
if BlackFilter(c) {
c.Abort()
return
}
// 重定向
if Redirect(c) {
c.Abort()
return
}
// 用户cookie用于统计
UserCookie(c)
c.Next()
}
}
// 黑名单过滤
func BlackFilter(c *gin.Context) bool {
ip := c.ClientIP()
if setting.BlackIP[ip] {
c.String(http.StatusForbidden, "Your IP is blacklisted.")
return true
}
return false
}
// 重定向
func Redirect(c *gin.Context) bool {
if setting.Conf.Mode.EnableHttps && c.Request.ProtoMajor == 1 {
var port string
if strings.Contains(c.Request.Host, ":") {
port = fmt.Sprintf(":%d", setting.Conf.Mode.HttpsPort)
}
c.Redirect(http.StatusMovedPermanently, "https://"+setting.Conf.Mode.Domain+port+c.Request.RequestURI)
return true
}
return false
}
// 用户识别
func UserCookie(c *gin.Context) {
cookie, err := c.Cookie("u")
@@ -35,15 +68,6 @@ func UserCookie(c *gin.Context) {
}
}
// 黑名单过滤
func BlackFilter(c *gin.Context) {
ip := c.ClientIP()
if setting.BlackIP[ip] {
c.Abort()
c.String(http.StatusForbidden, "Your IP is blacklisted.")
}
}
// 解析静态文件版本
func StaticVersion(c *gin.Context) (version int) {
cookie, err := c.Request.Cookie("v")
@@ -55,7 +79,6 @@ func StaticVersion(c *gin.Context) (version int) {
func GetBase() gin.H {
return gin.H{
"Favicon": setting.Conf.Favicon,
"BlogName": Ei.BlogName,
"SubTitle": Ei.SubTitle,
"Twitter": setting.Conf.Twitter,
@@ -63,10 +86,12 @@ func GetBase() gin.H {
"BTitle": Ei.BTitle,
"BeiAn": Ei.BeiAn,
"Domain": setting.Conf.Mode.Domain,
"Kodo": setting.Conf.Kodo,
"Qiniu": setting.Conf.Qiniu,
"Disqus": setting.Conf.Disqus,
}
}
// not found
func HandleNotFound(c *gin.Context) {
h := GetBase()
h["Version"] = StaticVersion(c)
@@ -77,6 +102,7 @@ func HandleNotFound(c *gin.Context) {
RenderHTMLFront(c, "notfound", h)
}
// 首页
func HandleHomePage(c *gin.Context) {
h := GetBase()
h["Version"] = StaticVersion(c)
@@ -88,11 +114,12 @@ func HandleHomePage(c *gin.Context) {
if err != nil || pn < 1 {
pn = 1
}
h["Prev"], h["Next"], h["List"] = PageList(pn, setting.Conf.PageNum)
h["Prev"], h["Next"], h["List"] = PageList(pn, setting.Conf.General.PageNum)
c.Status(http.StatusOK)
RenderHTMLFront(c, "home", h)
}
// 专题页
func HandleSeriesPage(c *gin.Context) {
h := GetBase()
h["Version"] = StaticVersion(c)
@@ -105,6 +132,7 @@ func HandleSeriesPage(c *gin.Context) {
RenderHTMLFront(c, "series", h)
}
// 归档页
func HandleArchivesPage(c *gin.Context) {
h := GetBase()
h["Version"] = StaticVersion(c)
@@ -117,13 +145,14 @@ func HandleArchivesPage(c *gin.Context) {
RenderHTMLFront(c, "archives", h)
}
// 文章
func HandleArticlePage(c *gin.Context) {
path := c.Param("slug")
artc := Ei.MapArticles[path[0:strings.Index(path, ".")]]
if artc == nil {
if !strings.HasSuffix(path, ".html") || Ei.MapArticles[path[:len(path)-5]] == nil {
HandleNotFound(c)
return
}
artc := Ei.MapArticles[path[:len(path)-5]]
h := GetBase()
h["Version"] = StaticVersion(c)
h["Title"] = artc.Title + " | " + Ei.BTitle
@@ -154,6 +183,7 @@ func HandleArticlePage(c *gin.Context) {
RenderHTMLFront(c, name, h)
}
// 搜索页
func HandleSearchPage(c *gin.Context) {
h := GetBase()
h["Version"] = StaticVersion(c)
@@ -171,7 +201,7 @@ func HandleSearchPage(c *gin.Context) {
h["Word"] = q
var result *ESSearchResult
vals := c.Request.URL.Query()
result = Elasticsearch(q, setting.Conf.PageNum, start-1)
result = Elasticsearch(q, setting.Conf.General.PageNum, start-1)
if result != nil {
result.Took /= 1000
for i, v := range result.Hits.Hits {
@@ -180,12 +210,12 @@ func HandleSearchPage(c *gin.Context) {
}
}
h["SearchResult"] = result
if start-setting.Conf.PageNum > 0 {
vals.Set("start", fmt.Sprint(start-setting.Conf.PageNum))
if start-setting.Conf.General.PageNum > 0 {
vals.Set("start", fmt.Sprint(start-setting.Conf.General.PageNum))
h["Prev"] = vals.Encode()
}
if result.Hits.Total >= start+setting.Conf.PageNum {
vals.Set("start", fmt.Sprint(start+setting.Conf.PageNum))
if result.Hits.Total >= start+setting.Conf.General.PageNum {
vals.Set("start", fmt.Sprint(start+setting.Conf.General.PageNum))
h["Next"] = vals.Encode()
}
}
@@ -196,6 +226,7 @@ func HandleSearchPage(c *gin.Context) {
RenderHTMLFront(c, "search", h)
}
// 评论页
func HandleDisqusFrom(c *gin.Context) {
params := strings.Split(c.Param("slug"), "|")
if len(params) != 4 || params[1] == "" {
@@ -207,6 +238,7 @@ func HandleDisqusFrom(c *gin.Context) {
"Title": "发表评论 | " + Ei.BTitle,
"ATitle": artc.Title,
"Thread": params[1],
"Slug": artc.Slug,
}
err := Tmpl.ExecuteTemplate(c.Writer, "disqus.html", data)
if err != nil {
@@ -215,22 +247,36 @@ func HandleDisqusFrom(c *gin.Context) {
c.Header("Content-Type", "text/html; charset=utf-8")
}
// feed
func HandleFeed(c *gin.Context) {
http.ServeFile(c.Writer, c.Request, "static/feed.xml")
}
// opensearch
func HandleOpenSearch(c *gin.Context) {
http.ServeFile(c.Writer, c.Request, "static/opensearch.xml")
}
// robots
func HandleRobots(c *gin.Context) {
http.ServeFile(c.Writer, c.Request, "static/robots.txt")
}
// sitemap
func HandleSitemap(c *gin.Context) {
http.ServeFile(c.Writer, c.Request, "static/sitemap.xml")
}
// cross domain
func HandleCrossDomain(c *gin.Context) {
http.ServeFile(c.Writer, c.Request, "static/crossdomain.xml")
}
// favicon
func HandleFavicon(c *gin.Context) {
http.ServeFile(c.Writer, c.Request, "static/favicon.ico")
}
// 服务端推送谷歌统计
func HandleBeacon(c *gin.Context) {
ua := c.Request.UserAgent()
@@ -245,12 +291,13 @@ func HandleBeacon(c *gin.Context) {
vals.Set("dl", c.Request.Referer())
vals.Set("uip", c.ClientIP())
go func() {
req, err := http.NewRequest("POST", "https://www.google-analytics.com/collect", strings.NewReader(vals.Encode()))
req, err := http.NewRequest("POST", setting.Conf.Google.URL, strings.NewReader(vals.Encode()))
if err != nil {
logd.Error(err)
return
}
req.Header.Set("User-Agent", ua)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
res, err := http.DefaultClient.Do(req)
if err != nil {
logd.Error(err)
@@ -275,7 +322,7 @@ type DisqusComments struct {
ErrMsg string `json:"errmsg"`
Data struct {
Next string `json:"next"`
Total int `json:"total,omitempty"`
Total int `json:"total"`
Comments []commentsDetail `json:"comments"`
Thread string `json:"thread"`
} `json:"data"`
@@ -287,17 +334,25 @@ type commentsDetail struct {
Name string `json:"name"`
Url string `json:"url"`
Avatar string `json:"avatar"`
CreatedAt string `json:"createdAt"`
CreatedAtStr string `json:"createdAtStr"`
Message string `json:"message"`
IsDeleted bool `json:"isDeleted"`
}
func HandleDisqus(c *gin.Context) {
slug := c.Param("slug")
cursor := c.Query("cursor")
dcs := DisqusComments{}
postsList := PostsList(slug, cursor)
if postsList != nil {
if artc := Ei.MapArticles[slug]; artc != nil {
dcs.Data.Thread = artc.Thread
}
postsList, err := PostsList(slug, cursor)
if err != nil {
logd.Error(err)
dcs.ErrNo = FAIL
dcs.ErrMsg = "系统错误"
} else {
dcs.ErrNo = postsList.Code
if postsList.Cursor.HasNext {
dcs.Data.Next = postsList.Cursor.Next
@@ -314,30 +369,35 @@ func HandleDisqus(c *gin.Context) {
Parent: v.Parent,
Url: v.Author.ProfileUrl,
Avatar: v.Author.Avatar.Cache,
CreatedAt: v.CreatedAt,
CreatedAtStr: ConvertStr(v.CreatedAt),
Message: v.Message,
IsDeleted: v.IsDeleted,
}
}
} else {
dcs.ErrNo = FAIL
dcs.ErrMsg = "系统错误"
}
c.JSON(http.StatusOK, dcs)
}
// 发表评论
// [thread:[5279901489] parent:[] identifier:[post-troubleshooting-https] next:[] author_name:[你好] author_email:[chenqijing2@163.com] message:[fdsfdsf]]
type DisqusCreate struct {
ErrNo int `json:"errno"`
ErrMsg string `json:"errmsg"`
Data commentsDetail `json:"data"`
}
func HandleDisqusCreate(c *gin.Context) {
rep := gin.H{"errno": SUCCESS, "errmsg": ""}
defer c.JSON(http.StatusOK, rep)
resp := &DisqusCreate{}
defer c.JSON(http.StatusOK, resp)
msg := c.PostForm("message")
email := c.PostForm("author_email")
name := c.PostForm("author_name")
thread := c.PostForm("thread")
identifier := c.PostForm("identifier")
if msg == "" || email == "" || name == "" || thread == "" || identifier == "" {
rep["errno"] = FAIL
rep["errmsg"] = "参数错误"
resp.ErrNo = FAIL
resp.ErrMsg = "参数错误"
return
}
pc := &PostCreate{
@@ -350,16 +410,34 @@ func HandleDisqusCreate(c *gin.Context) {
IpAddress: c.ClientIP(),
}
id := PostComment(pc)
if id == "" {
rep["errno"] = FAIL
rep["errmsg"] = "系统错误"
postDetail, err := PostComment(pc)
if err != nil {
logd.Error(err)
resp.ErrNo = FAIL
resp.ErrMsg = "系统错误"
return
}
rep["errno"] = SUCCESS
rep["data"] = gin.H{"id": id}
err = PostApprove(postDetail.Response.Id)
if err != nil {
logd.Error(err)
resp.ErrNo = FAIL
resp.ErrMsg = "系统错误"
return
}
resp.ErrNo = SUCCESS
resp.Data = commentsDetail{
Id: postDetail.Response.Id,
Name: name,
Parent: postDetail.Response.Parent,
Url: postDetail.Response.Author.ProfileUrl,
Avatar: postDetail.Response.Author.Avatar.Cache,
CreatedAtStr: ConvertStr(postDetail.Response.CreatedAt),
Message: postDetail.Response.Message,
IsDeleted: postDetail.Response.IsDeleted,
}
}
// 渲染页面
func RenderHTMLFront(c *gin.Context, name string, data gin.H) {
var buf bytes.Buffer
err := Tmpl.ExecuteTemplate(&buf, name, data)

76
glide.lock generated
View File

@@ -1,24 +1,28 @@
hash: bd360fa297ed66950543990f9433cdcdf13c29dd99d9a01b49027e236b2cb9da
updated: 2017-02-07T19:58:01.805533338+08:00
hash: c733fa4abeda21b59b001578b37a168bd33038d337b61198cc5fd94be8bfdf77
updated: 2017-11-05T12:08:01.167405372+08:00
imports:
- name: github.com/boj/redistore
version: fc113767cd6b051980f260d6dbe84b2740c46ab0
version: 4562487a4bee9a7c272b72bfaeda4917d0a47ab9
- name: github.com/deepzz0/logd
version: 2bbe53d047054777f3a171cdfc6dca7aa9f8af78
- name: github.com/eiblog/blackfriday
version: c0ec111761ae784fe31cc076f2fa0e2d2216d623
- name: github.com/eiblog/utils
version: ad2f63940c4f16d0dbfc3f4df59e8cb7af0f80ec
version: ddfd888542f9a093000f71c3709009c1440a0789
subpackages:
- logd
- mgo
- tmpl
- uuid
- name: github.com/garyburd/redigo
version: 908534c8b97586a4597e3fa195875d2d26502b97
version: 47dc60e71eed504e3ef8e77ee3c6fe720f3be57f
subpackages:
- internal
- redis
- name: github.com/gin-gonic/autotls
version: 8ca25fbde72bb72a00466215b94b489c71fcb815
- name: github.com/gin-gonic/contrib
version: 4d2dccc9a4541014fec054e483cc76609b97fb16
version: 5aa1e38d1d932e45fa5032bd1b8739e1a548e596
subpackages:
- sessions
- name: github.com/gin-gonic/gin
@@ -33,21 +37,40 @@ imports:
- name: github.com/gorilla/context
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
- name: github.com/gorilla/securecookie
version: fa5329f913702981df43dcb2a380bac429c810b5
version: e59506cc896acb7f7bf732d4fdf5e25f7ccd8983
- name: github.com/gorilla/sessions
version: 83c8db3bdc9be789e57e3756ffbcffd2d7d40176
version: a3acf13e802c358d65f249324d14ed24aac11370
- name: github.com/manucorporat/sse
version: ee05b128a739a0fb76c7ebd3ae4810c1de808d6d
- name: github.com/mattn/go-isatty
version: 30a891c33c7cde7b02a981314b4228ec99380cca
version: a5cdd64afdee435007ee3e9f6ed4684af949d568
- name: github.com/qiniu/api.v7
version: b7c7d6a2ce0aff8e5e7d14c39c3cde867efa1123
subpackages:
- auth/qbox
- conf
- storage
- name: github.com/qiniu/x
version: f512abcf45ab4e2ba0fd4784c57b53d495997d66
subpackages:
- bytes.v7
- bytes.v7/seekable
- ctype.v7
- rpc.v7
- xlog.v7
- name: github.com/shurcooL/sanitized_anchor_name
version: 1dba4b3954bc059efc3991ec364f9f9a35f597d2
version: 86672fcb3f950f35f2e675df2240550f2a50762f
- name: golang.org/x/crypto
version: bd6f299fb381e4c3393d1c4b1f0b94f5e77650c8
subpackages:
- acme
- acme/autocert
- name: golang.org/x/net
version: f315505cf3349909cdf013ea56690da34e96a451
subpackages:
- context
- name: golang.org/x/sys
version: 7a6e5648d140666db5d920909e082ca00a87ba2c
version: 8eb05f94d449fdf134ec24630ce69ada5b469c1c
subpackages:
- unix
- name: gopkg.in/go-playground/validator.v8
@@ -60,24 +83,23 @@ imports:
- internal/sasl
- internal/scram
- name: gopkg.in/yaml.v2
version: 4c78c975fe7c825c6d1466c42be594d1d6f3aba6
- name: qiniupkg.com/api.v7
version: 7cfd4b639917bf924d8c1cd17a6d61175e809066
subpackages:
- api
- auth/qbox
- conf
- kodo
- kodocli
version: eb3733d160e74a9c7e442f435eb3bea458e1d19f
- name: qiniupkg.com/x
version: f512abcf45ab4e2ba0fd4784c57b53d495997d66
version: 946c4a16076d6d98aeb78619e2bd4012357f7228
subpackages:
- bytes.v7
- bytes.v7/seekable
- ctype.v7
- log.v7
- reqid.v7
- rpc.v7
- url.v7
- xlog.v7
testImports: []
testImports:
- name: github.com/davecgh/go-spew
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
subpackages:
- spew
- name: github.com/pmezard/go-difflib
version: 792786c7400a136282c1664665ae0a8db921c6c2
subpackages:
- difflib
- name: github.com/stretchr/testify
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
subpackages:
- assert

View File

@@ -1,5 +1,6 @@
package: github.com/eiblog/eiblog
import:
- package: github.com/deepzz0/logd
- package: github.com/eiblog/blackfriday
- package: github.com/eiblog/utils
subpackages:
@@ -7,19 +8,20 @@ import:
- mgo
- tmpl
- uuid
- package: github.com/gin-gonic/autotls
- package: github.com/gin-gonic/contrib
subpackages:
- sessions
- package: github.com/gin-gonic/gin
version: ~1.1.4
- package: github.com/qiniu/api.v7
subpackages:
- auth/qbox
- storage
- package: gopkg.in/mgo.v2
subpackages:
- bson
- package: gopkg.in/yaml.v2
- package: qiniupkg.com/api.v7
testImport:
- package: github.com/stretchr/testify
subpackages:
- kodo
- kodocli
- package: qiniupkg.com/x
subpackages:
- url.v7
- assert

View File

@@ -18,6 +18,7 @@ const (
FAIL
)
// 月份转换
var monthToDays = map[time.Month]int{
time.January: 31,
time.February: 28,
@@ -43,14 +44,17 @@ func EncryptPasswd(name, pass string) string {
return fmt.Sprintf("%x", h.Sum(nil))
}
// 验证密码
func VerifyPasswd(origin, name, input string) bool {
return origin == EncryptPasswd(name, input)
}
// 随机 uuid
func RandUUIDv4() string {
return uuid.NewV4().String()
}
// 读取目录
func ReadDir(dir string, filter func(name string) bool) (files []string) {
fis, err := ioutil.ReadDir(dir)
if err != nil {
@@ -69,16 +73,18 @@ func ReadDir(dir string, filter func(name string) bool) (files []string) {
return
}
// 去掉 html tag
func IgnoreHtmlTag(src string) string {
//去除所有尖括号内的HTML代码
re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
// 去除所有尖括号内的HTML代码
re, _ := regexp.Compile(`<[\S\s]+?>`)
src = re.ReplaceAllString(src, "")
//去除换行符
re, _ = regexp.Compile("\\s{2,}")
// 去除换行符
re, _ = regexp.Compile(`\s+`)
return re.ReplaceAllString(src, "")
}
// 获取第一张图片
func PickFirstImage(html string) string {
re, _ := regexp.Compile(`data-src="(.*?)"`)
sli := re.FindAllStringSubmatch(html, 1)
@@ -98,15 +104,16 @@ const (
YEARS_AGO = "%d年前"
)
// 时间转换为间隔
func ConvertStr(str string) string {
t, err := time.ParseInLocation("2006-01-02T15:04:05", str, time.UTC)
if err != nil {
logd.Error(err, str)
return JUST_NOW
}
now := time.Now()
now := time.Now().UTC()
y1, m1, d1 := t.Date()
y2, m2, d2 := now.UTC().Date()
y2, m2, d2 := now.Date()
h1, mi1, s1 := t.Clock()
h2, mi2, s2 := now.Clock()
if y := y2 - y1; y > 1 || (y == 1 && m2-m1 >= 0) {
@@ -123,6 +130,7 @@ func ConvertStr(str string) string {
return JUST_NOW
}
// 获取天数
func dayIn(year int, m time.Month) int {
if m == time.February && isLeap(year) {
return 29
@@ -130,6 +138,7 @@ func dayIn(year int, m time.Month) int {
return monthToDays[m]
}
// 是否是闰年
func isLeap(year int) bool {
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
}

58
helper_test.go Normal file
View File

@@ -0,0 +1,58 @@
// Package main provides ...
package main
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestReadDir(t *testing.T) {
files := ReadDir("setting", func(name string) bool { return false })
assert.Len(t, files, 2)
}
func TestIgnoreHtmlTag(t *testing.T) {
testStr := []string{
"<script>hello</script>",
}
expectStr := []string{
"hello",
}
for i, v := range testStr {
assert.Equal(t, expectStr[i], IgnoreHtmlTag(v))
}
}
func TestPickFirstImage(t *testing.T) {
testStr := []string{
`<img width="480" height="310" alt="acme_aliyun_1" src="https://st.deepzz.com/blog/img/acme_aliyun_1.jpg">`,
`<img width="480" height="310" alt="acme_aliyun_1" data-src="https://st.deepzz.com/acme_aliyun_1.jpg"><img width="480" height="310" alt="acme_aliyun_1" src="https://st.deepzz.com/blog/img/acme_aliyun_1.jpg">`,
}
expectStr := []string{
"",
"https://st.deepzz.com/acme_aliyun_1.jpg",
}
for i, v := range testStr {
assert.Equal(t, expectStr[i], PickFirstImage(v))
}
}
func TestCovertStr(t *testing.T) {
testStr := []string{
time.Now().Format("2006-01-02T15:04:05"),
}
expectStr := []string{
JUST_NOW,
}
for i, v := range testStr {
assert.Equal(t, expectStr[i], ConvertStr(v))
}
}

View File

@@ -121,6 +121,8 @@ type Article struct {
Excerpt string `bson:"-"`
// 一句话描述,文章第一句
Desc string `bson:"-"`
// disqus thread
Thread string `bson:"-"`
}
type SortArticles []*Article

View File

@@ -105,6 +105,7 @@ func init() {
Pings = append(Pings, pr)
}
// ping
func DoPings(slug string) {
for _, p := range Pings {
go p.PingFunc(slug)

View File

@@ -4,20 +4,14 @@ import (
"errors"
"fmt"
"io"
"net/url"
"path/filepath"
"github.com/eiblog/eiblog/setting"
"qiniupkg.com/api.v7/kodo"
"qiniupkg.com/api.v7/kodocli"
url "qiniupkg.com/x/url.v7"
"github.com/qiniu/api.v7/auth/qbox"
"github.com/qiniu/api.v7/storage"
)
var qiniu_cfg = &kodo.Config{
AccessKey: setting.Conf.Kodo.AccessKey,
SecretKey: setting.Conf.Kodo.SecretKey,
Scheme: "https",
}
type bucket struct {
name string
domain string
@@ -30,71 +24,79 @@ type PutRet struct {
Key string `json:"key"`
}
// 进度条
func onProgress(fsize, uploaded int64) {
d := int(float64(uploaded) / float64(fsize) * 100)
if fsize == uploaded {
fmt.Printf("\rUpload completed! ")
fmt.Printf("\rUpload completed! \n")
} else {
fmt.Printf("\r%02d%% uploaded ", int(d))
}
}
// 上传文件
func FileUpload(name string, size int64, data io.Reader) (string, error) {
if setting.Conf.Kodo.AccessKey == "" || setting.Conf.Kodo.SecretKey == "" {
if setting.Conf.Qiniu.AccessKey == "" || setting.Conf.Qiniu.SecretKey == "" {
return "", errors.New("qiniu config error")
}
// 创建一个client
c := kodo.New(0, qiniu_cfg)
// 设置上传的策略
policy := &kodo.PutPolicy{
Scope: setting.Conf.Kodo.Name,
Expires: 3600,
InsertOnly: 1,
}
// 生成一个上传token
token := c.MakeUptoken(policy)
// 构建一个uploader
zone := 0
uploader := kodocli.NewUploader(zone, nil)
key := getKey(name)
if key == "" {
return "", errors.New("不支持的文件类型")
}
var ret PutRet
var extra = kodocli.PutExtra{OnProgress: onProgress}
err := uploader.Put(nil, &ret, token, key, data, size, &extra)
mac := qbox.NewMac(setting.Conf.Qiniu.AccessKey, setting.Conf.Qiniu.SecretKey)
// 设置上传的策略
putPolicy := &storage.PutPolicy{
Scope: setting.Conf.Qiniu.Bucket,
Expires: 3600,
InsertOnly: 1,
}
// 上传token
upToken := putPolicy.UploadToken(mac)
// 上传配置
cfg := &storage.Config{
Zone: &storage.ZoneHuadong,
UseHTTPS: true,
}
// uploader
uploader := storage.NewFormUploader(cfg)
ret := new(storage.PutRet)
putExtra := &storage.PutExtra{OnProgress: onProgress}
err := uploader.Put(nil, ret, upToken, key, data, size, putExtra)
if err != nil {
return "", err
}
url := "https://" + setting.Conf.Kodo.Domain + "/" + url.Escape(key)
url := "https://" + setting.Conf.Qiniu.Domain + "/" + url.QueryEscape(key)
return url, nil
}
// 删除文件
func FileDelete(name string) error {
// new一个Bucket管理对象
c := kodo.New(0, qiniu_cfg)
p := c.Bucket(setting.Conf.Kodo.Name)
key := getKey(name)
if key == "" {
return errors.New("不支持的文件类型")
}
// 调用Delete方法删除文件
err := p.Delete(nil, key)
// 打印返回值以及出错信息
mac := qbox.NewMac(setting.Conf.Qiniu.AccessKey, setting.Conf.Qiniu.SecretKey)
// 上传配置
cfg := &storage.Config{
Zone: &storage.ZoneHuadong,
UseHTTPS: true,
}
// manager
bucketManager := storage.NewBucketManager(mac, cfg)
// Delete
err := bucketManager.Delete(setting.Conf.Qiniu.Bucket, key)
if err != nil {
return err
}
return nil
}
// 修复路径
func getKey(name string) string {
ext := filepath.Ext(name)
var key string

View File

@@ -7,11 +7,13 @@ import (
)
func TestUpload(t *testing.T) {
path := "/Users/chen/Desktop/png-MicroService-by-StuQ.png"
path := "qiniu.go"
file, err := os.Open(path)
if err != nil {
t.Fatal(err)
}
defer file.Close()
info, _ := file.Stat()
url, err := FileUpload(info.Name(), info.Size(), file)
if err != nil {

View File

@@ -9,6 +9,7 @@ import (
"github.com/eiblog/eiblog/setting"
"github.com/eiblog/utils/logd"
"github.com/eiblog/utils/tmpl"
"github.com/gin-gonic/autotls"
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
)
@@ -19,10 +20,12 @@ var (
)
func init() {
// 运行模式
if setting.Conf.RunMode == setting.PROD {
gin.SetMode(gin.ReleaseMode)
logd.SetLevel(logd.Lerror)
}
router = gin.Default()
store := sessions.NewCookieStore([]byte("eiblog321"))
store.Options(sessions.Options{
@@ -62,6 +65,8 @@ func init() {
router.GET("/opensearch.xml", HandleOpenSearch)
router.GET("/sitemap.xml", HandleSitemap)
router.GET("/robots.txt", HandleRobots)
router.GET("/crossdomain.xml", HandleCrossDomain)
router.GET("/favicon.ico", HandleFavicon)
// 后台相关
admin := router.Group("/admin")
admin.GET("/login", HandleLogin)
@@ -87,6 +92,7 @@ func init() {
}
}
// 开始运行
func Run() {
var (
endRunning = make(chan bool, 1)
@@ -94,25 +100,38 @@ func Run() {
)
if setting.Conf.Mode.EnableHttp {
go func() {
logd.Infof("http server Running on %d\n", setting.Conf.Mode.HttpPort)
logd.Printf("http server Running on %d\n", setting.Conf.Mode.HttpPort)
err = router.Run(fmt.Sprintf(":%d", setting.Conf.Mode.HttpPort))
if err != nil {
logd.Info("ListenAndServe: ", err)
logd.Error("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
}()
}
if setting.Conf.Mode.EnableHttps {
go func() {
logd.Infof("https server Running on %d\n", setting.Conf.Mode.HttpsPort)
err = router.RunTLS(fmt.Sprintf(":%d", setting.Conf.Mode.HttpsPort), setting.Conf.Mode.CertFile, setting.Conf.Mode.KeyFile)
if err != nil {
logd.Info("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
}()
if setting.Conf.Mode.AutoCert {
go func() {
logd.Print("https server Running on 443")
err = autotls.Run(router, setting.Conf.Mode.Domain)
if err != nil {
logd.Error("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
}()
} else {
go func() {
logd.Printf("https server Running on %d\n", setting.Conf.Mode.HttpsPort)
err = router.RunTLS(fmt.Sprintf(":%d", setting.Conf.Mode.HttpsPort),
setting.Conf.Mode.CertFile, setting.Conf.Mode.KeyFile)
if err != nil {
logd.Error("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
}()
}
}
<-endRunning
}

View File

@@ -9,8 +9,8 @@ import (
)
const (
DEV = "dev"
PROD = "prod"
DEV = "dev" // 该模式会输出 debug 等信息
PROD = "prod" // 该模式用于生产环境
)
var (
@@ -19,55 +19,68 @@ var (
)
type Config struct {
StaticVersion int // 当前静态文件版本
RunMode string // 运行模式
Trash int // 回收箱文章保留时间
Clean int // 清理回收箱频率
PageNum int // 前端每页文章数量
PageSize int // 后台每页文章数量
Length int // 自动截取预览长度
Identifier string // 截取标示
Description string // 文章描述前缀
Favicon string // icon地址
StartID int32 // 文章起始id
SearchURL string // elasticsearch 地址
Disqus struct { // 获取文章数量相关
ShortName string
PublicKey string
PostsCount string
PostsList string
PostCreate string
Interval int
StaticVersion int // 当前静态文件版本
FeedrURL string // superfeedr url
HotWords []string // 热搜词
PingRPCs []string // ping rpc 地址
General struct {
PageNum int // 前端每页文章数量
PageSize int // 后台每页文章数量
StartID int32 // 文章起始id
DescPrefix string // 文章描述前缀
Identifier string // 文章截取标示
Length int // 文章自动截取预览长度
Trash int // 回收箱文章保留时间
Clean int // 清理回收箱频率
}
HotWords []string // 热搜词
Google struct { // 谷歌统计
Disqus struct { // 获取文章数量相关
ShortName string
PublicKey string
AccessToken string
PostsCount string
PostsList string
PostCreate string
PostApprove string
Embed string
Interval int
}
Google struct { // 谷歌统计
URL string
Tid string
V string
T string
}
Kodo struct { // 七牛CDN
Name string
Qiniu struct { // 七牛CDN
Bucket string
Domain string
AccessKey string
SecretKey string
}
Mode RunMode // 运行模式
Mode struct { // 运行模式
EnableHttp bool
HttpPort int
EnableHttps bool
HttpsPort int
CertFile string
KeyFile string
AutoCert bool
Domain string
}
Twitter struct { // twitter信息
Card string
Site string
Image string
Address string
}
FeedrURL string // superfeedr url
PingRPCs []string // ping rpc 地址
Account struct {
Account struct { // account 账户
Username string // *
Password string // *
Email string
PhoneNumber string
Address string
}
Blogger struct { // 初始化数据
Blogger struct { // blog info 博客信息
BlogName string
SubTitle string
BeiAn string
@@ -76,16 +89,6 @@ type Config struct {
}
}
type RunMode struct {
EnableHttp bool
HttpPort int
EnableHttps bool
HttpsPort int
CertFile string
KeyFile string
Domain string
}
func init() {
// 初始化配置
data, err := ioutil.ReadFile("conf/app.yml")

View File

@@ -2,11 +2,11 @@
package setting
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestInit(t *testing.T) {
init()
fmt.Printf("%v\n", *Conf)
assert.NotNil(t, Conf)
}

View File

@@ -1,6 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*" />
<allow-http-request-headers-from domain="*" headers="*"/>
</cross-domain-policy>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,6 +0,0 @@
User-agent: *
Allow: /
Sitemap: https://deepzz.com/sitemap.xml
User-agent: MJ12bot
Disallow: /

2
vendor/github.com/boj/redistore/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
.DS_Store

19
vendor/github.com/boj/redistore/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2013 Brian Jones
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.

52
vendor/github.com/boj/redistore/README.md generated vendored Normal file
View File

@@ -0,0 +1,52 @@
# redistore
[![Build Status](https://drone.io/github.com/boj/redistore/status.png)](https://drone.io/github.com/boj/redistore/latest)
A session store backend for [gorilla/sessions](http://www.gorillatoolkit.org/pkg/sessions) - [src](https://github.com/gorilla/sessions).
## Requirements
Depends on the [Redigo](https://github.com/garyburd/redigo) Redis library.
## Installation
go get gopkg.in/boj/redistore.v1
## Documentation
Available on [godoc.org](http://www.godoc.org/gopkg.in/boj/redistore.v1).
See http://www.gorillatoolkit.org/pkg/sessions for full documentation on underlying interface.
### Example
``` go
// Fetch new store.
store, err := NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
if err != nil {
panic(err)
}
defer store.Close()
// Get a session.
session, err = store.Get(req, "session-key")
if err != nil {
log.Error(err.Error())
}
// Add a value.
session.Values["foo"] = "bar"
// Save.
if err = sessions.Save(req, rsp); err != nil {
t.Fatalf("Error saving session: %v", err)
}
// Delete session.
session.Options.MaxAge = -1
if err = sessions.Save(req, rsp); err != nil {
t.Fatalf("Error saving session: %v", err)
}
// Change session storage configuration for MaxAge = 10 days.
store.SetMaxAge(10 * 24 * 3600)
```

4
vendor/github.com/boj/redistore/doc.go generated vendored Normal file
View File

@@ -0,0 +1,4 @@
/*
Package redistore is a session store backend for gorilla/sessions
*/
package redistore

361
vendor/github.com/boj/redistore/redistore.go generated vendored Normal file
View File

@@ -0,0 +1,361 @@
// Copyright 2012 Brian "bojo" Jones. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package redistore
import (
"bytes"
"encoding/base32"
"encoding/gob"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/garyburd/redigo/redis"
"github.com/gorilla/securecookie"
"github.com/gorilla/sessions"
)
// Amount of time for cookies/redis keys to expire.
var sessionExpire = 86400 * 30
// SessionSerializer provides an interface hook for alternative serializers
type SessionSerializer interface {
Deserialize(d []byte, ss *sessions.Session) error
Serialize(ss *sessions.Session) ([]byte, error)
}
// JSONSerializer encode the session map to JSON.
type JSONSerializer struct{}
// Serialize to JSON. Will err if there are unmarshalable key values
func (s JSONSerializer) Serialize(ss *sessions.Session) ([]byte, error) {
m := make(map[string]interface{}, len(ss.Values))
for k, v := range ss.Values {
ks, ok := k.(string)
if !ok {
err := fmt.Errorf("Non-string key value, cannot serialize session to JSON: %v", k)
fmt.Printf("redistore.JSONSerializer.serialize() Error: %v", err)
return nil, err
}
m[ks] = v
}
return json.Marshal(m)
}
// Deserialize back to map[string]interface{}
func (s JSONSerializer) Deserialize(d []byte, ss *sessions.Session) error {
m := make(map[string]interface{})
err := json.Unmarshal(d, &m)
if err != nil {
fmt.Printf("redistore.JSONSerializer.deserialize() Error: %v", err)
return err
}
for k, v := range m {
ss.Values[k] = v
}
return nil
}
// GobSerializer uses gob package to encode the session map
type GobSerializer struct{}
// Serialize using gob
func (s GobSerializer) Serialize(ss *sessions.Session) ([]byte, error) {
buf := new(bytes.Buffer)
enc := gob.NewEncoder(buf)
err := enc.Encode(ss.Values)
if err == nil {
return buf.Bytes(), nil
}
return nil, err
}
// Deserialize back to map[interface{}]interface{}
func (s GobSerializer) Deserialize(d []byte, ss *sessions.Session) error {
dec := gob.NewDecoder(bytes.NewBuffer(d))
return dec.Decode(&ss.Values)
}
// RediStore stores sessions in a redis backend.
type RediStore struct {
Pool *redis.Pool
Codecs []securecookie.Codec
Options *sessions.Options // default configuration
DefaultMaxAge int // default Redis TTL for a MaxAge == 0 session
maxLength int
keyPrefix string
serializer SessionSerializer
}
// SetMaxLength sets RediStore.maxLength if the `l` argument is greater or equal 0
// maxLength restricts the maximum length of new sessions to l.
// If l is 0 there is no limit to the size of a session, use with caution.
// The default for a new RediStore is 4096. Redis allows for max.
// value sizes of up to 512MB (http://redis.io/topics/data-types)
// Default: 4096,
func (s *RediStore) SetMaxLength(l int) {
if l >= 0 {
s.maxLength = l
}
}
// SetKeyPrefix set the prefix
func (s *RediStore) SetKeyPrefix(p string) {
s.keyPrefix = p
}
// SetSerializer sets the serializer
func (s *RediStore) SetSerializer(ss SessionSerializer) {
s.serializer = ss
}
// SetMaxAge restricts the maximum age, in seconds, of the session record
// both in database and a browser. This is to change session storage configuration.
// If you want just to remove session use your session `s` object and change it's
// `Options.MaxAge` to -1, as specified in
// http://godoc.org/github.com/gorilla/sessions#Options
//
// Default is the one provided by this package value - `sessionExpire`.
// Set it to 0 for no restriction.
// Because we use `MaxAge` also in SecureCookie crypting algorithm you should
// use this function to change `MaxAge` value.
func (s *RediStore) SetMaxAge(v int) {
var c *securecookie.SecureCookie
var ok bool
s.Options.MaxAge = v
for i := range s.Codecs {
if c, ok = s.Codecs[i].(*securecookie.SecureCookie); ok {
c.MaxAge(v)
} else {
fmt.Printf("Can't change MaxAge on codec %v\n", s.Codecs[i])
}
}
}
func dial(network, address, password string) (redis.Conn, error) {
c, err := redis.Dial(network, address)
if err != nil {
return nil, err
}
if password != "" {
if _, err := c.Do("AUTH", password); err != nil {
c.Close()
return nil, err
}
}
return c, err
}
// NewRediStore returns a new RediStore.
// size: maximum number of idle connections.
func NewRediStore(size int, network, address, password string, keyPairs ...[]byte) (*RediStore, error) {
return NewRediStoreWithPool(&redis.Pool{
MaxIdle: size,
IdleTimeout: 240 * time.Second,
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
return err
},
Dial: func() (redis.Conn, error) {
return dial(network, address, password)
},
}, keyPairs...)
}
func dialWithDB(network, address, password, DB string) (redis.Conn, error) {
c, err := dial(network, address, password)
if err != nil {
return nil, err
}
if _, err := c.Do("SELECT", DB); err != nil {
c.Close()
return nil, err
}
return c, err
}
// NewRediStoreWithDB - like NewRedisStore but accepts `DB` parameter to select
// redis DB instead of using the default one ("0")
func NewRediStoreWithDB(size int, network, address, password, DB string, keyPairs ...[]byte) (*RediStore, error) {
return NewRediStoreWithPool(&redis.Pool{
MaxIdle: size,
IdleTimeout: 240 * time.Second,
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
return err
},
Dial: func() (redis.Conn, error) {
return dialWithDB(network, address, password, DB)
},
}, keyPairs...)
}
// NewRediStoreWithPool instantiates a RediStore with a *redis.Pool passed in.
func NewRediStoreWithPool(pool *redis.Pool, keyPairs ...[]byte) (*RediStore, error) {
rs := &RediStore{
// http://godoc.org/github.com/garyburd/redigo/redis#Pool
Pool: pool,
Codecs: securecookie.CodecsFromPairs(keyPairs...),
Options: &sessions.Options{
Path: "/",
MaxAge: sessionExpire,
},
DefaultMaxAge: 60 * 20, // 20 minutes seems like a reasonable default
maxLength: 4096,
keyPrefix: "session_",
serializer: GobSerializer{},
}
_, err := rs.ping()
return rs, err
}
// Close closes the underlying *redis.Pool
func (s *RediStore) Close() error {
return s.Pool.Close()
}
// Get returns a session for the given name after adding it to the registry.
//
// See gorilla/sessions FilesystemStore.Get().
func (s *RediStore) Get(r *http.Request, name string) (*sessions.Session, error) {
return sessions.GetRegistry(r).Get(s, name)
}
// New returns a session for the given name without adding it to the registry.
//
// See gorilla/sessions FilesystemStore.New().
func (s *RediStore) New(r *http.Request, name string) (*sessions.Session, error) {
var (
err error
ok bool
)
session := sessions.NewSession(s, name)
// make a copy
options := *s.Options
session.Options = &options
session.IsNew = true
if c, errCookie := r.Cookie(name); errCookie == nil {
err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
if err == nil {
ok, err = s.load(session)
session.IsNew = !(err == nil && ok) // not new if no error and data available
}
}
return session, err
}
// Save adds a single session to the response.
func (s *RediStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
// Marked for deletion.
if session.Options.MaxAge < 0 {
if err := s.delete(session); err != nil {
return err
}
http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
} else {
// Build an alphanumeric key for the redis store.
if session.ID == "" {
session.ID = strings.TrimRight(base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)), "=")
}
if err := s.save(session); err != nil {
return err
}
encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, s.Codecs...)
if err != nil {
return err
}
http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options))
}
return nil
}
// Delete removes the session from redis, and sets the cookie to expire.
//
// WARNING: This method should be considered deprecated since it is not exposed via the gorilla/sessions interface.
// Set session.Options.MaxAge = -1 and call Save instead. - July 18th, 2013
func (s *RediStore) Delete(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
conn := s.Pool.Get()
defer conn.Close()
if _, err := conn.Do("DEL", s.keyPrefix+session.ID); err != nil {
return err
}
// Set cookie to expire.
options := *session.Options
options.MaxAge = -1
http.SetCookie(w, sessions.NewCookie(session.Name(), "", &options))
// Clear session values.
for k := range session.Values {
delete(session.Values, k)
}
return nil
}
// ping does an internal ping against a server to check if it is alive.
func (s *RediStore) ping() (bool, error) {
conn := s.Pool.Get()
defer conn.Close()
data, err := conn.Do("PING")
if err != nil || data == nil {
return false, err
}
return (data == "PONG"), nil
}
// save stores the session in redis.
func (s *RediStore) save(session *sessions.Session) error {
b, err := s.serializer.Serialize(session)
if err != nil {
return err
}
if s.maxLength != 0 && len(b) > s.maxLength {
return errors.New("SessionStore: the value to store is too big")
}
conn := s.Pool.Get()
defer conn.Close()
if err = conn.Err(); err != nil {
return err
}
age := session.Options.MaxAge
if age == 0 {
age = s.DefaultMaxAge
}
_, err = conn.Do("SETEX", s.keyPrefix+session.ID, age, b)
return err
}
// load reads the session from redis.
// returns true if there is a sessoin data in DB
func (s *RediStore) load(session *sessions.Session) (bool, error) {
conn := s.Pool.Get()
defer conn.Close()
if err := conn.Err(); err != nil {
return false, err
}
data, err := conn.Do("GET", s.keyPrefix+session.ID)
if err != nil {
return false, err
}
if data == nil {
return false, nil // no data was associated with this key
}
b, err := redis.Bytes(data, err)
if err != nil {
return false, err
}
return true, s.serializer.Deserialize(b, session)
}
// delete removes keys from redis if MaxAge<0
func (s *RediStore) delete(session *sessions.Session) error {
conn := s.Pool.Get()
defer conn.Close()
if _, err := conn.Do("DEL", s.keyPrefix+session.ID); err != nil {
return err
}
return nil
}

404
vendor/github.com/boj/redistore/redistore_test.go generated vendored Normal file
View File

@@ -0,0 +1,404 @@
package redistore
import (
"bytes"
"encoding/base64"
"encoding/gob"
"net/http"
"net/http/httptest"
"testing"
"github.com/gorilla/sessions"
)
// ----------------------------------------------------------------------------
// ResponseRecorder
// ----------------------------------------------------------------------------
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// ResponseRecorder is an implementation of http.ResponseWriter that
// records its mutations for later inspection in tests.
type ResponseRecorder struct {
Code int // the HTTP response code from WriteHeader
HeaderMap http.Header // the HTTP response headers
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
Flushed bool
}
// NewRecorder returns an initialized ResponseRecorder.
func NewRecorder() *ResponseRecorder {
return &ResponseRecorder{
HeaderMap: make(http.Header),
Body: new(bytes.Buffer),
}
}
// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
const DefaultRemoteAddr = "1.2.3.4"
// Header returns the response headers.
func (rw *ResponseRecorder) Header() http.Header {
return rw.HeaderMap
}
// Write always succeeds and writes to rw.Body, if not nil.
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
if rw.Body != nil {
rw.Body.Write(buf)
}
if rw.Code == 0 {
rw.Code = http.StatusOK
}
return len(buf), nil
}
// WriteHeader sets rw.Code.
func (rw *ResponseRecorder) WriteHeader(code int) {
rw.Code = code
}
// Flush sets rw.Flushed to true.
func (rw *ResponseRecorder) Flush() {
rw.Flushed = true
}
// ----------------------------------------------------------------------------
type FlashMessage struct {
Type int
Message string
}
func TestRediStore(t *testing.T) {
var req *http.Request
var rsp *ResponseRecorder
var hdr http.Header
var err error
var ok bool
var cookies []string
var session *sessions.Session
var flashes []interface{}
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Round 1 ----------------------------------------------------------------
// RedisStore
store, err := NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
if err != nil {
t.Fatal(err.Error())
}
defer store.Close()
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
rsp = NewRecorder()
// Get a session.
if session, err = store.Get(req, "session-key"); err != nil {
t.Fatalf("Error getting session: %v", err)
}
// Get a flash.
flashes = session.Flashes()
if len(flashes) != 0 {
t.Errorf("Expected empty flashes; Got %v", flashes)
}
// Add some flashes.
session.AddFlash("foo")
session.AddFlash("bar")
// Custom key.
session.AddFlash("baz", "custom_key")
// Save.
if err = sessions.Save(req, rsp); err != nil {
t.Fatalf("Error saving session: %v", err)
}
hdr = rsp.Header()
cookies, ok = hdr["Set-Cookie"]
if !ok || len(cookies) != 1 {
t.Fatalf("No cookies. Header:", hdr)
}
// Round 2 ----------------------------------------------------------------
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
req.Header.Add("Cookie", cookies[0])
rsp = NewRecorder()
// Get a session.
if session, err = store.Get(req, "session-key"); err != nil {
t.Fatalf("Error getting session: %v", err)
}
// Check all saved values.
flashes = session.Flashes()
if len(flashes) != 2 {
t.Fatalf("Expected flashes; Got %v", flashes)
}
if flashes[0] != "foo" || flashes[1] != "bar" {
t.Errorf("Expected foo,bar; Got %v", flashes)
}
flashes = session.Flashes()
if len(flashes) != 0 {
t.Errorf("Expected dumped flashes; Got %v", flashes)
}
// Custom key.
flashes = session.Flashes("custom_key")
if len(flashes) != 1 {
t.Errorf("Expected flashes; Got %v", flashes)
} else if flashes[0] != "baz" {
t.Errorf("Expected baz; Got %v", flashes)
}
flashes = session.Flashes("custom_key")
if len(flashes) != 0 {
t.Errorf("Expected dumped flashes; Got %v", flashes)
}
// RediStore specific
// Set MaxAge to -1 to mark for deletion.
session.Options.MaxAge = -1
// Save.
if err = sessions.Save(req, rsp); err != nil {
t.Fatalf("Error saving session: %v", err)
}
// Round 3 ----------------------------------------------------------------
// Custom type
// RedisStore
store, err = NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
if err != nil {
t.Fatal(err.Error())
}
defer store.Close()
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
rsp = NewRecorder()
// Get a session.
if session, err = store.Get(req, "session-key"); err != nil {
t.Fatalf("Error getting session: %v", err)
}
// Get a flash.
flashes = session.Flashes()
if len(flashes) != 0 {
t.Errorf("Expected empty flashes; Got %v", flashes)
}
// Add some flashes.
session.AddFlash(&FlashMessage{42, "foo"})
// Save.
if err = sessions.Save(req, rsp); err != nil {
t.Fatalf("Error saving session: %v", err)
}
hdr = rsp.Header()
cookies, ok = hdr["Set-Cookie"]
if !ok || len(cookies) != 1 {
t.Fatalf("No cookies. Header:", hdr)
}
// Round 4 ----------------------------------------------------------------
// Custom type
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
req.Header.Add("Cookie", cookies[0])
rsp = NewRecorder()
// Get a session.
if session, err = store.Get(req, "session-key"); err != nil {
t.Fatalf("Error getting session: %v", err)
}
// Check all saved values.
flashes = session.Flashes()
if len(flashes) != 1 {
t.Fatalf("Expected flashes; Got %v", flashes)
}
custom := flashes[0].(FlashMessage)
if custom.Type != 42 || custom.Message != "foo" {
t.Errorf("Expected %#v, got %#v", FlashMessage{42, "foo"}, custom)
}
// RediStore specific
// Set MaxAge to -1 to mark for deletion.
session.Options.MaxAge = -1
// Save.
if err = sessions.Save(req, rsp); err != nil {
t.Fatalf("Error saving session: %v", err)
}
// Round 5 ----------------------------------------------------------------
// RediStore Delete session (deprecated)
//req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
//req.Header.Add("Cookie", cookies[0])
//rsp = NewRecorder()
//// Get a session.
//if session, err = store.Get(req, "session-key"); err != nil {
// t.Fatalf("Error getting session: %v", err)
//}
//// Delete session.
//if err = store.Delete(req, rsp, session); err != nil {
// t.Fatalf("Error deleting session: %v", err)
//}
//// Get a flash.
//flashes = session.Flashes()
//if len(flashes) != 0 {
// t.Errorf("Expected empty flashes; Got %v", flashes)
//}
//hdr = rsp.Header()
//cookies, ok = hdr["Set-Cookie"]
//if !ok || len(cookies) != 1 {
// t.Fatalf("No cookies. Header:", hdr)
//}
// Round 6 ----------------------------------------------------------------
// RediStore change MaxLength of session
store, err = NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
if err != nil {
t.Fatal(err.Error())
}
req, err = http.NewRequest("GET", "http://www.example.com", nil)
if err != nil {
t.Fatal("failed to create request", err)
}
w := httptest.NewRecorder()
session, err = store.New(req, "my session")
session.Values["big"] = make([]byte, base64.StdEncoding.DecodedLen(4096*2))
err = session.Save(req, w)
if err == nil {
t.Fatal("expected an error, got nil")
}
store.SetMaxLength(4096 * 3) // A bit more than the value size to account for encoding overhead.
err = session.Save(req, w)
if err != nil {
t.Fatal("failed to Save:", err)
}
// Round 7 ----------------------------------------------------------------
// RedisStoreWithDB
store, err = NewRediStoreWithDB(10, "tcp", ":6379", "", "1", []byte("secret-key"))
if err != nil {
t.Fatal(err.Error())
}
defer store.Close()
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
rsp = NewRecorder()
// Get a session. Using the same key as previously, but on different DB
if session, err = store.Get(req, "session-key"); err != nil {
t.Fatalf("Error getting session: %v", err)
}
// Get a flash.
flashes = session.Flashes()
if len(flashes) != 0 {
t.Errorf("Expected empty flashes; Got %v", flashes)
}
// Add some flashes.
session.AddFlash("foo")
// Save.
if err = sessions.Save(req, rsp); err != nil {
t.Fatalf("Error saving session: %v", err)
}
hdr = rsp.Header()
cookies, ok = hdr["Set-Cookie"]
if !ok || len(cookies) != 1 {
t.Fatalf("No cookies. Header:", hdr)
}
// Get a session.
req.Header.Add("Cookie", cookies[0])
if session, err = store.Get(req, "session-key"); err != nil {
t.Fatalf("Error getting session: %v", err)
}
// Check all saved values.
flashes = session.Flashes()
if len(flashes) != 1 {
t.Fatalf("Expected flashes; Got %v", flashes)
}
if flashes[0] != "foo" {
t.Errorf("Expected foo,bar; Got %v", flashes)
}
// Round 8 ----------------------------------------------------------------
// JSONSerializer
// RedisStore
store, err = NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
store.SetSerializer(JSONSerializer{})
if err != nil {
t.Fatal(err.Error())
}
defer store.Close()
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
rsp = NewRecorder()
// Get a session.
if session, err = store.Get(req, "session-key"); err != nil {
t.Fatalf("Error getting session: %v", err)
}
// Get a flash.
flashes = session.Flashes()
if len(flashes) != 0 {
t.Errorf("Expected empty flashes; Got %v", flashes)
}
// Add some flashes.
session.AddFlash("foo")
// Save.
if err = sessions.Save(req, rsp); err != nil {
t.Fatalf("Error saving session: %v", err)
}
hdr = rsp.Header()
cookies, ok = hdr["Set-Cookie"]
if !ok || len(cookies) != 1 {
t.Fatalf("No cookies. Header:", hdr)
}
// Get a session.
req.Header.Add("Cookie", cookies[0])
if session, err = store.Get(req, "session-key"); err != nil {
t.Fatalf("Error getting session: %v", err)
}
// Check all saved values.
flashes = session.Flashes()
if len(flashes) != 1 {
t.Fatalf("Expected flashes; Got %v", flashes)
}
if flashes[0] != "foo" {
t.Errorf("Expected foo,bar; Got %v", flashes)
}
}
func TestPingGoodPort(t *testing.T) {
store, _ := NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
defer store.Close()
ok, err := store.ping()
if err != nil {
t.Error(err.Error())
}
if !ok {
t.Error("Expected server to PONG")
}
}
func TestPingBadPort(t *testing.T) {
store, _ := NewRediStore(10, "tcp", ":6378", "", []byte("secret-key"))
defer store.Close()
_, err := store.ping()
if err == nil {
t.Error("Expected error")
}
}
func ExampleRediStore() {
// RedisStore
store, err := NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
if err != nil {
panic(err)
}
defer store.Close()
}
func init() {
gob.Register(FlashMessage{})
}

22
vendor/github.com/davecgh/go-spew/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,22 @@
# 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

11
vendor/github.com/davecgh/go-spew/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,11 @@
language: go
go: 1.2
install:
- go get -v code.google.com/p/go.tools/cmd/cover
script:
- go test -v -tags=disableunsafe ./spew
- go test -v -tags=testcgo ./spew -covermode=count -coverprofile=profile.cov
after_success:
- go get -v github.com/mattn/goveralls
- export PATH=$PATH:$HOME/gopath/bin
- goveralls -coverprofile=profile.cov -service=travis-ci

13
vendor/github.com/davecgh/go-spew/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,13 @@
Copyright (c) 2012-2013 Dave Collins <dave@davec.name>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

194
vendor/github.com/davecgh/go-spew/README.md generated vendored Normal file
View File

@@ -0,0 +1,194 @@
go-spew
=======
[![Build Status](https://travis-ci.org/davecgh/go-spew.png?branch=master)]
(https://travis-ci.org/davecgh/go-spew) [![Coverage Status]
(https://coveralls.io/repos/davecgh/go-spew/badge.png?branch=master)]
(https://coveralls.io/r/davecgh/go-spew?branch=master)
Go-spew implements a deep pretty printer for Go data structures to aid in
debugging. A comprehensive suite of tests with 100% test coverage is provided
to ensure proper functionality. See `test_coverage.txt` for the gocov coverage
report. Go-spew is licensed under the liberal ISC license, so it may be used in
open source or commercial projects.
If you're interested in reading about how this package came to life and some
of the challenges involved in providing a deep pretty printer, there is a blog
post about it
[here](https://blog.cyphertite.com/go-spew-a-journey-into-dumping-go-data-structures/).
## Documentation
[![GoDoc](https://godoc.org/github.com/davecgh/go-spew/spew?status.png)]
(http://godoc.org/github.com/davecgh/go-spew/spew)
Full `go doc` style documentation for the project can be viewed online without
installing this package by using the excellent GoDoc site here:
http://godoc.org/github.com/davecgh/go-spew/spew
You can also view the documentation locally once the package is installed with
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
http://localhost:6060/pkg/github.com/davecgh/go-spew/spew
## Installation
```bash
$ go get -u github.com/davecgh/go-spew/spew
```
## Quick Start
Add this import line to the file you're working in:
```Go
import "github.com/davecgh/go-spew/spew"
```
To dump a variable with full newlines, indentation, type, and pointer
information use Dump, Fdump, or Sdump:
```Go
spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)
```
Alternatively, if you would prefer to use format strings with a compacted inline
printing style, use the convenience wrappers Printf, Fprintf, etc with %v (most
compact), %+v (adds pointer addresses), %#v (adds types), or %#+v (adds types
and pointer addresses):
```Go
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
```
## Debugging a Web Application Example
Here is an example of how you can use `spew.Sdump()` to help debug a web application. Please be sure to wrap your output using the `html.EscapeString()` function for safety reasons. You should also only use this debugging technique in a development environment, never in production.
```Go
package main
import (
"fmt"
"html"
"net/http"
"github.com/davecgh/go-spew/spew"
)
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
fmt.Fprintf(w, "Hi there, %s!", r.URL.Path[1:])
fmt.Fprintf(w, "<!--\n" + html.EscapeString(spew.Sdump(w)) + "\n-->")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
```
## Sample Dump Output
```
(main.Foo) {
unexportedField: (*main.Bar)(0xf84002e210)({
flag: (main.Flag) flagTwo,
data: (uintptr) <nil>
}),
ExportedField: (map[interface {}]interface {}) {
(string) "one": (bool) true
}
}
([]uint8) {
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000020 31 32 |12|
}
```
## Sample Formatter Output
Double pointer to a uint8:
```
%v: <**>5
%+v: <**>(0xf8400420d0->0xf8400420c8)5
%#v: (**uint8)5
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
```
Pointer to circular struct with a uint8 field and a pointer to itself:
```
%v: <*>{1 <*><shown>}
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
```
## Configuration Options
Configuration of spew is handled by fields in the ConfigState type. For
convenience, all of the top-level functions use a global state available via the
spew.Config global.
It is also possible to create a ConfigState instance that provides methods
equivalent to the top-level functions. This allows concurrent configuration
options. See the ConfigState documentation for more details.
```
* Indent
String to use for each indentation level for Dump functions.
It is a single space by default. A popular alternative is "\t".
* MaxDepth
Maximum number of levels to descend into nested data structures.
There is no limit by default.
* DisableMethods
Disables invocation of error and Stringer interface methods.
Method invocation is enabled by default.
* DisablePointerMethods
Disables invocation of error and Stringer interface methods on types
which only accept pointer receivers from non-pointer variables. This option
relies on access to the unsafe package, so it will not have any effect when
running in environments without access to the unsafe package such as Google
App Engine or with the "disableunsafe" build tag specified.
Pointer method invocation is enabled by default.
* ContinueOnMethod
Enables recursion into types after invoking error and Stringer interface
methods. Recursion after method invocation is disabled by default.
* SortKeys
Specifies map keys should be sorted before being printed. Use
this to have a more deterministic, diffable output. Note that
only native types (bool, int, uint, floats, uintptr and string)
and types which implement error or Stringer interfaces are supported,
with other types sorted according to the reflect.Value.String() output
which guarantees display stability. Natural map order is used by
default.
* SpewKeys
SpewKeys specifies that, as a last resort attempt, map keys should be
spewed to strings and sorted by those strings. This is only considered
if SortKeys is true.
```
## Unsafe Package Dependency
This package relies on the unsafe package to perform some of the more advanced
features, however it also supports a "limited" mode which allows it to work in
environments where the unsafe package is not available. By default, it will
operate in this mode on Google App Engine. The "disableunsafe" build tag may
also be specified to force the package to build without using the unsafe
package.
## License
Go-spew is licensed under the liberal ISC License.

22
vendor/github.com/davecgh/go-spew/cov_report.sh generated vendored Normal file
View File

@@ -0,0 +1,22 @@
#!/bin/sh
# This script uses gocov to generate a test coverage report.
# The gocov tool my be obtained with the following command:
# go get github.com/axw/gocov/gocov
#
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
# Check for gocov.
if ! type gocov >/dev/null 2>&1; then
echo >&2 "This script requires the gocov tool."
echo >&2 "You may obtain it with the following command:"
echo >&2 "go get github.com/axw/gocov/gocov"
exit 1
fi
# Only run the cgo tests if gcc is installed.
if type gcc >/dev/null 2>&1; then
(cd spew && gocov test -tags testcgo | gocov report)
else
(cd spew && gocov test | gocov report)
fi

151
vendor/github.com/davecgh/go-spew/spew/bypass.go generated vendored Normal file
View File

@@ -0,0 +1,151 @@
// Copyright (c) 2015 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is not running on Google App Engine and "-tags disableunsafe"
// is not added to the go build command line.
// +build !appengine,!disableunsafe
package spew
import (
"reflect"
"unsafe"
)
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = false
// ptrSize is the size of a pointer on the current arch.
ptrSize = unsafe.Sizeof((*byte)(nil))
)
var (
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
// internal reflect.Value fields. These values are valid before golang
// commit ecccf07e7f9d which changed the format. The are also valid
// after commit 82f48826c6c7 which changed the format again to mirror
// the original format. Code in the init function updates these offsets
// as necessary.
offsetPtr = uintptr(ptrSize)
offsetScalar = uintptr(0)
offsetFlag = uintptr(ptrSize * 2)
// flagKindWidth and flagKindShift indicate various bits that the
// reflect package uses internally to track kind information.
//
// flagRO indicates whether or not the value field of a reflect.Value is
// read-only.
//
// flagIndir indicates whether the value field of a reflect.Value is
// the actual data or a pointer to the data.
//
// These values are valid before golang commit 90a7c3c86944 which
// changed their positions. Code in the init function updates these
// flags as necessary.
flagKindWidth = uintptr(5)
flagKindShift = uintptr(flagKindWidth - 1)
flagRO = uintptr(1 << 0)
flagIndir = uintptr(1 << 1)
)
func init() {
// Older versions of reflect.Value stored small integers directly in the
// ptr field (which is named val in the older versions). Versions
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
// scalar for this purpose which unfortunately came before the flag
// field, so the offset of the flag field is different for those
// versions.
//
// This code constructs a new reflect.Value from a known small integer
// and checks if the size of the reflect.Value struct indicates it has
// the scalar field. When it does, the offsets are updated accordingly.
vv := reflect.ValueOf(0xf00)
if unsafe.Sizeof(vv) == (ptrSize * 4) {
offsetScalar = ptrSize * 2
offsetFlag = ptrSize * 3
}
// Commit 90a7c3c86944 changed the flag positions such that the low
// order bits are the kind. This code extracts the kind from the flags
// field and ensures it's the correct type. When it's not, the flag
// order has been changed to the newer format, so the flags are updated
// accordingly.
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
upfv := *(*uintptr)(upf)
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
flagKindShift = 0
flagRO = 1 << 5
flagIndir = 1 << 6
// Commit adf9b30e5594 modified the flags to separate the
// flagRO flag into two bits which specifies whether or not the
// field is embedded. This causes flagIndir to move over a bit
// and means that flagRO is the combination of either of the
// original flagRO bit and the new bit.
//
// This code detects the change by extracting what used to be
// the indirect bit to ensure it's set. When it's not, the flag
// order has been changed to the newer format, so the flags are
// updated accordingly.
if upfv&flagIndir == 0 {
flagRO = 3 << 5
flagIndir = 1 << 7
}
}
}
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
// the typical safety restrictions preventing access to unaddressable and
// unexported data. It works by digging the raw pointer to the underlying
// value out of the protected value and generating a new unprotected (unsafe)
// reflect.Value to it.
//
// This allows us to check for implementations of the Stringer and error
// interfaces to be used for pretty printing ordinarily unaddressable and
// inaccessible values such as unexported struct fields.
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
indirects := 1
vt := v.Type()
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
if rvf&flagIndir != 0 {
vt = reflect.PtrTo(v.Type())
indirects++
} else if offsetScalar != 0 {
// The value is in the scalar field when it's not one of the
// reference types.
switch vt.Kind() {
case reflect.Uintptr:
case reflect.Chan:
case reflect.Func:
case reflect.Map:
case reflect.Ptr:
case reflect.UnsafePointer:
default:
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
offsetScalar)
}
}
pv := reflect.NewAt(vt, upv)
rv = pv
for i := 0; i < indirects; i++ {
rv = rv.Elem()
}
return rv
}

37
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go generated vendored Normal file
View File

@@ -0,0 +1,37 @@
// Copyright (c) 2015 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when either the code is running on Google App Engine or "-tags disableunsafe"
// is added to the go build command line.
// +build appengine disableunsafe
package spew
import "reflect"
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = true
)
// unsafeReflectValue typically converts the passed reflect.Value into a one
// that bypasses the typical safety restrictions preventing access to
// unaddressable and unexported data. However, doing this relies on access to
// the unsafe package. This is a stub version which simply returns the passed
// reflect.Value when the unsafe package is not available.
func unsafeReflectValue(v reflect.Value) reflect.Value {
return v
}

341
vendor/github.com/davecgh/go-spew/spew/common.go generated vendored Normal file
View File

@@ -0,0 +1,341 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"reflect"
"sort"
"strconv"
)
// Some constants in the form of bytes to avoid string overhead. This mirrors
// the technique used in the fmt package.
var (
panicBytes = []byte("(PANIC=")
plusBytes = []byte("+")
iBytes = []byte("i")
trueBytes = []byte("true")
falseBytes = []byte("false")
interfaceBytes = []byte("(interface {})")
commaNewlineBytes = []byte(",\n")
newlineBytes = []byte("\n")
openBraceBytes = []byte("{")
openBraceNewlineBytes = []byte("{\n")
closeBraceBytes = []byte("}")
asteriskBytes = []byte("*")
colonBytes = []byte(":")
colonSpaceBytes = []byte(": ")
openParenBytes = []byte("(")
closeParenBytes = []byte(")")
spaceBytes = []byte(" ")
pointerChainBytes = []byte("->")
nilAngleBytes = []byte("<nil>")
maxNewlineBytes = []byte("<max depth reached>\n")
maxShortBytes = []byte("<max>")
circularBytes = []byte("<already shown>")
circularShortBytes = []byte("<shown>")
invalidAngleBytes = []byte("<invalid>")
openBracketBytes = []byte("[")
closeBracketBytes = []byte("]")
percentBytes = []byte("%")
precisionBytes = []byte(".")
openAngleBytes = []byte("<")
closeAngleBytes = []byte(">")
openMapBytes = []byte("map[")
closeMapBytes = []byte("]")
lenEqualsBytes = []byte("len=")
capEqualsBytes = []byte("cap=")
)
// hexDigits is used to map a decimal value to a hex digit.
var hexDigits = "0123456789abcdef"
// catchPanic handles any panics that might occur during the handleMethods
// calls.
func catchPanic(w io.Writer, v reflect.Value) {
if err := recover(); err != nil {
w.Write(panicBytes)
fmt.Fprintf(w, "%v", err)
w.Write(closeParenBytes)
}
}
// handleMethods attempts to call the Error and String methods on the underlying
// type the passed reflect.Value represents and outputes the result to Writer w.
//
// It handles panics in any called methods by catching and displaying the error
// as the formatted value.
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
// We need an interface to check if the type implements the error or
// Stringer interface. However, the reflect package won't give us an
// interface on certain things like unexported struct fields in order
// to enforce visibility rules. We use unsafe, when it's available,
// to bypass these restrictions since this package does not mutate the
// values.
if !v.CanInterface() {
if UnsafeDisabled {
return false
}
v = unsafeReflectValue(v)
}
// Choose whether or not to do error and Stringer interface lookups against
// the base type or a pointer to the base type depending on settings.
// Technically calling one of these methods with a pointer receiver can
// mutate the value, however, types which choose to satisify an error or
// Stringer interface with a pointer receiver should not be mutating their
// state inside these interface methods.
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
v = unsafeReflectValue(v)
}
if v.CanAddr() {
v = v.Addr()
}
// Is it an error or Stringer?
switch iface := v.Interface().(type) {
case error:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.Error()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.Error()))
return true
case fmt.Stringer:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.String()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.String()))
return true
}
return false
}
// printBool outputs a boolean value as true or false to Writer w.
func printBool(w io.Writer, val bool) {
if val {
w.Write(trueBytes)
} else {
w.Write(falseBytes)
}
}
// printInt outputs a signed integer value to Writer w.
func printInt(w io.Writer, val int64, base int) {
w.Write([]byte(strconv.FormatInt(val, base)))
}
// printUint outputs an unsigned integer value to Writer w.
func printUint(w io.Writer, val uint64, base int) {
w.Write([]byte(strconv.FormatUint(val, base)))
}
// printFloat outputs a floating point value using the specified precision,
// which is expected to be 32 or 64bit, to Writer w.
func printFloat(w io.Writer, val float64, precision int) {
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
}
// printComplex outputs a complex value using the specified float precision
// for the real and imaginary parts to Writer w.
func printComplex(w io.Writer, c complex128, floatPrecision int) {
r := real(c)
w.Write(openParenBytes)
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
i := imag(c)
if i >= 0 {
w.Write(plusBytes)
}
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
w.Write(iBytes)
w.Write(closeParenBytes)
}
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
// prefix to Writer w.
func printHexPtr(w io.Writer, p uintptr) {
// Null pointer.
num := uint64(p)
if num == 0 {
w.Write(nilAngleBytes)
return
}
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
buf := make([]byte, 18)
// It's simpler to construct the hex string right to left.
base := uint64(16)
i := len(buf) - 1
for num >= base {
buf[i] = hexDigits[num%base]
num /= base
i--
}
buf[i] = hexDigits[num]
// Add '0x' prefix.
i--
buf[i] = 'x'
i--
buf[i] = '0'
// Strip unused leading bytes.
buf = buf[i:]
w.Write(buf)
}
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
// elements to be sorted.
type valuesSorter struct {
values []reflect.Value
strings []string // either nil or same len and values
cs *ConfigState
}
// newValuesSorter initializes a valuesSorter instance, which holds a set of
// surrogate keys on which the data should be sorted. It uses flags in
// ConfigState to decide if and how to populate those surrogate keys.
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
vs := &valuesSorter{values: values, cs: cs}
if canSortSimply(vs.values[0].Kind()) {
return vs
}
if !cs.DisableMethods {
vs.strings = make([]string, len(values))
for i := range vs.values {
b := bytes.Buffer{}
if !handleMethods(cs, &b, vs.values[i]) {
vs.strings = nil
break
}
vs.strings[i] = b.String()
}
}
if vs.strings == nil && cs.SpewKeys {
vs.strings = make([]string, len(values))
for i := range vs.values {
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
}
}
return vs
}
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
// directly, or whether it should be considered for sorting by surrogate keys
// (if the ConfigState allows it).
func canSortSimply(kind reflect.Kind) bool {
// This switch parallels valueSortLess, except for the default case.
switch kind {
case reflect.Bool:
return true
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return true
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return true
case reflect.Float32, reflect.Float64:
return true
case reflect.String:
return true
case reflect.Uintptr:
return true
case reflect.Array:
return true
}
return false
}
// Len returns the number of values in the slice. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Len() int {
return len(s.values)
}
// Swap swaps the values at the passed indices. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Swap(i, j int) {
s.values[i], s.values[j] = s.values[j], s.values[i]
if s.strings != nil {
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
}
}
// valueSortLess returns whether the first value should sort before the second
// value. It is used by valueSorter.Less as part of the sort.Interface
// implementation.
func valueSortLess(a, b reflect.Value) bool {
switch a.Kind() {
case reflect.Bool:
return !a.Bool() && b.Bool()
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return a.Int() < b.Int()
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return a.Uint() < b.Uint()
case reflect.Float32, reflect.Float64:
return a.Float() < b.Float()
case reflect.String:
return a.String() < b.String()
case reflect.Uintptr:
return a.Uint() < b.Uint()
case reflect.Array:
// Compare the contents of both arrays.
l := a.Len()
for i := 0; i < l; i++ {
av := a.Index(i)
bv := b.Index(i)
if av.Interface() == bv.Interface() {
continue
}
return valueSortLess(av, bv)
}
}
return a.String() < b.String()
}
// Less returns whether the value at index i should sort before the
// value at index j. It is part of the sort.Interface implementation.
func (s *valuesSorter) Less(i, j int) bool {
if s.strings == nil {
return valueSortLess(s.values[i], s.values[j])
}
return s.strings[i] < s.strings[j]
}
// sortValues is a sort function that handles both native types and any type that
// can be converted to error or Stringer. Other inputs are sorted according to
// their Value.String() value to ensure display stability.
func sortValues(values []reflect.Value, cs *ConfigState) {
if len(values) == 0 {
return
}
sort.Sort(newValuesSorter(values, cs))
}

298
vendor/github.com/davecgh/go-spew/spew/common_test.go generated vendored Normal file
View File

@@ -0,0 +1,298 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew_test
import (
"fmt"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
)
// custom type to test Stinger interface on non-pointer receiver.
type stringer string
// String implements the Stringer interface for testing invocation of custom
// stringers on types with non-pointer receivers.
func (s stringer) String() string {
return "stringer " + string(s)
}
// custom type to test Stinger interface on pointer receiver.
type pstringer string
// String implements the Stringer interface for testing invocation of custom
// stringers on types with only pointer receivers.
func (s *pstringer) String() string {
return "stringer " + string(*s)
}
// xref1 and xref2 are cross referencing structs for testing circular reference
// detection.
type xref1 struct {
ps2 *xref2
}
type xref2 struct {
ps1 *xref1
}
// indirCir1, indirCir2, and indirCir3 are used to generate an indirect circular
// reference for testing detection.
type indirCir1 struct {
ps2 *indirCir2
}
type indirCir2 struct {
ps3 *indirCir3
}
type indirCir3 struct {
ps1 *indirCir1
}
// embed is used to test embedded structures.
type embed struct {
a string
}
// embedwrap is used to test embedded structures.
type embedwrap struct {
*embed
e *embed
}
// panicer is used to intentionally cause a panic for testing spew properly
// handles them
type panicer int
func (p panicer) String() string {
panic("test panic")
}
// customError is used to test custom error interface invocation.
type customError int
func (e customError) Error() string {
return fmt.Sprintf("error: %d", int(e))
}
// stringizeWants converts a slice of wanted test output into a format suitable
// for a test error message.
func stringizeWants(wants []string) string {
s := ""
for i, want := range wants {
if i > 0 {
s += fmt.Sprintf("want%d: %s", i+1, want)
} else {
s += "want: " + want
}
}
return s
}
// testFailed returns whether or not a test failed by checking if the result
// of the test is in the slice of wanted strings.
func testFailed(result string, wants []string) bool {
for _, want := range wants {
if result == want {
return false
}
}
return true
}
type sortableStruct struct {
x int
}
func (ss sortableStruct) String() string {
return fmt.Sprintf("ss.%d", ss.x)
}
type unsortableStruct struct {
x int
}
type sortTestCase struct {
input []reflect.Value
expected []reflect.Value
}
func helpTestSortValues(tests []sortTestCase, cs *spew.ConfigState, t *testing.T) {
getInterfaces := func(values []reflect.Value) []interface{} {
interfaces := []interface{}{}
for _, v := range values {
interfaces = append(interfaces, v.Interface())
}
return interfaces
}
for _, test := range tests {
spew.SortValues(test.input, cs)
// reflect.DeepEqual cannot really make sense of reflect.Value,
// probably because of all the pointer tricks. For instance,
// v(2.0) != v(2.0) on a 32-bits system. Turn them into interface{}
// instead.
input := getInterfaces(test.input)
expected := getInterfaces(test.expected)
if !reflect.DeepEqual(input, expected) {
t.Errorf("Sort mismatch:\n %v != %v", input, expected)
}
}
}
// TestSortValues ensures the sort functionality for relect.Value based sorting
// works as intended.
func TestSortValues(t *testing.T) {
v := reflect.ValueOf
a := v("a")
b := v("b")
c := v("c")
embedA := v(embed{"a"})
embedB := v(embed{"b"})
embedC := v(embed{"c"})
tests := []sortTestCase{
// No values.
{
[]reflect.Value{},
[]reflect.Value{},
},
// Bools.
{
[]reflect.Value{v(false), v(true), v(false)},
[]reflect.Value{v(false), v(false), v(true)},
},
// Ints.
{
[]reflect.Value{v(2), v(1), v(3)},
[]reflect.Value{v(1), v(2), v(3)},
},
// Uints.
{
[]reflect.Value{v(uint8(2)), v(uint8(1)), v(uint8(3))},
[]reflect.Value{v(uint8(1)), v(uint8(2)), v(uint8(3))},
},
// Floats.
{
[]reflect.Value{v(2.0), v(1.0), v(3.0)},
[]reflect.Value{v(1.0), v(2.0), v(3.0)},
},
// Strings.
{
[]reflect.Value{b, a, c},
[]reflect.Value{a, b, c},
},
// Array
{
[]reflect.Value{v([3]int{3, 2, 1}), v([3]int{1, 3, 2}), v([3]int{1, 2, 3})},
[]reflect.Value{v([3]int{1, 2, 3}), v([3]int{1, 3, 2}), v([3]int{3, 2, 1})},
},
// Uintptrs.
{
[]reflect.Value{v(uintptr(2)), v(uintptr(1)), v(uintptr(3))},
[]reflect.Value{v(uintptr(1)), v(uintptr(2)), v(uintptr(3))},
},
// SortableStructs.
{
// Note: not sorted - DisableMethods is set.
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
},
// UnsortableStructs.
{
// Note: not sorted - SpewKeys is false.
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
},
// Invalid.
{
[]reflect.Value{embedB, embedA, embedC},
[]reflect.Value{embedB, embedA, embedC},
},
}
cs := spew.ConfigState{DisableMethods: true, SpewKeys: false}
helpTestSortValues(tests, &cs, t)
}
// TestSortValuesWithMethods ensures the sort functionality for relect.Value
// based sorting works as intended when using string methods.
func TestSortValuesWithMethods(t *testing.T) {
v := reflect.ValueOf
a := v("a")
b := v("b")
c := v("c")
tests := []sortTestCase{
// Ints.
{
[]reflect.Value{v(2), v(1), v(3)},
[]reflect.Value{v(1), v(2), v(3)},
},
// Strings.
{
[]reflect.Value{b, a, c},
[]reflect.Value{a, b, c},
},
// SortableStructs.
{
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
},
// UnsortableStructs.
{
// Note: not sorted - SpewKeys is false.
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
},
}
cs := spew.ConfigState{DisableMethods: false, SpewKeys: false}
helpTestSortValues(tests, &cs, t)
}
// TestSortValuesWithSpew ensures the sort functionality for relect.Value
// based sorting works as intended when using spew to stringify keys.
func TestSortValuesWithSpew(t *testing.T) {
v := reflect.ValueOf
a := v("a")
b := v("b")
c := v("c")
tests := []sortTestCase{
// Ints.
{
[]reflect.Value{v(2), v(1), v(3)},
[]reflect.Value{v(1), v(2), v(3)},
},
// Strings.
{
[]reflect.Value{b, a, c},
[]reflect.Value{a, b, c},
},
// SortableStructs.
{
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
},
// UnsortableStructs.
{
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
[]reflect.Value{v(unsortableStruct{1}), v(unsortableStruct{2}), v(unsortableStruct{3})},
},
}
cs := spew.ConfigState{DisableMethods: true, SpewKeys: true}
helpTestSortValues(tests, &cs, t)
}

297
vendor/github.com/davecgh/go-spew/spew/config.go generated vendored Normal file
View File

@@ -0,0 +1,297 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"os"
)
// ConfigState houses the configuration options used by spew to format and
// display values. There is a global instance, Config, that is used to control
// all top-level Formatter and Dump functionality. Each ConfigState instance
// provides methods equivalent to the top-level functions.
//
// The zero value for ConfigState provides no indentation. You would typically
// want to set it to a space or a tab.
//
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
// with default settings. See the documentation of NewDefaultConfig for default
// values.
type ConfigState struct {
// Indent specifies the string to use for each indentation level. The
// global config instance that all top-level functions use set this to a
// single space by default. If you would like more indentation, you might
// set this to a tab with "\t" or perhaps two spaces with " ".
Indent string
// MaxDepth controls the maximum number of levels to descend into nested
// data structures. The default, 0, means there is no limit.
//
// NOTE: Circular data structures are properly detected, so it is not
// necessary to set this value unless you specifically want to limit deeply
// nested data structures.
MaxDepth int
// DisableMethods specifies whether or not error and Stringer interfaces are
// invoked for types that implement them.
DisableMethods bool
// DisablePointerMethods specifies whether or not to check for and invoke
// error and Stringer interfaces on types which only accept a pointer
// receiver when the current type is not a pointer.
//
// NOTE: This might be an unsafe action since calling one of these methods
// with a pointer receiver could technically mutate the value, however,
// in practice, types which choose to satisify an error or Stringer
// interface with a pointer receiver should not be mutating their state
// inside these interface methods. As a result, this option relies on
// access to the unsafe package, so it will not have any effect when
// running in environments without access to the unsafe package such as
// Google App Engine or with the "disableunsafe" build tag specified.
DisablePointerMethods bool
// ContinueOnMethod specifies whether or not recursion should continue once
// a custom error or Stringer interface is invoked. The default, false,
// means it will print the results of invoking the custom error or Stringer
// interface and return immediately instead of continuing to recurse into
// the internals of the data type.
//
// NOTE: This flag does not have any effect if method invocation is disabled
// via the DisableMethods or DisablePointerMethods options.
ContinueOnMethod bool
// SortKeys specifies map keys should be sorted before being printed. Use
// this to have a more deterministic, diffable output. Note that only
// native types (bool, int, uint, floats, uintptr and string) and types
// that support the error or Stringer interfaces (if methods are
// enabled) are supported, with other types sorted according to the
// reflect.Value.String() output which guarantees display stability.
SortKeys bool
// SpewKeys specifies that, as a last resort attempt, map keys should
// be spewed to strings and sorted by those strings. This is only
// considered if SortKeys is true.
SpewKeys bool
}
// Config is the active configuration of the top-level functions.
// The configuration can be changed by modifying the contents of spew.Config.
var Config = ConfigState{Indent: " "}
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the formatted string as a value that satisfies error. See NewFormatter
// for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, c.convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, c.convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, c.convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a Formatter interface returned by c.NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, c.convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
return fmt.Print(c.convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, c.convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
return fmt.Println(c.convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprint(a ...interface{}) string {
return fmt.Sprint(c.convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, c.convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a Formatter interface returned by c.NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintln(a ...interface{}) string {
return fmt.Sprintln(c.convertArgs(a)...)
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
c.Printf, c.Println, or c.Printf.
*/
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(c, v)
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
fdump(c, w, a...)
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by modifying the public members
of c. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func (c *ConfigState) Dump(a ...interface{}) {
fdump(c, os.Stdout, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func (c *ConfigState) Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(c, &buf, a...)
return buf.String()
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a spew Formatter interface using
// the ConfigState associated with s.
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = newFormatter(c, arg)
}
return formatters
}
// NewDefaultConfig returns a ConfigState with the following default settings.
//
// Indent: " "
// MaxDepth: 0
// DisableMethods: false
// DisablePointerMethods: false
// ContinueOnMethod: false
// SortKeys: false
func NewDefaultConfig() *ConfigState {
return &ConfigState{Indent: " "}
}

202
vendor/github.com/davecgh/go-spew/spew/doc.go generated vendored Normal file
View File

@@ -0,0 +1,202 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
Package spew implements a deep pretty printer for Go data structures to aid in
debugging.
A quick overview of the additional features spew provides over the built-in
printing facilities for Go data types are as follows:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output (only when using
Dump style)
There are two different approaches spew allows for dumping Go data structures:
* Dump style which prints with newlines, customizable indentation,
and additional debug information such as types and all pointer addresses
used to indirect to the final value
* A custom Formatter interface that integrates cleanly with the standard fmt
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
similar to the default %v while providing the additional functionality
outlined above and passing unsupported format verbs such as %x and %q
along to fmt
Quick Start
This section demonstrates how to quickly get started with spew. See the
sections below for further details on formatting and configuration options.
To dump a variable with full newlines, indentation, type, and pointer
information use Dump, Fdump, or Sdump:
spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)
Alternatively, if you would prefer to use format strings with a compacted inline
printing style, use the convenience wrappers Printf, Fprintf, etc with
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
%#+v (adds types and pointer addresses):
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
Configuration Options
Configuration of spew is handled by fields in the ConfigState type. For
convenience, all of the top-level functions use a global state available
via the spew.Config global.
It is also possible to create a ConfigState instance that provides methods
equivalent to the top-level functions. This allows concurrent configuration
options. See the ConfigState documentation for more details.
The following configuration options are available:
* Indent
String to use for each indentation level for Dump functions.
It is a single space by default. A popular alternative is "\t".
* MaxDepth
Maximum number of levels to descend into nested data structures.
There is no limit by default.
* DisableMethods
Disables invocation of error and Stringer interface methods.
Method invocation is enabled by default.
* DisablePointerMethods
Disables invocation of error and Stringer interface methods on types
which only accept pointer receivers from non-pointer variables.
Pointer method invocation is enabled by default.
* ContinueOnMethod
Enables recursion into types after invoking error and Stringer interface
methods. Recursion after method invocation is disabled by default.
* SortKeys
Specifies map keys should be sorted before being printed. Use
this to have a more deterministic, diffable output. Note that
only native types (bool, int, uint, floats, uintptr and string)
and types which implement error or Stringer interfaces are
supported with other types sorted according to the
reflect.Value.String() output which guarantees display
stability. Natural map order is used by default.
* SpewKeys
Specifies that, as a last resort attempt, map keys should be
spewed to strings and sorted by those strings. This is only
considered if SortKeys is true.
Dump Usage
Simply call spew.Dump with a list of variables you want to dump:
spew.Dump(myVar1, myVar2, ...)
You may also call spew.Fdump if you would prefer to output to an arbitrary
io.Writer. For example, to dump to standard error:
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
A third option is to call spew.Sdump to get the formatted output as a string:
str := spew.Sdump(myVar1, myVar2, ...)
Sample Dump Output
See the Dump example for details on the setup of the types and variables being
shown here.
(main.Foo) {
unexportedField: (*main.Bar)(0xf84002e210)({
flag: (main.Flag) flagTwo,
data: (uintptr) <nil>
}),
ExportedField: (map[interface {}]interface {}) (len=1) {
(string) (len=3) "one": (bool) true
}
}
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
command as shown.
([]uint8) (len=32 cap=32) {
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000020 31 32 |12|
}
Custom Formatter
Spew provides a custom formatter that implements the fmt.Formatter interface
so that it integrates cleanly with standard fmt package printing functions. The
formatter is useful for inline printing of smaller data types similar to the
standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Custom Formatter Usage
The simplest way to make use of the spew custom formatter is to call one of the
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
functions have syntax you are most likely already familiar with:
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Println(myVar, myVar2)
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
See the Index for the full list convenience functions.
Sample Formatter Output
Double pointer to a uint8:
%v: <**>5
%+v: <**>(0xf8400420d0->0xf8400420c8)5
%#v: (**uint8)5
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
Pointer to circular struct with a uint8 field and a pointer to itself:
%v: <*>{1 <*><shown>}
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
See the Printf example for details on the setup of variables being shown
here.
Errors
Since it is possible for custom Stringer/error interfaces to panic, spew
detects them and handles them internally by printing the panic information
inline with the output. Since spew is intended to provide deep pretty printing
capabilities on structures, it intentionally does not return any errors.
*/
package spew

509
vendor/github.com/davecgh/go-spew/spew/dump.go generated vendored Normal file
View File

@@ -0,0 +1,509 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"os"
"reflect"
"regexp"
"strconv"
"strings"
)
var (
// uint8Type is a reflect.Type representing a uint8. It is used to
// convert cgo types to uint8 slices for hexdumping.
uint8Type = reflect.TypeOf(uint8(0))
// cCharRE is a regular expression that matches a cgo char.
// It is used to detect character arrays to hexdump them.
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
// char. It is used to detect unsigned character arrays to hexdump
// them.
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
// It is used to detect uint8_t arrays to hexdump them.
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
)
// dumpState contains information about the state of a dump operation.
type dumpState struct {
w io.Writer
depth int
pointers map[uintptr]int
ignoreNextType bool
ignoreNextIndent bool
cs *ConfigState
}
// indent performs indentation according to the depth level and cs.Indent
// option.
func (d *dumpState) indent() {
if d.ignoreNextIndent {
d.ignoreNextIndent = false
return
}
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
}
// unpackValue returns values inside of non-nil interfaces when possible.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface && !v.IsNil() {
v = v.Elem()
}
return v
}
// dumpPtr handles formatting of pointers by indirecting them as necessary.
func (d *dumpState) dumpPtr(v reflect.Value) {
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range d.pointers {
if depth >= d.depth {
delete(d.pointers, k)
}
}
// Keep list of all dereferenced pointers to show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by dereferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
cycleFound = true
indirects--
break
}
d.pointers[addr] = d.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type information.
d.w.Write(openParenBytes)
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
d.w.Write([]byte(ve.Type().String()))
d.w.Write(closeParenBytes)
// Display pointer information.
if len(pointerChain) > 0 {
d.w.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
d.w.Write(pointerChainBytes)
}
printHexPtr(d.w, addr)
}
d.w.Write(closeParenBytes)
}
// Display dereferenced value.
d.w.Write(openParenBytes)
switch {
case nilFound == true:
d.w.Write(nilAngleBytes)
case cycleFound == true:
d.w.Write(circularBytes)
default:
d.ignoreNextType = true
d.dump(ve)
}
d.w.Write(closeParenBytes)
}
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
// reflection) arrays and slices are dumped in hexdump -C fashion.
func (d *dumpState) dumpSlice(v reflect.Value) {
// Determine whether this type should be hex dumped or not. Also,
// for types which should be hexdumped, try to use the underlying data
// first, then fall back to trying to convert them to a uint8 slice.
var buf []uint8
doConvert := false
doHexDump := false
numEntries := v.Len()
if numEntries > 0 {
vt := v.Index(0).Type()
vts := vt.String()
switch {
// C types that need to be converted.
case cCharRE.MatchString(vts):
fallthrough
case cUnsignedCharRE.MatchString(vts):
fallthrough
case cUint8tCharRE.MatchString(vts):
doConvert = true
// Try to use existing uint8 slices and fall back to converting
// and copying if that fails.
case vt.Kind() == reflect.Uint8:
// We need an addressable interface to convert the type
// to a byte slice. However, the reflect package won't
// give us an interface on certain things like
// unexported struct fields in order to enforce
// visibility rules. We use unsafe, when available, to
// bypass these restrictions since this package does not
// mutate the values.
vs := v
if !vs.CanInterface() || !vs.CanAddr() {
vs = unsafeReflectValue(vs)
}
if !UnsafeDisabled {
vs = vs.Slice(0, numEntries)
// Use the existing uint8 slice if it can be
// type asserted.
iface := vs.Interface()
if slice, ok := iface.([]uint8); ok {
buf = slice
doHexDump = true
break
}
}
// The underlying data needs to be converted if it can't
// be type asserted to a uint8 slice.
doConvert = true
}
// Copy and convert the underlying type if needed.
if doConvert && vt.ConvertibleTo(uint8Type) {
// Convert and copy each element into a uint8 byte
// slice.
buf = make([]uint8, numEntries)
for i := 0; i < numEntries; i++ {
vv := v.Index(i)
buf[i] = uint8(vv.Convert(uint8Type).Uint())
}
doHexDump = true
}
}
// Hexdump the entire slice as needed.
if doHexDump {
indent := strings.Repeat(d.cs.Indent, d.depth)
str := indent + hex.Dump(buf)
str = strings.Replace(str, "\n", "\n"+indent, -1)
str = strings.TrimRight(str, d.cs.Indent)
d.w.Write([]byte(str))
return
}
// Recursively call dump for each item.
for i := 0; i < numEntries; i++ {
d.dump(d.unpackValue(v.Index(i)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
// dump is the main workhorse for dumping a value. It uses the passed reflect
// value to figure out what kind of object we are dealing with and formats it
// appropriately. It is a recursive function, however circular data structures
// are detected and handled properly.
func (d *dumpState) dump(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
d.w.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
d.indent()
d.dumpPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !d.ignoreNextType {
d.indent()
d.w.Write(openParenBytes)
d.w.Write([]byte(v.Type().String()))
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
d.ignoreNextType = false
// Display length and capacity if the built-in len and cap functions
// work with the value's kind and the len/cap itself is non-zero.
valueLen, valueCap := 0, 0
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.Chan:
valueLen, valueCap = v.Len(), v.Cap()
case reflect.Map, reflect.String:
valueLen = v.Len()
}
if valueLen != 0 || valueCap != 0 {
d.w.Write(openParenBytes)
if valueLen != 0 {
d.w.Write(lenEqualsBytes)
printInt(d.w, int64(valueLen), 10)
}
if valueCap != 0 {
if valueLen != 0 {
d.w.Write(spaceBytes)
}
d.w.Write(capEqualsBytes)
printInt(d.w, int64(valueCap), 10)
}
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
// Call Stringer/error interfaces if they exist and the handle methods flag
// is enabled
if !d.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(d.cs, d.w, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(d.w, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(d.w, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(d.w, v.Uint(), 10)
case reflect.Float32:
printFloat(d.w, v.Float(), 32)
case reflect.Float64:
printFloat(d.w, v.Float(), 64)
case reflect.Complex64:
printComplex(d.w, v.Complex(), 32)
case reflect.Complex128:
printComplex(d.w, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
d.dumpSlice(v)
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.String:
d.w.Write([]byte(strconv.Quote(v.String())))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
d.w.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
numEntries := v.Len()
keys := v.MapKeys()
if d.cs.SortKeys {
sortValues(keys, d.cs)
}
for i, key := range keys {
d.dump(d.unpackValue(key))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.MapIndex(key)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Struct:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
vt := v.Type()
numFields := v.NumField()
for i := 0; i < numFields; i++ {
d.indent()
vtf := vt.Field(i)
d.w.Write([]byte(vtf.Name))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.Field(i)))
if i < (numFields - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(d.w, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(d.w, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it in case any new
// types are added.
default:
if v.CanInterface() {
fmt.Fprintf(d.w, "%v", v.Interface())
} else {
fmt.Fprintf(d.w, "%v", v.String())
}
}
}
// fdump is a helper function to consolidate the logic from the various public
// methods which take varying writers and config states.
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
for _, arg := range a {
if arg == nil {
w.Write(interfaceBytes)
w.Write(spaceBytes)
w.Write(nilAngleBytes)
w.Write(newlineBytes)
continue
}
d := dumpState{w: w, cs: cs}
d.pointers = make(map[uintptr]int)
d.dump(reflect.ValueOf(arg))
d.w.Write(newlineBytes)
}
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func Fdump(w io.Writer, a ...interface{}) {
fdump(&Config, w, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(&Config, &buf, a...)
return buf.String()
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by an exported package global,
spew.Config. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func Dump(a ...interface{}) {
fdump(&Config, os.Stdout, a...)
}

1042
vendor/github.com/davecgh/go-spew/spew/dump_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

98
vendor/github.com/davecgh/go-spew/spew/dumpcgo_test.go generated vendored Normal file
View File

@@ -0,0 +1,98 @@
// Copyright (c) 2013 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when both cgo is supported and "-tags testcgo" is added to the go test
// command line. This means the cgo tests are only added (and hence run) when
// specifially requested. This configuration is used because spew itself
// does not require cgo to run even though it does handle certain cgo types
// specially. Rather than forcing all clients to require cgo and an external
// C compiler just to run the tests, this scheme makes them optional.
// +build cgo,testcgo
package spew_test
import (
"fmt"
"github.com/davecgh/go-spew/spew/testdata"
)
func addCgoDumpTests() {
// C char pointer.
v := testdata.GetCgoCharPointer()
nv := testdata.GetCgoNullCharPointer()
pv := &v
vcAddr := fmt.Sprintf("%p", v)
vAddr := fmt.Sprintf("%p", pv)
pvAddr := fmt.Sprintf("%p", &pv)
vt := "*testdata._Ctype_char"
vs := "116"
addDumpTest(v, "("+vt+")("+vcAddr+")("+vs+")\n")
addDumpTest(pv, "(*"+vt+")("+vAddr+"->"+vcAddr+")("+vs+")\n")
addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+"->"+vcAddr+")("+vs+")\n")
addDumpTest(nv, "("+vt+")(<nil>)\n")
// C char array.
v2, v2l, v2c := testdata.GetCgoCharArray()
v2Len := fmt.Sprintf("%d", v2l)
v2Cap := fmt.Sprintf("%d", v2c)
v2t := "[6]testdata._Ctype_char"
v2s := "(len=" + v2Len + " cap=" + v2Cap + ") " +
"{\n 00000000 74 65 73 74 32 00 " +
" |test2.|\n}"
addDumpTest(v2, "("+v2t+") "+v2s+"\n")
// C unsigned char array.
v3, v3l, v3c := testdata.GetCgoUnsignedCharArray()
v3Len := fmt.Sprintf("%d", v3l)
v3Cap := fmt.Sprintf("%d", v3c)
v3t := "[6]testdata._Ctype_unsignedchar"
v3s := "(len=" + v3Len + " cap=" + v3Cap + ") " +
"{\n 00000000 74 65 73 74 33 00 " +
" |test3.|\n}"
addDumpTest(v3, "("+v3t+") "+v3s+"\n")
// C signed char array.
v4, v4l, v4c := testdata.GetCgoSignedCharArray()
v4Len := fmt.Sprintf("%d", v4l)
v4Cap := fmt.Sprintf("%d", v4c)
v4t := "[6]testdata._Ctype_schar"
v4t2 := "testdata._Ctype_schar"
v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " +
"{\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 101,\n (" + v4t2 +
") 115,\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 52,\n (" + v4t2 +
") 0\n}"
addDumpTest(v4, "("+v4t+") "+v4s+"\n")
// C uint8_t array.
v5, v5l, v5c := testdata.GetCgoUint8tArray()
v5Len := fmt.Sprintf("%d", v5l)
v5Cap := fmt.Sprintf("%d", v5c)
v5t := "[6]testdata._Ctype_uint8_t"
v5s := "(len=" + v5Len + " cap=" + v5Cap + ") " +
"{\n 00000000 74 65 73 74 35 00 " +
" |test5.|\n}"
addDumpTest(v5, "("+v5t+") "+v5s+"\n")
// C typedefed unsigned char array.
v6, v6l, v6c := testdata.GetCgoTypdefedUnsignedCharArray()
v6Len := fmt.Sprintf("%d", v6l)
v6Cap := fmt.Sprintf("%d", v6c)
v6t := "[6]testdata._Ctype_custom_uchar_t"
v6s := "(len=" + v6Len + " cap=" + v6Cap + ") " +
"{\n 00000000 74 65 73 74 36 00 " +
" |test6.|\n}"
addDumpTest(v6, "("+v6t+") "+v6s+"\n")
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) 2013 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when either cgo is not supported or "-tags testcgo" is not added to the go
// test command line. This file intentionally does not setup any cgo tests in
// this scenario.
// +build !cgo !testcgo
package spew_test
func addCgoDumpTests() {
// Don't add any tests for cgo since this file is only compiled when
// there should not be any cgo tests.
}

226
vendor/github.com/davecgh/go-spew/spew/example_test.go generated vendored Normal file
View File

@@ -0,0 +1,226 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew_test
import (
"fmt"
"github.com/davecgh/go-spew/spew"
)
type Flag int
const (
flagOne Flag = iota
flagTwo
)
var flagStrings = map[Flag]string{
flagOne: "flagOne",
flagTwo: "flagTwo",
}
func (f Flag) String() string {
if s, ok := flagStrings[f]; ok {
return s
}
return fmt.Sprintf("Unknown flag (%d)", int(f))
}
type Bar struct {
data uintptr
}
type Foo struct {
unexportedField Bar
ExportedField map[interface{}]interface{}
}
// This example demonstrates how to use Dump to dump variables to stdout.
func ExampleDump() {
// The following package level declarations are assumed for this example:
/*
type Flag int
const (
flagOne Flag = iota
flagTwo
)
var flagStrings = map[Flag]string{
flagOne: "flagOne",
flagTwo: "flagTwo",
}
func (f Flag) String() string {
if s, ok := flagStrings[f]; ok {
return s
}
return fmt.Sprintf("Unknown flag (%d)", int(f))
}
type Bar struct {
data uintptr
}
type Foo struct {
unexportedField Bar
ExportedField map[interface{}]interface{}
}
*/
// Setup some sample data structures for the example.
bar := Bar{uintptr(0)}
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
f := Flag(5)
b := []byte{
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
0x31, 0x32,
}
// Dump!
spew.Dump(s1, f, b)
// Output:
// (spew_test.Foo) {
// unexportedField: (spew_test.Bar) {
// data: (uintptr) <nil>
// },
// ExportedField: (map[interface {}]interface {}) (len=1) {
// (string) (len=3) "one": (bool) true
// }
// }
// (spew_test.Flag) Unknown flag (5)
// ([]uint8) (len=34 cap=34) {
// 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
// 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
// 00000020 31 32 |12|
// }
//
}
// This example demonstrates how to use Printf to display a variable with a
// format string and inline formatting.
func ExamplePrintf() {
// Create a double pointer to a uint 8.
ui8 := uint8(5)
pui8 := &ui8
ppui8 := &pui8
// Create a circular data type.
type circular struct {
ui8 uint8
c *circular
}
c := circular{ui8: 1}
c.c = &c
// Print!
spew.Printf("ppui8: %v\n", ppui8)
spew.Printf("circular: %v\n", c)
// Output:
// ppui8: <**>5
// circular: {1 <*>{1 <*><shown>}}
}
// This example demonstrates how to use a ConfigState.
func ExampleConfigState() {
// Modify the indent level of the ConfigState only. The global
// configuration is not modified.
scs := spew.ConfigState{Indent: "\t"}
// Output using the ConfigState instance.
v := map[string]int{"one": 1}
scs.Printf("v: %v\n", v)
scs.Dump(v)
// Output:
// v: map[one:1]
// (map[string]int) (len=1) {
// (string) (len=3) "one": (int) 1
// }
}
// This example demonstrates how to use ConfigState.Dump to dump variables to
// stdout
func ExampleConfigState_Dump() {
// See the top-level Dump example for details on the types used in this
// example.
// Create two ConfigState instances with different indentation.
scs := spew.ConfigState{Indent: "\t"}
scs2 := spew.ConfigState{Indent: " "}
// Setup some sample data structures for the example.
bar := Bar{uintptr(0)}
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
// Dump using the ConfigState instances.
scs.Dump(s1)
scs2.Dump(s1)
// Output:
// (spew_test.Foo) {
// unexportedField: (spew_test.Bar) {
// data: (uintptr) <nil>
// },
// ExportedField: (map[interface {}]interface {}) (len=1) {
// (string) (len=3) "one": (bool) true
// }
// }
// (spew_test.Foo) {
// unexportedField: (spew_test.Bar) {
// data: (uintptr) <nil>
// },
// ExportedField: (map[interface {}]interface {}) (len=1) {
// (string) (len=3) "one": (bool) true
// }
// }
//
}
// This example demonstrates how to use ConfigState.Printf to display a variable
// with a format string and inline formatting.
func ExampleConfigState_Printf() {
// See the top-level Dump example for details on the types used in this
// example.
// Create two ConfigState instances and modify the method handling of the
// first ConfigState only.
scs := spew.NewDefaultConfig()
scs2 := spew.NewDefaultConfig()
scs.DisableMethods = true
// Alternatively
// scs := spew.ConfigState{Indent: " ", DisableMethods: true}
// scs2 := spew.ConfigState{Indent: " "}
// This is of type Flag which implements a Stringer and has raw value 1.
f := flagTwo
// Dump using the ConfigState instances.
scs.Printf("f: %v\n", f)
scs2.Printf("f: %v\n", f)
// Output:
// f: 1
// f: flagTwo
}

419
vendor/github.com/davecgh/go-spew/spew/format.go generated vendored Normal file
View File

@@ -0,0 +1,419 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
)
// supportedFlags is a list of all the character flags supported by fmt package.
const supportedFlags = "0-+# "
// formatState implements the fmt.Formatter interface and contains information
// about the state of a formatting operation. The NewFormatter function can
// be used to get a new Formatter which can be used directly as arguments
// in standard fmt package printing calls.
type formatState struct {
value interface{}
fs fmt.State
depth int
pointers map[uintptr]int
ignoreNextType bool
cs *ConfigState
}
// buildDefaultFormat recreates the original format string without precision
// and width information to pass in to fmt.Sprintf in the case of an
// unrecognized type. Unless new types are added to the language, this
// function won't ever be called.
func (f *formatState) buildDefaultFormat() (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
buf.WriteRune('v')
format = buf.String()
return format
}
// constructOrigFormat recreates the original format string including precision
// and width information to pass along to the standard fmt package. This allows
// automatic deferral of all format strings this package doesn't support.
func (f *formatState) constructOrigFormat(verb rune) (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
if width, ok := f.fs.Width(); ok {
buf.WriteString(strconv.Itoa(width))
}
if precision, ok := f.fs.Precision(); ok {
buf.Write(precisionBytes)
buf.WriteString(strconv.Itoa(precision))
}
buf.WriteRune(verb)
format = buf.String()
return format
}
// unpackValue returns values inside of non-nil interfaces when possible and
// ensures that types for values which have been unpacked from an interface
// are displayed when the show types flag is also set.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface {
f.ignoreNextType = false
if !v.IsNil() {
v = v.Elem()
}
}
return v
}
// formatPtr handles formatting of pointers by indirecting them as necessary.
func (f *formatState) formatPtr(v reflect.Value) {
// Display nil if top level pointer is nil.
showTypes := f.fs.Flag('#')
if v.IsNil() && (!showTypes || f.ignoreNextType) {
f.fs.Write(nilAngleBytes)
return
}
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range f.pointers {
if depth >= f.depth {
delete(f.pointers, k)
}
}
// Keep list of all dereferenced pointers to possibly show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by derferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
cycleFound = true
indirects--
break
}
f.pointers[addr] = f.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type or indirection level depending on flags.
if showTypes && !f.ignoreNextType {
f.fs.Write(openParenBytes)
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
f.fs.Write([]byte(ve.Type().String()))
f.fs.Write(closeParenBytes)
} else {
if nilFound || cycleFound {
indirects += strings.Count(ve.Type().String(), "*")
}
f.fs.Write(openAngleBytes)
f.fs.Write([]byte(strings.Repeat("*", indirects)))
f.fs.Write(closeAngleBytes)
}
// Display pointer information depending on flags.
if f.fs.Flag('+') && (len(pointerChain) > 0) {
f.fs.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
f.fs.Write(pointerChainBytes)
}
printHexPtr(f.fs, addr)
}
f.fs.Write(closeParenBytes)
}
// Display dereferenced value.
switch {
case nilFound == true:
f.fs.Write(nilAngleBytes)
case cycleFound == true:
f.fs.Write(circularShortBytes)
default:
f.ignoreNextType = true
f.format(ve)
}
}
// format is the main workhorse for providing the Formatter interface. It
// uses the passed reflect value to figure out what kind of object we are
// dealing with and formats it appropriately. It is a recursive function,
// however circular data structures are detected and handled properly.
func (f *formatState) format(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
f.fs.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
f.formatPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !f.ignoreNextType && f.fs.Flag('#') {
f.fs.Write(openParenBytes)
f.fs.Write([]byte(v.Type().String()))
f.fs.Write(closeParenBytes)
}
f.ignoreNextType = false
// Call Stringer/error interfaces if they exist and the handle methods
// flag is enabled.
if !f.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(f.cs, f.fs, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(f.fs, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(f.fs, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(f.fs, v.Uint(), 10)
case reflect.Float32:
printFloat(f.fs, v.Float(), 32)
case reflect.Float64:
printFloat(f.fs, v.Float(), 64)
case reflect.Complex64:
printComplex(f.fs, v.Complex(), 32)
case reflect.Complex128:
printComplex(f.fs, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
f.fs.Write(openBracketBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
numEntries := v.Len()
for i := 0; i < numEntries; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(v.Index(i)))
}
}
f.depth--
f.fs.Write(closeBracketBytes)
case reflect.String:
f.fs.Write([]byte(v.String()))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
f.fs.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
f.fs.Write(openMapBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
keys := v.MapKeys()
if f.cs.SortKeys {
sortValues(keys, f.cs)
}
for i, key := range keys {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(key))
f.fs.Write(colonBytes)
f.ignoreNextType = true
f.format(f.unpackValue(v.MapIndex(key)))
}
}
f.depth--
f.fs.Write(closeMapBytes)
case reflect.Struct:
numFields := v.NumField()
f.fs.Write(openBraceBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
vt := v.Type()
for i := 0; i < numFields; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
vtf := vt.Field(i)
if f.fs.Flag('+') || f.fs.Flag('#') {
f.fs.Write([]byte(vtf.Name))
f.fs.Write(colonBytes)
}
f.format(f.unpackValue(v.Field(i)))
}
}
f.depth--
f.fs.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(f.fs, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(f.fs, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it if any get added.
default:
format := f.buildDefaultFormat()
if v.CanInterface() {
fmt.Fprintf(f.fs, format, v.Interface())
} else {
fmt.Fprintf(f.fs, format, v.String())
}
}
}
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
// details.
func (f *formatState) Format(fs fmt.State, verb rune) {
f.fs = fs
// Use standard formatting for verbs that are not v.
if verb != 'v' {
format := f.constructOrigFormat(verb)
fmt.Fprintf(fs, format, f.value)
return
}
if f.value == nil {
if fs.Flag('#') {
fs.Write(interfaceBytes)
}
fs.Write(nilAngleBytes)
return
}
f.format(reflect.ValueOf(f.value))
}
// newFormatter is a helper function to consolidate the logic from the various
// public methods which take varying config states.
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
fs := &formatState{value: v, cs: cs}
fs.pointers = make(map[uintptr]int)
return fs
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
Printf, Println, or Fprintf.
*/
func NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(&Config, v)
}

1558
vendor/github.com/davecgh/go-spew/spew/format_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
This test file is part of the spew package rather than than the spew_test
package because it needs access to internals to properly test certain cases
which are not possible via the public interface since they should never happen.
*/
package spew
import (
"bytes"
"reflect"
"testing"
)
// dummyFmtState implements a fake fmt.State to use for testing invalid
// reflect.Value handling. This is necessary because the fmt package catches
// invalid values before invoking the formatter on them.
type dummyFmtState struct {
bytes.Buffer
}
func (dfs *dummyFmtState) Flag(f int) bool {
if f == int('+') {
return true
}
return false
}
func (dfs *dummyFmtState) Precision() (int, bool) {
return 0, false
}
func (dfs *dummyFmtState) Width() (int, bool) {
return 0, false
}
// TestInvalidReflectValue ensures the dump and formatter code handles an
// invalid reflect value properly. This needs access to internal state since it
// should never happen in real code and therefore can't be tested via the public
// API.
func TestInvalidReflectValue(t *testing.T) {
i := 1
// Dump invalid reflect value.
v := new(reflect.Value)
buf := new(bytes.Buffer)
d := dumpState{w: buf, cs: &Config}
d.dump(*v)
s := buf.String()
want := "<invalid>"
if s != want {
t.Errorf("InvalidReflectValue #%d\n got: %s want: %s", i, s, want)
}
i++
// Formatter invalid reflect value.
buf2 := new(dummyFmtState)
f := formatState{value: *v, cs: &Config, fs: buf2}
f.format(*v)
s = buf2.String()
want = "<invalid>"
if s != want {
t.Errorf("InvalidReflectValue #%d got: %s want: %s", i, s, want)
}
}
// SortValues makes the internal sortValues function available to the test
// package.
func SortValues(values []reflect.Value, cs *ConfigState) {
sortValues(values, cs)
}

View File

@@ -0,0 +1,101 @@
// Copyright (c) 2013-2015 Dave Collins <dave@davec.name>
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is not running on Google App Engine and "-tags disableunsafe"
// is not added to the go build command line.
// +build !appengine,!disableunsafe
/*
This test file is part of the spew package rather than than the spew_test
package because it needs access to internals to properly test certain cases
which are not possible via the public interface since they should never happen.
*/
package spew
import (
"bytes"
"reflect"
"testing"
"unsafe"
)
// changeKind uses unsafe to intentionally change the kind of a reflect.Value to
// the maximum kind value which does not exist. This is needed to test the
// fallback code which punts to the standard fmt library for new types that
// might get added to the language.
func changeKind(v *reflect.Value, readOnly bool) {
rvf := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + offsetFlag))
*rvf = *rvf | ((1<<flagKindWidth - 1) << flagKindShift)
if readOnly {
*rvf |= flagRO
} else {
*rvf &= ^uintptr(flagRO)
}
}
// TestAddedReflectValue tests functionaly of the dump and formatter code which
// falls back to the standard fmt library for new types that might get added to
// the language.
func TestAddedReflectValue(t *testing.T) {
i := 1
// Dump using a reflect.Value that is exported.
v := reflect.ValueOf(int8(5))
changeKind(&v, false)
buf := new(bytes.Buffer)
d := dumpState{w: buf, cs: &Config}
d.dump(v)
s := buf.String()
want := "(int8) 5"
if s != want {
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
}
i++
// Dump using a reflect.Value that is not exported.
changeKind(&v, true)
buf.Reset()
d.dump(v)
s = buf.String()
want = "(int8) <int8 Value>"
if s != want {
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
}
i++
// Formatter using a reflect.Value that is exported.
changeKind(&v, false)
buf2 := new(dummyFmtState)
f := formatState{value: v, cs: &Config, fs: buf2}
f.format(v)
s = buf2.String()
want = "5"
if s != want {
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
}
i++
// Formatter using a reflect.Value that is not exported.
changeKind(&v, true)
buf2.Reset()
f = formatState{value: v, cs: &Config, fs: buf2}
f.format(v)
s = buf2.String()
want = "<int8 Value>"
if s != want {
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
}
}

148
vendor/github.com/davecgh/go-spew/spew/spew.go generated vendored Normal file
View File

@@ -0,0 +1,148 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"fmt"
"io"
)
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the formatted string as a value that satisfies error. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a default Formatter interface returned by NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
func Print(a ...interface{}) (n int, err error) {
return fmt.Print(convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
func Println(a ...interface{}) (n int, err error) {
return fmt.Println(convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprint(a ...interface{}) string {
return fmt.Sprint(convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintln(a ...interface{}) string {
return fmt.Sprintln(convertArgs(a)...)
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a default spew Formatter interface.
func convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = NewFormatter(arg)
}
return formatters
}

309
vendor/github.com/davecgh/go-spew/spew/spew_test.go generated vendored Normal file
View File

@@ -0,0 +1,309 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew_test
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/davecgh/go-spew/spew"
)
// spewFunc is used to identify which public function of the spew package or
// ConfigState a test applies to.
type spewFunc int
const (
fCSFdump spewFunc = iota
fCSFprint
fCSFprintf
fCSFprintln
fCSPrint
fCSPrintln
fCSSdump
fCSSprint
fCSSprintf
fCSSprintln
fCSErrorf
fCSNewFormatter
fErrorf
fFprint
fFprintln
fPrint
fPrintln
fSdump
fSprint
fSprintf
fSprintln
)
// Map of spewFunc values to names for pretty printing.
var spewFuncStrings = map[spewFunc]string{
fCSFdump: "ConfigState.Fdump",
fCSFprint: "ConfigState.Fprint",
fCSFprintf: "ConfigState.Fprintf",
fCSFprintln: "ConfigState.Fprintln",
fCSSdump: "ConfigState.Sdump",
fCSPrint: "ConfigState.Print",
fCSPrintln: "ConfigState.Println",
fCSSprint: "ConfigState.Sprint",
fCSSprintf: "ConfigState.Sprintf",
fCSSprintln: "ConfigState.Sprintln",
fCSErrorf: "ConfigState.Errorf",
fCSNewFormatter: "ConfigState.NewFormatter",
fErrorf: "spew.Errorf",
fFprint: "spew.Fprint",
fFprintln: "spew.Fprintln",
fPrint: "spew.Print",
fPrintln: "spew.Println",
fSdump: "spew.Sdump",
fSprint: "spew.Sprint",
fSprintf: "spew.Sprintf",
fSprintln: "spew.Sprintln",
}
func (f spewFunc) String() string {
if s, ok := spewFuncStrings[f]; ok {
return s
}
return fmt.Sprintf("Unknown spewFunc (%d)", int(f))
}
// spewTest is used to describe a test to be performed against the public
// functions of the spew package or ConfigState.
type spewTest struct {
cs *spew.ConfigState
f spewFunc
format string
in interface{}
want string
}
// spewTests houses the tests to be performed against the public functions of
// the spew package and ConfigState.
//
// These tests are only intended to ensure the public functions are exercised
// and are intentionally not exhaustive of types. The exhaustive type
// tests are handled in the dump and format tests.
var spewTests []spewTest
// redirStdout is a helper function to return the standard output from f as a
// byte slice.
func redirStdout(f func()) ([]byte, error) {
tempFile, err := ioutil.TempFile("", "ss-test")
if err != nil {
return nil, err
}
fileName := tempFile.Name()
defer os.Remove(fileName) // Ignore error
origStdout := os.Stdout
os.Stdout = tempFile
f()
os.Stdout = origStdout
tempFile.Close()
return ioutil.ReadFile(fileName)
}
func initSpewTests() {
// Config states with various settings.
scsDefault := spew.NewDefaultConfig()
scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true}
scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true}
scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1}
scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true}
// Variables for tests on types which implement Stringer interface with and
// without a pointer receiver.
ts := stringer("test")
tps := pstringer("test")
// depthTester is used to test max depth handling for structs, array, slices
// and maps.
type depthTester struct {
ic indirCir1
arr [1]string
slice []string
m map[string]int
}
dt := depthTester{indirCir1{nil}, [1]string{"arr"}, []string{"slice"},
map[string]int{"one": 1}}
// Variable for tests on types which implement error interface.
te := customError(10)
spewTests = []spewTest{
{scsDefault, fCSFdump, "", int8(127), "(int8) 127\n"},
{scsDefault, fCSFprint, "", int16(32767), "32767"},
{scsDefault, fCSFprintf, "%v", int32(2147483647), "2147483647"},
{scsDefault, fCSFprintln, "", int(2147483647), "2147483647\n"},
{scsDefault, fCSPrint, "", int64(9223372036854775807), "9223372036854775807"},
{scsDefault, fCSPrintln, "", uint8(255), "255\n"},
{scsDefault, fCSSdump, "", uint8(64), "(uint8) 64\n"},
{scsDefault, fCSSprint, "", complex(1, 2), "(1+2i)"},
{scsDefault, fCSSprintf, "%v", complex(float32(3), 4), "(3+4i)"},
{scsDefault, fCSSprintln, "", complex(float64(5), 6), "(5+6i)\n"},
{scsDefault, fCSErrorf, "%#v", uint16(65535), "(uint16)65535"},
{scsDefault, fCSNewFormatter, "%v", uint32(4294967295), "4294967295"},
{scsDefault, fErrorf, "%v", uint64(18446744073709551615), "18446744073709551615"},
{scsDefault, fFprint, "", float32(3.14), "3.14"},
{scsDefault, fFprintln, "", float64(6.28), "6.28\n"},
{scsDefault, fPrint, "", true, "true"},
{scsDefault, fPrintln, "", false, "false\n"},
{scsDefault, fSdump, "", complex(-10, -20), "(complex128) (-10-20i)\n"},
{scsDefault, fSprint, "", complex(-1, -2), "(-1-2i)"},
{scsDefault, fSprintf, "%v", complex(float32(-3), -4), "(-3-4i)"},
{scsDefault, fSprintln, "", complex(float64(-5), -6), "(-5-6i)\n"},
{scsNoMethods, fCSFprint, "", ts, "test"},
{scsNoMethods, fCSFprint, "", &ts, "<*>test"},
{scsNoMethods, fCSFprint, "", tps, "test"},
{scsNoMethods, fCSFprint, "", &tps, "<*>test"},
{scsNoPmethods, fCSFprint, "", ts, "stringer test"},
{scsNoPmethods, fCSFprint, "", &ts, "<*>stringer test"},
{scsNoPmethods, fCSFprint, "", tps, "test"},
{scsNoPmethods, fCSFprint, "", &tps, "<*>stringer test"},
{scsMaxDepth, fCSFprint, "", dt, "{{<max>} [<max>] [<max>] map[<max>]}"},
{scsMaxDepth, fCSFdump, "", dt, "(spew_test.depthTester) {\n" +
" ic: (spew_test.indirCir1) {\n <max depth reached>\n },\n" +
" arr: ([1]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
" slice: ([]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
" m: (map[string]int) (len=1) {\n <max depth reached>\n }\n}\n"},
{scsContinue, fCSFprint, "", ts, "(stringer test) test"},
{scsContinue, fCSFdump, "", ts, "(spew_test.stringer) " +
"(len=4) (stringer test) \"test\"\n"},
{scsContinue, fCSFprint, "", te, "(error: 10) 10"},
{scsContinue, fCSFdump, "", te, "(spew_test.customError) " +
"(error: 10) 10\n"},
}
}
// TestSpew executes all of the tests described by spewTests.
func TestSpew(t *testing.T) {
initSpewTests()
t.Logf("Running %d tests", len(spewTests))
for i, test := range spewTests {
buf := new(bytes.Buffer)
switch test.f {
case fCSFdump:
test.cs.Fdump(buf, test.in)
case fCSFprint:
test.cs.Fprint(buf, test.in)
case fCSFprintf:
test.cs.Fprintf(buf, test.format, test.in)
case fCSFprintln:
test.cs.Fprintln(buf, test.in)
case fCSPrint:
b, err := redirStdout(func() { test.cs.Print(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fCSPrintln:
b, err := redirStdout(func() { test.cs.Println(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fCSSdump:
str := test.cs.Sdump(test.in)
buf.WriteString(str)
case fCSSprint:
str := test.cs.Sprint(test.in)
buf.WriteString(str)
case fCSSprintf:
str := test.cs.Sprintf(test.format, test.in)
buf.WriteString(str)
case fCSSprintln:
str := test.cs.Sprintln(test.in)
buf.WriteString(str)
case fCSErrorf:
err := test.cs.Errorf(test.format, test.in)
buf.WriteString(err.Error())
case fCSNewFormatter:
fmt.Fprintf(buf, test.format, test.cs.NewFormatter(test.in))
case fErrorf:
err := spew.Errorf(test.format, test.in)
buf.WriteString(err.Error())
case fFprint:
spew.Fprint(buf, test.in)
case fFprintln:
spew.Fprintln(buf, test.in)
case fPrint:
b, err := redirStdout(func() { spew.Print(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fPrintln:
b, err := redirStdout(func() { spew.Println(test.in) })
if err != nil {
t.Errorf("%v #%d %v", test.f, i, err)
continue
}
buf.Write(b)
case fSdump:
str := spew.Sdump(test.in)
buf.WriteString(str)
case fSprint:
str := spew.Sprint(test.in)
buf.WriteString(str)
case fSprintf:
str := spew.Sprintf(test.format, test.in)
buf.WriteString(str)
case fSprintln:
str := spew.Sprintln(test.in)
buf.WriteString(str)
default:
t.Errorf("%v #%d unrecognized function", test.f, i)
continue
}
s := buf.String()
if test.want != s {
t.Errorf("ConfigState #%d\n got: %s want: %s", i, s, test.want)
continue
}
}
}

View File

@@ -0,0 +1,82 @@
// Copyright (c) 2013 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when both cgo is supported and "-tags testcgo" is added to the go test
// command line. This code should really only be in the dumpcgo_test.go file,
// but unfortunately Go will not allow cgo in test files, so this is a
// workaround to allow cgo types to be tested. This configuration is used
// because spew itself does not require cgo to run even though it does handle
// certain cgo types specially. Rather than forcing all clients to require cgo
// and an external C compiler just to run the tests, this scheme makes them
// optional.
// +build cgo,testcgo
package testdata
/*
#include <stdint.h>
typedef unsigned char custom_uchar_t;
char *ncp = 0;
char *cp = "test";
char ca[6] = {'t', 'e', 's', 't', '2', '\0'};
unsigned char uca[6] = {'t', 'e', 's', 't', '3', '\0'};
signed char sca[6] = {'t', 'e', 's', 't', '4', '\0'};
uint8_t ui8ta[6] = {'t', 'e', 's', 't', '5', '\0'};
custom_uchar_t tuca[6] = {'t', 'e', 's', 't', '6', '\0'};
*/
import "C"
// GetCgoNullCharPointer returns a null char pointer via cgo. This is only
// used for tests.
func GetCgoNullCharPointer() interface{} {
return C.ncp
}
// GetCgoCharPointer returns a char pointer via cgo. This is only used for
// tests.
func GetCgoCharPointer() interface{} {
return C.cp
}
// GetCgoCharArray returns a char array via cgo and the array's len and cap.
// This is only used for tests.
func GetCgoCharArray() (interface{}, int, int) {
return C.ca, len(C.ca), cap(C.ca)
}
// GetCgoUnsignedCharArray returns an unsigned char array via cgo and the
// array's len and cap. This is only used for tests.
func GetCgoUnsignedCharArray() (interface{}, int, int) {
return C.uca, len(C.uca), cap(C.uca)
}
// GetCgoSignedCharArray returns a signed char array via cgo and the array's len
// and cap. This is only used for tests.
func GetCgoSignedCharArray() (interface{}, int, int) {
return C.sca, len(C.sca), cap(C.sca)
}
// GetCgoUint8tArray returns a uint8_t array via cgo and the array's len and
// cap. This is only used for tests.
func GetCgoUint8tArray() (interface{}, int, int) {
return C.ui8ta, len(C.ui8ta), cap(C.ui8ta)
}
// GetCgoTypdefedUnsignedCharArray returns a typedefed unsigned char array via
// cgo and the array's len and cap. This is only used for tests.
func GetCgoTypdefedUnsignedCharArray() (interface{}, int, int) {
return C.tuca, len(C.tuca), cap(C.tuca)
}

61
vendor/github.com/davecgh/go-spew/test_coverage.txt generated vendored Normal file
View File

@@ -0,0 +1,61 @@
github.com/davecgh/go-spew/spew/dump.go dumpState.dump 100.00% (88/88)
github.com/davecgh/go-spew/spew/format.go formatState.format 100.00% (82/82)
github.com/davecgh/go-spew/spew/format.go formatState.formatPtr 100.00% (52/52)
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpPtr 100.00% (44/44)
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpSlice 100.00% (39/39)
github.com/davecgh/go-spew/spew/common.go handleMethods 100.00% (30/30)
github.com/davecgh/go-spew/spew/common.go printHexPtr 100.00% (18/18)
github.com/davecgh/go-spew/spew/common.go unsafeReflectValue 100.00% (13/13)
github.com/davecgh/go-spew/spew/format.go formatState.constructOrigFormat 100.00% (12/12)
github.com/davecgh/go-spew/spew/dump.go fdump 100.00% (11/11)
github.com/davecgh/go-spew/spew/format.go formatState.Format 100.00% (11/11)
github.com/davecgh/go-spew/spew/common.go init 100.00% (10/10)
github.com/davecgh/go-spew/spew/common.go printComplex 100.00% (9/9)
github.com/davecgh/go-spew/spew/common.go valuesSorter.Less 100.00% (8/8)
github.com/davecgh/go-spew/spew/format.go formatState.buildDefaultFormat 100.00% (7/7)
github.com/davecgh/go-spew/spew/format.go formatState.unpackValue 100.00% (5/5)
github.com/davecgh/go-spew/spew/dump.go dumpState.indent 100.00% (4/4)
github.com/davecgh/go-spew/spew/common.go catchPanic 100.00% (4/4)
github.com/davecgh/go-spew/spew/config.go ConfigState.convertArgs 100.00% (4/4)
github.com/davecgh/go-spew/spew/spew.go convertArgs 100.00% (4/4)
github.com/davecgh/go-spew/spew/format.go newFormatter 100.00% (3/3)
github.com/davecgh/go-spew/spew/dump.go Sdump 100.00% (3/3)
github.com/davecgh/go-spew/spew/common.go printBool 100.00% (3/3)
github.com/davecgh/go-spew/spew/common.go sortValues 100.00% (3/3)
github.com/davecgh/go-spew/spew/config.go ConfigState.Sdump 100.00% (3/3)
github.com/davecgh/go-spew/spew/dump.go dumpState.unpackValue 100.00% (3/3)
github.com/davecgh/go-spew/spew/spew.go Printf 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Println 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Sprint 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Sprintf 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Sprintln 100.00% (1/1)
github.com/davecgh/go-spew/spew/common.go printFloat 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go NewDefaultConfig 100.00% (1/1)
github.com/davecgh/go-spew/spew/common.go printInt 100.00% (1/1)
github.com/davecgh/go-spew/spew/common.go printUint 100.00% (1/1)
github.com/davecgh/go-spew/spew/common.go valuesSorter.Len 100.00% (1/1)
github.com/davecgh/go-spew/spew/common.go valuesSorter.Swap 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Errorf 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprint 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintf 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintln 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Print 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Printf 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Println 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprint 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintf 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintln 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.NewFormatter 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Fdump 100.00% (1/1)
github.com/davecgh/go-spew/spew/config.go ConfigState.Dump 100.00% (1/1)
github.com/davecgh/go-spew/spew/dump.go Fdump 100.00% (1/1)
github.com/davecgh/go-spew/spew/dump.go Dump 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Fprintln 100.00% (1/1)
github.com/davecgh/go-spew/spew/format.go NewFormatter 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Errorf 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Fprint 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Fprintf 100.00% (1/1)
github.com/davecgh/go-spew/spew/spew.go Print 100.00% (1/1)
github.com/davecgh/go-spew/spew ------------------------------- 100.00% (505/505)

1
vendor/github.com/deepzz0/logd/.gitignore generated vendored Normal file
View File

@@ -0,0 +1 @@
.DS_Store

149
vendor/github.com/deepzz0/logd/README.md generated vendored Normal file
View File

@@ -0,0 +1,149 @@
# Logd
高性能日志系统每日24时自动打包前一天数据自动删除过期数据30天自动邮件告警。
#### Bechmark
```
BenchmarkLogFileChan-8 500000 2265 ns/op
BenchmarkLogFile-8 300000 4393 ns/op
BenchmarkStandardFile-8 1000000 2216 ns/op
BenchmarkLogFileChanMillion-8 1000000 2319 ns/op
BenchmarkLogFileMillion-8 1000000 4397 ns/op
BenchmarkStandardFileMillion-8 1000000 2232 ns/op
```
#### Print Level
```
Ldebug = 1 << iota //
Linfo //
Lwarn //
Lerror //
Lfatal //
logd.Printf()
logd.Print()
logd.Debugf()
logd.Debug()
logd.Infof()
logd.Info()
logd.Warnf()
logd.Warn()
logd.Errorf()
logd.Error()
logd.Fatalf()
logd.Fatal()
```
5种日志等级12种打印方法完全理解你记录日志的需求。
```
Ldebug < Linfo < Lwarn < Lerror < Lfatal
```
通过`SetLevel()`方法设置logd.SetLevel(Lwarn)系统只会打印大于等于Lwarn的日志。
> 注意logd.Printf() 和 logd.Print() 不论等级为什么都会输出,请悉知。
#### Print Option
系统提供多种格式输出选项:
```
Lasync // 异步输出日志
Ldate // like 2006/01/02
Ltime // like 15:04:05
Lmicroseconds // like 15:04:05.123123
Llongfile // like /a/b/c/d.go:23
Lshortfile // like d.go:23
LUTC // 时间utc输出
Ldaily // 按天归档
默认提供:
LstdFlags // 2006/01/02 15:04:05.123123, /a/b/c/d.go:23
```
#### 使用方法
1、默认方式
默认方式使用以下配置选项:
```
// 2006/01/02 15:04:05.123123, /a/b/c/d.go:23
Lall = Ldebug | Linfo | Lwarn | Lerror | Lfatal
LstdFlags = Lall | Ldate | Lmicroseconds | Lshortfile
```
这里会打印所有等级日志,打印日志格式为`2006/01/02 15:04:05.123123, /a/b/c/d.go:23`
可以通过`logd.SetLevel(level)`方式调整日志等级。`logd.SetOutput(writer)`输出日志到别的地方。例子:
```
package main
import (
"fmt"
"os"
"github.com/deepzz0/logd"
)
func main() {
logd.Printf("Printf: foo\n")
logd.Print("Print: foo")
logd.Debugf("Debugf: foo\n")
logd.Debug("Debug: foo")
logd.Errorf("Errorf: foo\n")
logd.Error("Error: foo")
// 改变输出等级
logd.SetLevel(logd.Lerror)
// 不论等级如何都会输出
fmt.Println("----- 改变日志等级为Lerror -----")
logd.Printf("Printf: foo\n")
logd.Print("Print: foo")
logd.Debugf("Debugf: foo\n")
logd.Debug("Debug: foo")
logd.Errorf("Errorf: foo\n")
logd.Error("Error: foo")
fmt.Println("----- 日志等级为Lerror并写入到test.log中 -----")
f, err := os.Create("./test.log")
if err != nil {
panic(err)
}
defer f.Close()
logd.SetOutput(f)
logd.Printf("Printf: foo\n")
logd.Print("Print: foo")
logd.Debugf("Debugf: foo\n")
logd.Debug("Debug: foo")
logd.Errorf("Errorf: foo\n")
logd.Error("Error: foo")
}
```
输出如下:
![logd_print](http://7xokm2.com1.z0.glb.clouddn.com/img/logd_print.png)
1、自定义配置
推荐需要将日志保存下来的用户使用。
```
Flags := Lwarn | Lerror | Lfatal | Ldate | Ltime | Lshortfile | Ldaily | Lasync
f, _ := os.OpenFile("testdata/onlyfile.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
log := New(LogOption{
Out: f,
Flag: Flags,
LogDir: "testdata",
ChannelLen: 1000,
})
```
* 输出日志等级Lwarn | Lerror | Lfatal
* 输出日志格式Ldate | Ltime | Lshortfile
* 按日归档Ldaily
* 异步输出Lasync
它会将每天的日志,采用 gzip 压缩,并保留 30 天以内的日志。自动删除 30 以前的日志。

489
vendor/github.com/deepzz0/logd/logd.go generated vendored Normal file
View File

@@ -0,0 +1,489 @@
package logd
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
)
const (
Ldebug = 1 << iota //
Linfo //
Lwarn //
Lerror //
Lfatal //
Ldate // 如2006/01/02
Ltime // 如15:04:05
Lmicroseconds // 如15:04:05.123123
Llongfile // 如:/a/b/c/d.go:23
Lshortfile // 如d.go:23
LUTC // 时间utc输出
LAsync // 异步输出日志
LDaily // 按日归档保留30天
//
// 建议标准格式为: LVEVL | FORMAT | ROTATE | ASYNC
//
Lall = Ldebug | Linfo | Lwarn | Lerror | Lfatal
// 2006/01/02 15:04:05.123123, d.go:23
LstdFlags = Lall | Ldate | Lmicroseconds | Lshortfile
// 2006/01/02 15:04:05, d.go:23
LwarnFlags = Lwarn | Lerror | Lfatal | Ldate | Ltime | Lshortfile | LDaily | LAsync
)
// 等级显示字符串
var levelMaps = map[int]string{
Ldebug: "DEBUG",
Linfo: "INFO",
Lwarn: "WARN",
Lerror: "ERROR",
Lfatal: "FATAL",
}
// 日志结构体
type Logger struct {
mu sync.Mutex
obj string // 打印日志对象
out io.Writer // 输出
in chan []byte // channel
dir string // 输出目录
flag int // 标志
mails Emailer // 告警邮件
}
// 日志配置项
type LogOption struct {
Out io.Writer // 输出writer
LogDir string // 日志输出目录,为空不输出到文件
ChannelLen int // channel
Flag int // 标志位
Mails Emailer // 告警邮件
}
// 新建日志打印器
func New(option LogOption) *Logger {
wd, _ := os.Getwd()
index := strings.LastIndex(wd, "/")
logger := &Logger{
obj: wd[index+1:],
out: option.Out,
in: make(chan []byte, option.ChannelLen),
dir: option.LogDir,
flag: option.Flag,
mails: option.Mails,
}
if logger.flag|LAsync != 0 {
go logger.receive()
}
return logger
}
func (l *Logger) receive() {
today := time.Now()
var file *os.File
var err error
for data := range l.in {
if l.dir != "" && (file == nil || today.Day() != time.Now().Day()) {
l.mu.Lock()
today = time.Now()
file, err = os.OpenFile(fmt.Sprintf("%s/%s_%s.log", l.dir, l.obj, today.Format("2006-01-02")), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
if err != nil {
panic(err)
}
l.mu.Unlock()
if l.flag&LDaily != 0 {
go l.rotate(today)
}
}
if file != nil {
file.Write(data)
}
if l.out != nil {
l.out.Write(data)
}
}
}
// 压缩
func (l *Logger) rotate(t time.Time) {
filepath.Walk(l.dir, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if int(t.Sub(f.ModTime()).Hours()) > 24 {
if strings.HasSuffix(f.Name(), ".log") {
cmd := exec.Command("gzip", path)
err = cmd.Run()
if err != nil {
return err
}
}
}
if int(t.Sub(f.ModTime()).Hours()) > 24*30 {
if err := os.Remove(path); err != nil {
return err
}
}
return nil
})
}
// 日志基本格式: date, time(hour:minute:second:microsecond), level, module, shortfile:line, <content>
func (l *Logger) Output(lvl int, calldepth int, content string) error {
_, file, line, ok := runtime.Caller(calldepth)
if !ok {
return nil
}
var buf []byte
l.formatHeader(&buf, lvl, time.Now(), file, line)
buf = append(buf, content...)
if l.mails != nil && lvl >= Lwarn {
go l.mails.SendMail(l.obj, buf)
}
// 异步输出
if l.flag&LAsync != 0 {
l.in <- buf
} else {
l.mu.Lock()
defer l.mu.Unlock()
l.out.Write(buf)
}
return nil
}
// 整理日志header
func (l *Logger) formatHeader(buf *[]byte, lvl int, t time.Time, file string, line int) {
if l.flag&LUTC != 0 {
t = t.UTC()
}
if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
if l.flag&Ldate != 0 {
year, month, day := t.Date()
itoa(buf, year, 4)
*buf = append(*buf, '/')
itoa(buf, int(month), 2)
*buf = append(*buf, '/')
itoa(buf, day, 2)
*buf = append(*buf, ' ')
}
if l.flag&(Ltime|Lmicroseconds) != 0 {
hour, min, sec := t.Clock()
itoa(buf, hour, 2)
*buf = append(*buf, ':')
itoa(buf, min, 2)
*buf = append(*buf, ':')
itoa(buf, sec, 2)
if l.flag&Lmicroseconds != 0 {
*buf = append(*buf, '.')
itoa(buf, t.Nanosecond()/1e3, 6)
}
*buf = append(*buf, ' ')
}
}
*buf = append(*buf, getColorLevel(levelMaps[lvl])...)
*buf = append(*buf, ' ')
if l.flag&(Lshortfile|Llongfile) != 0 {
if l.flag&Lshortfile != 0 {
short := file
for i := len(file) - 1; i > 0; i-- {
if file[i] == '/' {
short = file[i+1:]
break
}
}
file = short
}
*buf = append(*buf, file...)
*buf = append(*buf, ':')
itoa(buf, line, -1)
*buf = append(*buf, ":"...)
}
}
// 等待flush channel
func (l *Logger) WaitFlush() {
for {
if len(l.in) > 0 {
time.Sleep(time.Nanosecond * 50)
} else {
break
}
}
}
// print
func (l *Logger) Printf(format string, v ...interface{}) {
l.Output(Linfo, 2, fmt.Sprintf(format, v...))
}
func (l *Logger) Print(v ...interface{}) {
l.Output(Linfo, 2, fmt.Sprintf(smartFormat(v...), v...))
}
// debug
func (l *Logger) Debugf(format string, v ...interface{}) {
if Ldebug&l.flag != 0 {
l.Output(Ldebug, 2, fmt.Sprintf(format, v...))
}
}
func (l *Logger) Debug(v ...interface{}) {
if Ldebug&l.flag != 0 {
l.Output(Ldebug, 2, fmt.Sprintf(smartFormat(v...), v...))
}
}
// info
func (l *Logger) Infof(format string, v ...interface{}) {
if Linfo&l.flag != 0 {
l.Output(Linfo, 2, fmt.Sprintf(format, v...))
}
}
func (l *Logger) Info(v ...interface{}) {
if Linfo&l.flag != 0 {
l.Output(Linfo, 2, fmt.Sprintf(smartFormat(v...), v...))
}
}
// warn
func (l *Logger) Warnf(format string, v ...interface{}) {
if Lwarn&l.flag != 0 {
l.Output(Lwarn, 2, fmt.Sprintf(format, v...))
}
}
func (l *Logger) Warn(v ...interface{}) {
if Lwarn&l.flag != 0 {
l.Output(Lwarn, 2, fmt.Sprintf(smartFormat(v...), v...))
}
}
// error
func (l *Logger) Errorf(format string, v ...interface{}) {
if Lerror&l.flag != 0 {
l.Output(Lerror, 2, fmt.Sprintf(format, v...)+CallerStack())
}
}
func (l *Logger) Error(v ...interface{}) {
if Lerror&l.flag != 0 {
l.Output(Lerror, 2, fmt.Sprintf(smartFormat(v...), v...)+CallerStack())
}
}
// fatal
func (l *Logger) Fatalf(format string, v ...interface{}) {
l.Output(Lfatal, 2, fmt.Sprintf(format, v...))
os.Exit(1)
}
func (l *Logger) Fatal(v ...interface{}) {
l.Output(Lfatal, 2, fmt.Sprintf(smartFormat(v...), v...))
os.Exit(1)
}
func (l *Logger) Breakpoint() {
l.Output(Ldebug, 3, fmt.Sprintln("breakpoint"))
}
// 设置日志目录
func (l *Logger) SetLogDir(dir string) {
l.mu.Lock()
defer l.mu.Unlock()
l.dir = dir
}
// 设置日志对象名称
func (l *Logger) SetObj(obj string) {
l.mu.Lock()
defer l.mu.Unlock()
l.obj = obj
}
// 改变日志输出writer
func (l *Logger) SetOutput(out io.Writer) {
l.mu.Lock()
defer l.mu.Unlock()
l.out = out
}
// 设置日志等级
func (l *Logger) SetLevel(lvl int) {
l.mu.Lock()
defer l.mu.Unlock()
var i uint
for i = 0; i < uint(len(levelMaps)); i++ {
if lvl&1 != 0 {
break
}
lvl >>= 1
}
l.flag = (l.flag >> i) | (Lall >> i)
l.flag <<= i
}
// standard wrapper
var Std = New(LogOption{Out: os.Stdout, Flag: LstdFlags})
func Printf(format string, v ...interface{}) {
Std.Output(Linfo, 2, fmt.Sprintf(format, v...))
}
func Print(v ...interface{}) {
Std.Output(Linfo, 2, fmt.Sprintf(smartFormat(v...), v...))
}
func Debugf(format string, v ...interface{}) {
if Ldebug&Std.flag != 0 {
Std.Output(Ldebug, 2, fmt.Sprintf(format, v...))
}
}
func Debug(v ...interface{}) {
if Ldebug&Std.flag != 0 {
Std.Output(Ldebug, 2, fmt.Sprintf(smartFormat(v...), v...))
}
}
func Infof(format string, v ...interface{}) {
if Linfo&Std.flag != 0 {
Std.Output(Linfo, 2, fmt.Sprintf(format, v...))
}
}
func Info(v ...interface{}) {
if Linfo&Std.flag != 0 {
Std.Output(Linfo, 2, fmt.Sprintf(smartFormat(v...), v...))
}
}
func Warnf(format string, v ...interface{}) {
if Lwarn&Std.flag != 0 {
Std.Output(Lwarn, 2, fmt.Sprintf(format, v...))
}
}
func Warn(v ...interface{}) {
if Lwarn&Std.flag != 0 {
Std.Output(Lwarn, 2, fmt.Sprintf(smartFormat(v...), v...))
}
}
func Errorf(format string, v ...interface{}) {
if Lerror&Std.flag != 0 {
Std.Output(Lerror, 2, fmt.Sprintf(format, v...)+CallerStack())
}
}
func Error(v ...interface{}) {
if Lerror&Std.flag != 0 {
Std.Output(Lerror, 2, fmt.Sprintf(smartFormat(v...), v...)+CallerStack())
}
}
func Fatalf(format string, v ...interface{}) {
Std.Output(Lfatal, 2, fmt.Sprintf(format, v...)+CallerStack())
os.Exit(1)
}
func Fatal(v ...interface{}) {
Std.Output(Lfatal, 2, fmt.Sprintf(smartFormat(v...), v...)+CallerStack())
os.Exit(1)
}
func Breakpoint() {
Std.Breakpoint()
}
func SetLevel(lvl int) {
Std.SetLevel(lvl)
}
func SetOutput(w io.Writer) {
Std.SetOutput(w)
}
func SetObj(obj string) {
Std.SetObj(obj)
}
///////////////////////////////////////////////////////////////////////////////////////////
func smartFormat(v ...interface{}) string {
format := ""
for i := 0; i < len(v); i++ {
format += " %v"
}
format += "\n"
return format
}
// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
func itoa(buf *[]byte, i int, wid int) {
// Assemble decimal in reverse order.
var b [20]byte
bp := len(b) - 1
for i >= 10 || wid > 1 {
wid--
q := i / 10
b[bp] = byte('0' + i - q*10)
bp--
i = q
}
// i < 10
b[bp] = byte('0' + i)
*buf = append(*buf, b[bp:]...)
}
const (
Gray = uint8(iota + 90)
Red
Green
Yellow
Blue
Magenta
)
// getColorLevel returns colored level string by given level.
func getColorLevel(level string) string {
level = strings.ToUpper(level)
switch level {
case "DEBUG":
return fmt.Sprintf("\033[%dm[%5s]\033[0m", Green, level)
case "INFO":
return fmt.Sprintf("\033[%dm[%5s]\033[0m", Blue, level)
case "WARN":
return fmt.Sprintf("\033[%dm[%5s]\033[0m", Magenta, level)
case "ERROR":
return fmt.Sprintf("\033[%dm[%5s]\033[0m", Yellow, level)
case "FATAL":
return fmt.Sprintf("\033[%dm[%5s]\033[0m", Red, level)
default:
return level
}
}
func CallerStack() string {
var caller_str string
for skip := 2; ; skip++ {
// 获取调用者的信息
pc, file, line, ok := runtime.Caller(skip)
if !ok {
break
}
func_name := runtime.FuncForPC(pc).Name()
caller_str += "Func : " + func_name + "\nFile:" + file + ":" + fmt.Sprint(line) + "\n"
}
return caller_str
}

107
vendor/github.com/deepzz0/logd/logd_test.go generated vendored Normal file
View File

@@ -0,0 +1,107 @@
package logd
import (
"log"
"os"
"testing"
)
func TestLog(t *testing.T) {
Printf("Printf: foo\n")
Print("Print: foo")
SetLevel(Ldebug)
Debugf("Debugf: foo\n")
Debug("Debug: foo")
Infof("Infof: foo\n")
Info("Info: foo")
Errorf("Errorf: foo\n")
Error("Error: foo")
SetLevel(Lerror)
Debugf("Debugf: foo\n")
Debug("Debug: foo")
Infof("Infof: foo\n")
Info("Info: foo")
Errorf("Errorf: foo\n")
Error("Error: foo")
}
func BenchmarkLogFileChan(b *testing.B) {
log := New(LogOption{
Flag: LAsync | Ldate | Ltime | Lshortfile,
LogDir: "testdata",
ChannelLen: 1000,
})
for i := 0; i < b.N; i++ {
log.Print("testing this is a testing about benchmark")
}
log.WaitFlush()
}
func BenchmarkLogFile(b *testing.B) {
f, _ := os.OpenFile("testdata/onlyfile.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
log := New(LogOption{
Out: f,
Flag: Ldate | Ltime | Lshortfile,
LogDir: "testdata",
ChannelLen: 1000,
})
for i := 0; i < b.N; i++ {
log.Print("testing this is a testing about benchmark")
}
log.WaitFlush()
}
func BenchmarkStandardFile(b *testing.B) {
f, _ := os.OpenFile("testdata/logfile.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
log := log.New(f, "", log.LstdFlags)
for i := 0; i < b.N; i++ {
log.Print("testing this is a testing about benchmark")
}
}
func BenchmarkLogFileChanMillion(b *testing.B) {
log := New(LogOption{
Flag: LAsync | Ldate | Ltime | Lshortfile,
LogDir: "testdata",
ChannelLen: 1000,
})
b.N = 1000000
for i := 0; i < b.N; i++ {
log.Print("testing this is a testing about benchmark")
}
log.WaitFlush()
}
func BenchmarkLogFileMillion(b *testing.B) {
f, _ := os.OpenFile("testdata/onlyfilemillion.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
log := New(LogOption{
Out: f,
Flag: Ldate | Ltime | Lshortfile,
LogDir: "testdata",
ChannelLen: 1000,
})
b.N = 1000000
for i := 0; i < b.N; i++ {
log.Print("testing this is a testing about benchmark")
}
log.WaitFlush()
}
func BenchmarkStandardFileMillion(b *testing.B) {
f, _ := os.OpenFile("testdata/logfilemillion.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
log := log.New(f, "", log.LstdFlags)
b.N = 1000000
for i := 0; i < b.N; i++ {
log.Print("testing this is a testing about benchmark")
}
}

80
vendor/github.com/deepzz0/logd/mail.go generated vendored Normal file
View File

@@ -0,0 +1,80 @@
package logd
import (
"crypto/tls"
"fmt"
"net/smtp"
"strings"
)
type Emailer interface {
SendMail(fromname string, msg []byte) error
}
type Smtp struct {
From string // 发件箱number@qq.com
Key string // 发件密钥peerdmnoqirqbiaa
Host string // 主机地址smtp.example.com
Port string // 主机端口465
To []string // 发送给object@163.com
Subject string // 标题:警告邮件[goblog]
}
func (s *Smtp) SendMail(fromname string, msg []byte) error {
// 新建连接
conn, err := tls.Dial("tcp", s.Host+":"+s.Port, nil)
if err != nil {
return err
}
// 新建客户端
client, err := smtp.NewClient(conn, s.Host)
if err != nil {
return err
}
// 获取授权
auth := smtp.PlainAuth("", s.From, s.Key, s.Host)
if err = client.Auth(auth); err != nil {
return err
}
// 向服务器发送MAIL命令
if err = client.Mail(s.From); err != nil {
return err
}
// 准备数据
str := fmt.Sprint(
"To:", strings.Join(s.To, ","),
"\r\nFrom:", fmt.Sprintf("%s<%s>", fromname, s.From),
"\r\nSubject:", s.Subject,
"\r\n", "Content-Type:text/plain;charset=UTF-8",
"\r\n\r\n",
)
data := make([]byte, len(str)+len(msg))
copy(data, []byte(str))
copy(data[len(str):], msg)
// RCPT
for _, d := range s.To {
if err := client.Rcpt(d); err != nil {
return err
}
}
// 获取WriteCloser
wc, err := client.Data()
if err != nil {
return err
}
// 写入数据
_, err = wc.Write(data)
if err != nil {
return err
}
wc.Close()
return client.Quit()
}

20
vendor/github.com/deepzz0/logd/mail_test.go generated vendored Normal file
View File

@@ -0,0 +1,20 @@
// Package logd provides ...
package logd
import "testing"
func TestSmtpSendMail(t *testing.T) {
s := &Smtp{
From: "120735581@qq.com",
Key: "peerdmnoqirqbiaa",
Host: "smtp.qq.com",
Port: "465",
To: []string{"a120735581@foxmail.com"},
Subject: "test email from logd",
}
err := s.SendMail("test", []byte("hello world"))
if err != nil {
t.Error(err)
}
}

View File

@@ -0,0 +1,5 @@
Ask questions at
[StackOverflow](https://stackoverflow.com/questions/ask?tags=go+redis).
[Open an issue](https://github.com/garyburd/redigo/issues/new) to discuss your
plans before doing any work on Redigo.

View File

@@ -0,0 +1 @@
Ask questions at https://stackoverflow.com/questions/ask?tags=go+redis

19
vendor/github.com/garyburd/redigo/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,19 @@
language: go
sudo: false
services:
- redis-server
go:
- 1.4
- 1.5
- 1.6
- 1.7
- 1.8
- 1.9
- tip
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d .)
- go vet $(go list ./... | grep -v /vendor/)
- go test -v -race ./...

175
vendor/github.com/garyburd/redigo/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,175 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

51
vendor/github.com/garyburd/redigo/README.markdown generated vendored Normal file
View File

@@ -0,0 +1,51 @@
Redigo
======
[![Build Status](https://travis-ci.org/garyburd/redigo.svg?branch=master)](https://travis-ci.org/garyburd/redigo)
[![GoDoc](https://godoc.org/github.com/garyburd/redigo/redis?status.svg)](https://godoc.org/github.com/garyburd/redigo/redis)
Redigo is a [Go](http://golang.org/) client for the [Redis](http://redis.io/) database.
Features
-------
* A [Print-like](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Executing_Commands) API with support for all Redis commands.
* [Pipelining](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Pipelining), including pipelined transactions.
* [Publish/Subscribe](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Publish_and_Subscribe).
* [Connection pooling](http://godoc.org/github.com/garyburd/redigo/redis#Pool).
* [Script helper type](http://godoc.org/github.com/garyburd/redigo/redis#Script) with optimistic use of EVALSHA.
* [Helper functions](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Reply_Helpers) for working with command replies.
Documentation
-------------
- [API Reference](http://godoc.org/github.com/garyburd/redigo/redis)
- [FAQ](https://github.com/garyburd/redigo/wiki/FAQ)
- [Examples](https://godoc.org/github.com/garyburd/redigo/redis#pkg-examples)
Installation
------------
Install Redigo using the "go get" command:
go get github.com/garyburd/redigo/redis
The Go distribution is Redigo's only dependency.
Related Projects
----------------
- [rafaeljusto/redigomock](https://godoc.org/github.com/rafaeljusto/redigomock) - A mock library for Redigo.
- [chasex/redis-go-cluster](https://github.com/chasex/redis-go-cluster) - A Redis cluster client implementation.
- [FZambia/go-sentinel](https://github.com/FZambia/go-sentinel) - Redis Sentinel support for Redigo
- [PuerkitoBio/redisc](https://github.com/PuerkitoBio/redisc) - Redis Cluster client built on top of Redigo
Contributing
------------
See [CONTRIBUTING.md](https://github.com/garyburd/redigo/blob/master/.github/CONTRIBUTING.md).
License
-------
Redigo is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).

View File

@@ -0,0 +1,54 @@
// Copyright 2014 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package internal // import "github.com/garyburd/redigo/internal"
import (
"strings"
)
const (
WatchState = 1 << iota
MultiState
SubscribeState
MonitorState
)
type CommandInfo struct {
Set, Clear int
}
var commandInfos = map[string]CommandInfo{
"WATCH": {Set: WatchState},
"UNWATCH": {Clear: WatchState},
"MULTI": {Set: MultiState},
"EXEC": {Clear: WatchState | MultiState},
"DISCARD": {Clear: WatchState | MultiState},
"PSUBSCRIBE": {Set: SubscribeState},
"SUBSCRIBE": {Set: SubscribeState},
"MONITOR": {Set: MonitorState},
}
func init() {
for n, ci := range commandInfos {
commandInfos[strings.ToLower(n)] = ci
}
}
func LookupCommandInfo(commandName string) CommandInfo {
if ci, ok := commandInfos[commandName]; ok {
return ci
}
return commandInfos[strings.ToUpper(commandName)]
}

View File

@@ -0,0 +1,27 @@
package internal
import "testing"
func TestLookupCommandInfo(t *testing.T) {
for _, n := range []string{"watch", "WATCH", "wAtch"} {
if LookupCommandInfo(n) == (CommandInfo{}) {
t.Errorf("LookupCommandInfo(%q) = CommandInfo{}, expected non-zero value", n)
}
}
}
func benchmarkLookupCommandInfo(b *testing.B, names ...string) {
for i := 0; i < b.N; i++ {
for _, c := range names {
LookupCommandInfo(c)
}
}
}
func BenchmarkLookupCommandInfoCorrectCase(b *testing.B) {
benchmarkLookupCommandInfo(b, "watch", "WATCH", "monitor", "MONITOR")
}
func BenchmarkLookupCommandInfoMixedCase(b *testing.B) {
benchmarkLookupCommandInfo(b, "wAtch", "WeTCH", "monItor", "MONiTOR")
}

View File

@@ -0,0 +1,68 @@
// Copyright 2014 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// Package redistest contains utilities for writing Redigo tests.
package redistest
import (
"errors"
"time"
"github.com/garyburd/redigo/redis"
)
type testConn struct {
redis.Conn
}
func (t testConn) Close() error {
_, err := t.Conn.Do("SELECT", "9")
if err != nil {
return nil
}
_, err = t.Conn.Do("FLUSHDB")
if err != nil {
return err
}
return t.Conn.Close()
}
// Dial dials the local Redis server and selects database 9. To prevent
// stomping on real data, DialTestDB fails if database 9 contains data. The
// returned connection flushes database 9 on close.
func Dial() (redis.Conn, error) {
c, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second)
if err != nil {
return nil, err
}
_, err = c.Do("SELECT", "9")
if err != nil {
c.Close()
return nil, err
}
n, err := redis.Int(c.Do("DBSIZE"))
if err != nil {
c.Close()
return nil, err
}
if n != 0 {
c.Close()
return nil, errors.New("database #9 is not empty, test can not continue")
}
return testConn{c}, nil
}

635
vendor/github.com/garyburd/redigo/redis/conn.go generated vendored Normal file
View File

@@ -0,0 +1,635 @@
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package redis
import (
"bufio"
"bytes"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"net/url"
"regexp"
"strconv"
"sync"
"time"
)
// conn is the low-level implementation of Conn
type conn struct {
// Shared
mu sync.Mutex
pending int
err error
conn net.Conn
// Read
readTimeout time.Duration
br *bufio.Reader
// Write
writeTimeout time.Duration
bw *bufio.Writer
// Scratch space for formatting argument length.
// '*' or '$', length, "\r\n"
lenScratch [32]byte
// Scratch space for formatting integers and floats.
numScratch [40]byte
}
// DialTimeout acts like Dial but takes timeouts for establishing the
// connection to the server, writing a command and reading a reply.
//
// Deprecated: Use Dial with options instead.
func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) {
return Dial(network, address,
DialConnectTimeout(connectTimeout),
DialReadTimeout(readTimeout),
DialWriteTimeout(writeTimeout))
}
// DialOption specifies an option for dialing a Redis server.
type DialOption struct {
f func(*dialOptions)
}
type dialOptions struct {
readTimeout time.Duration
writeTimeout time.Duration
dial func(network, addr string) (net.Conn, error)
db int
password string
useTLS bool
skipVerify bool
tlsConfig *tls.Config
}
// DialReadTimeout specifies the timeout for reading a single command reply.
func DialReadTimeout(d time.Duration) DialOption {
return DialOption{func(do *dialOptions) {
do.readTimeout = d
}}
}
// DialWriteTimeout specifies the timeout for writing a single command.
func DialWriteTimeout(d time.Duration) DialOption {
return DialOption{func(do *dialOptions) {
do.writeTimeout = d
}}
}
// DialConnectTimeout specifies the timeout for connecting to the Redis server.
func DialConnectTimeout(d time.Duration) DialOption {
return DialOption{func(do *dialOptions) {
dialer := net.Dialer{Timeout: d}
do.dial = dialer.Dial
}}
}
// DialNetDial specifies a custom dial function for creating TCP
// connections. If this option is left out, then net.Dial is
// used. DialNetDial overrides DialConnectTimeout.
func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {
return DialOption{func(do *dialOptions) {
do.dial = dial
}}
}
// DialDatabase specifies the database to select when dialing a connection.
func DialDatabase(db int) DialOption {
return DialOption{func(do *dialOptions) {
do.db = db
}}
}
// DialPassword specifies the password to use when connecting to
// the Redis server.
func DialPassword(password string) DialOption {
return DialOption{func(do *dialOptions) {
do.password = password
}}
}
// DialTLSConfig specifies the config to use when a TLS connection is dialed.
// Has no effect when not dialing a TLS connection.
func DialTLSConfig(c *tls.Config) DialOption {
return DialOption{func(do *dialOptions) {
do.tlsConfig = c
}}
}
// DialTLSSkipVerify disables server name verification when connecting over
// TLS. Has no effect when not dialing a TLS connection.
func DialTLSSkipVerify(skip bool) DialOption {
return DialOption{func(do *dialOptions) {
do.skipVerify = skip
}}
}
// DialUseTLS specifies whether TLS should be used when connecting to the
// server. This option is ignore by DialURL.
func DialUseTLS(useTLS bool) DialOption {
return DialOption{func(do *dialOptions) {
do.useTLS = useTLS
}}
}
// Dial connects to the Redis server at the given network and
// address using the specified options.
func Dial(network, address string, options ...DialOption) (Conn, error) {
do := dialOptions{
dial: net.Dial,
}
for _, option := range options {
option.f(&do)
}
netConn, err := do.dial(network, address)
if err != nil {
return nil, err
}
if do.useTLS {
tlsConfig := cloneTLSClientConfig(do.tlsConfig, do.skipVerify)
if tlsConfig.ServerName == "" {
host, _, err := net.SplitHostPort(address)
if err != nil {
netConn.Close()
return nil, err
}
tlsConfig.ServerName = host
}
tlsConn := tls.Client(netConn, tlsConfig)
if err := tlsConn.Handshake(); err != nil {
netConn.Close()
return nil, err
}
netConn = tlsConn
}
c := &conn{
conn: netConn,
bw: bufio.NewWriter(netConn),
br: bufio.NewReader(netConn),
readTimeout: do.readTimeout,
writeTimeout: do.writeTimeout,
}
if do.password != "" {
if _, err := c.Do("AUTH", do.password); err != nil {
netConn.Close()
return nil, err
}
}
if do.db != 0 {
if _, err := c.Do("SELECT", do.db); err != nil {
netConn.Close()
return nil, err
}
}
return c, nil
}
var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`)
// DialURL connects to a Redis server at the given URL using the Redis
// URI scheme. URLs should follow the draft IANA specification for the
// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis).
func DialURL(rawurl string, options ...DialOption) (Conn, error) {
u, err := url.Parse(rawurl)
if err != nil {
return nil, err
}
if u.Scheme != "redis" && u.Scheme != "rediss" {
return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme)
}
// As per the IANA draft spec, the host defaults to localhost and
// the port defaults to 6379.
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
// assume port is missing
host = u.Host
port = "6379"
}
if host == "" {
host = "localhost"
}
address := net.JoinHostPort(host, port)
if u.User != nil {
password, isSet := u.User.Password()
if isSet {
options = append(options, DialPassword(password))
}
}
match := pathDBRegexp.FindStringSubmatch(u.Path)
if len(match) == 2 {
db := 0
if len(match[1]) > 0 {
db, err = strconv.Atoi(match[1])
if err != nil {
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
}
}
if db != 0 {
options = append(options, DialDatabase(db))
}
} else if u.Path != "" {
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
}
options = append(options, DialUseTLS(u.Scheme == "rediss"))
return Dial("tcp", address, options...)
}
// NewConn returns a new Redigo connection for the given net connection.
func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn {
return &conn{
conn: netConn,
bw: bufio.NewWriter(netConn),
br: bufio.NewReader(netConn),
readTimeout: readTimeout,
writeTimeout: writeTimeout,
}
}
func (c *conn) Close() error {
c.mu.Lock()
err := c.err
if c.err == nil {
c.err = errors.New("redigo: closed")
err = c.conn.Close()
}
c.mu.Unlock()
return err
}
func (c *conn) fatal(err error) error {
c.mu.Lock()
if c.err == nil {
c.err = err
// Close connection to force errors on subsequent calls and to unblock
// other reader or writer.
c.conn.Close()
}
c.mu.Unlock()
return err
}
func (c *conn) Err() error {
c.mu.Lock()
err := c.err
c.mu.Unlock()
return err
}
func (c *conn) writeLen(prefix byte, n int) error {
c.lenScratch[len(c.lenScratch)-1] = '\n'
c.lenScratch[len(c.lenScratch)-2] = '\r'
i := len(c.lenScratch) - 3
for {
c.lenScratch[i] = byte('0' + n%10)
i -= 1
n = n / 10
if n == 0 {
break
}
}
c.lenScratch[i] = prefix
_, err := c.bw.Write(c.lenScratch[i:])
return err
}
func (c *conn) writeString(s string) error {
c.writeLen('$', len(s))
c.bw.WriteString(s)
_, err := c.bw.WriteString("\r\n")
return err
}
func (c *conn) writeBytes(p []byte) error {
c.writeLen('$', len(p))
c.bw.Write(p)
_, err := c.bw.WriteString("\r\n")
return err
}
func (c *conn) writeInt64(n int64) error {
return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10))
}
func (c *conn) writeFloat64(n float64) error {
return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64))
}
func (c *conn) writeCommand(cmd string, args []interface{}) error {
c.writeLen('*', 1+len(args))
if err := c.writeString(cmd); err != nil {
return err
}
for _, arg := range args {
if err := c.writeArg(arg, true); err != nil {
return err
}
}
return nil
}
func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) {
switch arg := arg.(type) {
case string:
return c.writeString(arg)
case []byte:
return c.writeBytes(arg)
case int:
return c.writeInt64(int64(arg))
case int64:
return c.writeInt64(arg)
case float64:
return c.writeFloat64(arg)
case bool:
if arg {
return c.writeString("1")
} else {
return c.writeString("0")
}
case nil:
return c.writeString("")
case Argument:
if argumentTypeOK {
return c.writeArg(arg.RedisArg(), false)
}
// See comment in default clause below.
var buf bytes.Buffer
fmt.Fprint(&buf, arg)
return c.writeBytes(buf.Bytes())
default:
// This default clause is intended to handle builtin numeric types.
// The function should return an error for other types, but this is not
// done for compatibility with previous versions of the package.
var buf bytes.Buffer
fmt.Fprint(&buf, arg)
return c.writeBytes(buf.Bytes())
}
}
type protocolError string
func (pe protocolError) Error() string {
return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe))
}
func (c *conn) readLine() ([]byte, error) {
p, err := c.br.ReadSlice('\n')
if err == bufio.ErrBufferFull {
return nil, protocolError("long response line")
}
if err != nil {
return nil, err
}
i := len(p) - 2
if i < 0 || p[i] != '\r' {
return nil, protocolError("bad response line terminator")
}
return p[:i], nil
}
// parseLen parses bulk string and array lengths.
func parseLen(p []byte) (int, error) {
if len(p) == 0 {
return -1, protocolError("malformed length")
}
if p[0] == '-' && len(p) == 2 && p[1] == '1' {
// handle $-1 and $-1 null replies.
return -1, nil
}
var n int
for _, b := range p {
n *= 10
if b < '0' || b > '9' {
return -1, protocolError("illegal bytes in length")
}
n += int(b - '0')
}
return n, nil
}
// parseInt parses an integer reply.
func parseInt(p []byte) (interface{}, error) {
if len(p) == 0 {
return 0, protocolError("malformed integer")
}
var negate bool
if p[0] == '-' {
negate = true
p = p[1:]
if len(p) == 0 {
return 0, protocolError("malformed integer")
}
}
var n int64
for _, b := range p {
n *= 10
if b < '0' || b > '9' {
return 0, protocolError("illegal bytes in length")
}
n += int64(b - '0')
}
if negate {
n = -n
}
return n, nil
}
var (
okReply interface{} = "OK"
pongReply interface{} = "PONG"
)
func (c *conn) readReply() (interface{}, error) {
line, err := c.readLine()
if err != nil {
return nil, err
}
if len(line) == 0 {
return nil, protocolError("short response line")
}
switch line[0] {
case '+':
switch {
case len(line) == 3 && line[1] == 'O' && line[2] == 'K':
// Avoid allocation for frequent "+OK" response.
return okReply, nil
case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G':
// Avoid allocation in PING command benchmarks :)
return pongReply, nil
default:
return string(line[1:]), nil
}
case '-':
return Error(string(line[1:])), nil
case ':':
return parseInt(line[1:])
case '$':
n, err := parseLen(line[1:])
if n < 0 || err != nil {
return nil, err
}
p := make([]byte, n)
_, err = io.ReadFull(c.br, p)
if err != nil {
return nil, err
}
if line, err := c.readLine(); err != nil {
return nil, err
} else if len(line) != 0 {
return nil, protocolError("bad bulk string format")
}
return p, nil
case '*':
n, err := parseLen(line[1:])
if n < 0 || err != nil {
return nil, err
}
r := make([]interface{}, n)
for i := range r {
r[i], err = c.readReply()
if err != nil {
return nil, err
}
}
return r, nil
}
return nil, protocolError("unexpected response line")
}
func (c *conn) Send(cmd string, args ...interface{}) error {
c.mu.Lock()
c.pending += 1
c.mu.Unlock()
if c.writeTimeout != 0 {
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
}
if err := c.writeCommand(cmd, args); err != nil {
return c.fatal(err)
}
return nil
}
func (c *conn) Flush() error {
if c.writeTimeout != 0 {
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
}
if err := c.bw.Flush(); err != nil {
return c.fatal(err)
}
return nil
}
func (c *conn) Receive() (reply interface{}, err error) {
if c.readTimeout != 0 {
c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
}
if reply, err = c.readReply(); err != nil {
return nil, c.fatal(err)
}
// When using pub/sub, the number of receives can be greater than the
// number of sends. To enable normal use of the connection after
// unsubscribing from all channels, we do not decrement pending to a
// negative value.
//
// The pending field is decremented after the reply is read to handle the
// case where Receive is called before Send.
c.mu.Lock()
if c.pending > 0 {
c.pending -= 1
}
c.mu.Unlock()
if err, ok := reply.(Error); ok {
return nil, err
}
return
}
func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
c.mu.Lock()
pending := c.pending
c.pending = 0
c.mu.Unlock()
if cmd == "" && pending == 0 {
return nil, nil
}
if c.writeTimeout != 0 {
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
}
if cmd != "" {
if err := c.writeCommand(cmd, args); err != nil {
return nil, c.fatal(err)
}
}
if err := c.bw.Flush(); err != nil {
return nil, c.fatal(err)
}
if c.readTimeout != 0 {
c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
}
if cmd == "" {
reply := make([]interface{}, pending)
for i := range reply {
r, e := c.readReply()
if e != nil {
return nil, c.fatal(e)
}
reply[i] = r
}
return reply, nil
}
var err error
var reply interface{}
for i := 0; i <= pending; i++ {
var e error
if reply, e = c.readReply(); e != nil {
return nil, c.fatal(e)
}
if e, ok := reply.(Error); ok && err == nil {
err = e
}
}
return reply, err
}

823
vendor/github.com/garyburd/redigo/redis/conn_test.go generated vendored Normal file
View File

@@ -0,0 +1,823 @@
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package redis_test
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"math"
"net"
"os"
"reflect"
"strings"
"testing"
"time"
"github.com/garyburd/redigo/redis"
)
type testConn struct {
io.Reader
io.Writer
}
func (*testConn) Close() error { return nil }
func (*testConn) LocalAddr() net.Addr { return nil }
func (*testConn) RemoteAddr() net.Addr { return nil }
func (*testConn) SetDeadline(t time.Time) error { return nil }
func (*testConn) SetReadDeadline(t time.Time) error { return nil }
func (*testConn) SetWriteDeadline(t time.Time) error { return nil }
func dialTestConn(r string, w io.Writer) redis.DialOption {
return redis.DialNetDial(func(network, addr string) (net.Conn, error) {
return &testConn{Reader: strings.NewReader(r), Writer: w}, nil
})
}
type tlsTestConn struct {
net.Conn
done chan struct{}
}
func (c *tlsTestConn) Close() error {
c.Conn.Close()
<-c.done
return nil
}
func dialTestConnTLS(r string, w io.Writer) redis.DialOption {
return redis.DialNetDial(func(network, addr string) (net.Conn, error) {
client, server := net.Pipe()
tlsServer := tls.Server(server, &serverTLSConfig)
go io.Copy(tlsServer, strings.NewReader(r))
done := make(chan struct{})
go func() {
io.Copy(w, tlsServer)
close(done)
}()
return &tlsTestConn{Conn: client, done: done}, nil
})
}
type durationArg struct {
time.Duration
}
func (t durationArg) RedisArg() interface{} {
return t.Seconds()
}
type recursiveArg int
func (v recursiveArg) RedisArg() interface{} { return v }
var writeTests = []struct {
args []interface{}
expected string
}{
{
[]interface{}{"SET", "key", "value"},
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n",
},
{
[]interface{}{"SET", "key", "value"},
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n",
},
{
[]interface{}{"SET", "key", byte(100)},
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n",
},
{
[]interface{}{"SET", "key", 100},
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n",
},
{
[]interface{}{"SET", "key", int64(math.MinInt64)},
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$20\r\n-9223372036854775808\r\n",
},
{
[]interface{}{"SET", "key", float64(1349673917.939762)},
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$21\r\n1.349673917939762e+09\r\n",
},
{
[]interface{}{"SET", "key", ""},
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n",
},
{
[]interface{}{"SET", "key", nil},
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n",
},
{
[]interface{}{"SET", "key", durationArg{time.Minute}},
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$2\r\n60\r\n",
},
{
[]interface{}{"SET", "key", recursiveArg(123)},
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n123\r\n",
},
{
[]interface{}{"ECHO", true, false},
"*3\r\n$4\r\nECHO\r\n$1\r\n1\r\n$1\r\n0\r\n",
},
}
func TestWrite(t *testing.T) {
for _, tt := range writeTests {
var buf bytes.Buffer
c, _ := redis.Dial("", "", dialTestConn("", &buf))
err := c.Send(tt.args[0].(string), tt.args[1:]...)
if err != nil {
t.Errorf("Send(%v) returned error %v", tt.args, err)
continue
}
c.Flush()
actual := buf.String()
if actual != tt.expected {
t.Errorf("Send(%v) = %q, want %q", tt.args, actual, tt.expected)
}
}
}
var errorSentinel = &struct{}{}
var readTests = []struct {
reply string
expected interface{}
}{
{
"+OK\r\n",
"OK",
},
{
"+PONG\r\n",
"PONG",
},
{
"@OK\r\n",
errorSentinel,
},
{
"$6\r\nfoobar\r\n",
[]byte("foobar"),
},
{
"$-1\r\n",
nil,
},
{
":1\r\n",
int64(1),
},
{
":-2\r\n",
int64(-2),
},
{
"*0\r\n",
[]interface{}{},
},
{
"*-1\r\n",
nil,
},
{
"*4\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$5\r\nHello\r\n$5\r\nWorld\r\n",
[]interface{}{[]byte("foo"), []byte("bar"), []byte("Hello"), []byte("World")},
},
{
"*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n",
[]interface{}{[]byte("foo"), nil, []byte("bar")},
},
{
// "x" is not a valid length
"$x\r\nfoobar\r\n",
errorSentinel,
},
{
// -2 is not a valid length
"$-2\r\n",
errorSentinel,
},
{
// "x" is not a valid integer
":x\r\n",
errorSentinel,
},
{
// missing \r\n following value
"$6\r\nfoobar",
errorSentinel,
},
{
// short value
"$6\r\nxx",
errorSentinel,
},
{
// long value
"$6\r\nfoobarx\r\n",
errorSentinel,
},
}
func TestRead(t *testing.T) {
for _, tt := range readTests {
c, _ := redis.Dial("", "", dialTestConn(tt.reply, nil))
actual, err := c.Receive()
if tt.expected == errorSentinel {
if err == nil {
t.Errorf("Receive(%q) did not return expected error", tt.reply)
}
} else {
if err != nil {
t.Errorf("Receive(%q) returned error %v", tt.reply, err)
continue
}
if !reflect.DeepEqual(actual, tt.expected) {
t.Errorf("Receive(%q) = %v, want %v", tt.reply, actual, tt.expected)
}
}
}
}
var testCommands = []struct {
args []interface{}
expected interface{}
}{
{
[]interface{}{"PING"},
"PONG",
},
{
[]interface{}{"SET", "foo", "bar"},
"OK",
},
{
[]interface{}{"GET", "foo"},
[]byte("bar"),
},
{
[]interface{}{"GET", "nokey"},
nil,
},
{
[]interface{}{"MGET", "nokey", "foo"},
[]interface{}{nil, []byte("bar")},
},
{
[]interface{}{"INCR", "mycounter"},
int64(1),
},
{
[]interface{}{"LPUSH", "mylist", "foo"},
int64(1),
},
{
[]interface{}{"LPUSH", "mylist", "bar"},
int64(2),
},
{
[]interface{}{"LRANGE", "mylist", 0, -1},
[]interface{}{[]byte("bar"), []byte("foo")},
},
{
[]interface{}{"MULTI"},
"OK",
},
{
[]interface{}{"LRANGE", "mylist", 0, -1},
"QUEUED",
},
{
[]interface{}{"PING"},
"QUEUED",
},
{
[]interface{}{"EXEC"},
[]interface{}{
[]interface{}{[]byte("bar"), []byte("foo")},
"PONG",
},
},
}
func TestDoCommands(t *testing.T) {
c, err := redis.DialDefaultServer()
if err != nil {
t.Fatalf("error connection to database, %v", err)
}
defer c.Close()
for _, cmd := range testCommands {
actual, err := c.Do(cmd.args[0].(string), cmd.args[1:]...)
if err != nil {
t.Errorf("Do(%v) returned error %v", cmd.args, err)
continue
}
if !reflect.DeepEqual(actual, cmd.expected) {
t.Errorf("Do(%v) = %v, want %v", cmd.args, actual, cmd.expected)
}
}
}
func TestPipelineCommands(t *testing.T) {
c, err := redis.DialDefaultServer()
if err != nil {
t.Fatalf("error connection to database, %v", err)
}
defer c.Close()
for _, cmd := range testCommands {
if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil {
t.Fatalf("Send(%v) returned error %v", cmd.args, err)
}
}
if err := c.Flush(); err != nil {
t.Errorf("Flush() returned error %v", err)
}
for _, cmd := range testCommands {
actual, err := c.Receive()
if err != nil {
t.Fatalf("Receive(%v) returned error %v", cmd.args, err)
}
if !reflect.DeepEqual(actual, cmd.expected) {
t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected)
}
}
}
func TestBlankCommmand(t *testing.T) {
c, err := redis.DialDefaultServer()
if err != nil {
t.Fatalf("error connection to database, %v", err)
}
defer c.Close()
for _, cmd := range testCommands {
if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil {
t.Fatalf("Send(%v) returned error %v", cmd.args, err)
}
}
reply, err := redis.Values(c.Do(""))
if err != nil {
t.Fatalf("Do() returned error %v", err)
}
if len(reply) != len(testCommands) {
t.Fatalf("len(reply)=%d, want %d", len(reply), len(testCommands))
}
for i, cmd := range testCommands {
actual := reply[i]
if !reflect.DeepEqual(actual, cmd.expected) {
t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected)
}
}
}
func TestRecvBeforeSend(t *testing.T) {
c, err := redis.DialDefaultServer()
if err != nil {
t.Fatalf("error connection to database, %v", err)
}
defer c.Close()
done := make(chan struct{})
go func() {
c.Receive()
close(done)
}()
time.Sleep(time.Millisecond)
c.Send("PING")
c.Flush()
<-done
_, err = c.Do("")
if err != nil {
t.Fatalf("error=%v", err)
}
}
func TestError(t *testing.T) {
c, err := redis.DialDefaultServer()
if err != nil {
t.Fatalf("error connection to database, %v", err)
}
defer c.Close()
c.Do("SET", "key", "val")
_, err = c.Do("HSET", "key", "fld", "val")
if err == nil {
t.Errorf("Expected err for HSET on string key.")
}
if c.Err() != nil {
t.Errorf("Conn has Err()=%v, expect nil", c.Err())
}
_, err = c.Do("SET", "key", "val")
if err != nil {
t.Errorf("Do(SET, key, val) returned error %v, expected nil.", err)
}
}
func TestReadTimeout(t *testing.T) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("net.Listen returned %v", err)
}
defer l.Close()
go func() {
for {
c, err := l.Accept()
if err != nil {
return
}
go func() {
time.Sleep(time.Second)
c.Write([]byte("+OK\r\n"))
c.Close()
}()
}
}()
// Do
c1, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond))
if err != nil {
t.Fatalf("redis.Dial returned %v", err)
}
defer c1.Close()
_, err = c1.Do("PING")
if err == nil {
t.Fatalf("c1.Do() returned nil, expect error")
}
if c1.Err() == nil {
t.Fatalf("c1.Err() = nil, expect error")
}
// Send/Flush/Receive
c2, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond))
if err != nil {
t.Fatalf("redis.Dial returned %v", err)
}
defer c2.Close()
c2.Send("PING")
c2.Flush()
_, err = c2.Receive()
if err == nil {
t.Fatalf("c2.Receive() returned nil, expect error")
}
if c2.Err() == nil {
t.Fatalf("c2.Err() = nil, expect error")
}
}
var dialErrors = []struct {
rawurl string
expectedError string
}{
{
"localhost",
"invalid redis URL scheme",
},
// The error message for invalid hosts is different in different
// versions of Go, so just check that there is an error message.
{
"redis://weird url",
"",
},
{
"redis://foo:bar:baz",
"",
},
{
"http://www.google.com",
"invalid redis URL scheme: http",
},
{
"redis://localhost:6379/abc123",
"invalid database: abc123",
},
}
func TestDialURLErrors(t *testing.T) {
for _, d := range dialErrors {
_, err := redis.DialURL(d.rawurl)
if err == nil || !strings.Contains(err.Error(), d.expectedError) {
t.Errorf("DialURL did not return expected error (expected %v to contain %s)", err, d.expectedError)
}
}
}
func TestDialURLPort(t *testing.T) {
checkPort := func(network, address string) (net.Conn, error) {
if address != "localhost:6379" {
t.Errorf("DialURL did not set port to 6379 by default (got %v)", address)
}
return nil, nil
}
_, err := redis.DialURL("redis://localhost", redis.DialNetDial(checkPort))
if err != nil {
t.Error("dial error:", err)
}
}
func TestDialURLHost(t *testing.T) {
checkHost := func(network, address string) (net.Conn, error) {
if address != "localhost:6379" {
t.Errorf("DialURL did not set host to localhost by default (got %v)", address)
}
return nil, nil
}
_, err := redis.DialURL("redis://:6379", redis.DialNetDial(checkHost))
if err != nil {
t.Error("dial error:", err)
}
}
var dialURLTests = []struct {
description string
url string
r string
w string
}{
{"password", "redis://x:abc123@localhost", "+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"},
{"database 3", "redis://localhost/3", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n"},
{"database 99", "redis://localhost/99", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$2\r\n99\r\n"},
{"no database", "redis://localhost/", "+OK\r\n", ""},
}
func TestDialURL(t *testing.T) {
for _, tt := range dialURLTests {
var buf bytes.Buffer
// UseTLS should be ignored in all of these tests.
_, err := redis.DialURL(tt.url, dialTestConn(tt.r, &buf), redis.DialUseTLS(true))
if err != nil {
t.Errorf("%s dial error: %v", tt.description, err)
continue
}
if w := buf.String(); w != tt.w {
t.Errorf("%s commands = %q, want %q", tt.description, w, tt.w)
}
}
}
func checkPingPong(t *testing.T, buf *bytes.Buffer, c redis.Conn) {
resp, err := c.Do("PING")
if err != nil {
t.Fatal("ping error:", err)
}
// Close connection to ensure that writes to buf are complete.
c.Close()
expected := "*1\r\n$4\r\nPING\r\n"
actual := buf.String()
if actual != expected {
t.Errorf("commands = %q, want %q", actual, expected)
}
if resp != "PONG" {
t.Errorf("resp = %v, want %v", resp, "PONG")
}
}
const pingResponse = "+PONG\r\n"
func TestDialURLTLS(t *testing.T) {
var buf bytes.Buffer
c, err := redis.DialURL("rediss://example.com/",
redis.DialTLSConfig(&clientTLSConfig),
dialTestConnTLS(pingResponse, &buf))
if err != nil {
t.Fatal("dial error:", err)
}
checkPingPong(t, &buf, c)
}
func TestDialUseTLS(t *testing.T) {
var buf bytes.Buffer
c, err := redis.Dial("tcp", "example.com:6379",
redis.DialTLSConfig(&clientTLSConfig),
dialTestConnTLS(pingResponse, &buf),
redis.DialUseTLS(true))
if err != nil {
t.Fatal("dial error:", err)
}
checkPingPong(t, &buf, c)
}
func TestDialTLSSKipVerify(t *testing.T) {
var buf bytes.Buffer
c, err := redis.Dial("tcp", "example.com:6379",
dialTestConnTLS(pingResponse, &buf),
redis.DialTLSSkipVerify(true),
redis.DialUseTLS(true))
if err != nil {
t.Fatal("dial error:", err)
}
checkPingPong(t, &buf, c)
}
// Connect to local instance of Redis running on the default port.
func ExampleDial() {
c, err := redis.Dial("tcp", ":6379")
if err != nil {
// handle error
}
defer c.Close()
}
// Connect to remote instance of Redis using a URL.
func ExampleDialURL() {
c, err := redis.DialURL(os.Getenv("REDIS_URL"))
if err != nil {
// handle connection error
}
defer c.Close()
}
// TextExecError tests handling of errors in a transaction. See
// http://redis.io/topics/transactions for information on how Redis handles
// errors in a transaction.
func TestExecError(t *testing.T) {
c, err := redis.DialDefaultServer()
if err != nil {
t.Fatalf("error connection to database, %v", err)
}
defer c.Close()
// Execute commands that fail before EXEC is called.
c.Do("DEL", "k0")
c.Do("ZADD", "k0", 0, 0)
c.Send("MULTI")
c.Send("NOTACOMMAND", "k0", 0, 0)
c.Send("ZINCRBY", "k0", 0, 0)
v, err := c.Do("EXEC")
if err == nil {
t.Fatalf("EXEC returned values %v, expected error", v)
}
// Execute commands that fail after EXEC is called. The first command
// returns an error.
c.Do("DEL", "k1")
c.Do("ZADD", "k1", 0, 0)
c.Send("MULTI")
c.Send("HSET", "k1", 0, 0)
c.Send("ZINCRBY", "k1", 0, 0)
v, err = c.Do("EXEC")
if err != nil {
t.Fatalf("EXEC returned error %v", err)
}
vs, err := redis.Values(v, nil)
if err != nil {
t.Fatalf("Values(v) returned error %v", err)
}
if len(vs) != 2 {
t.Fatalf("len(vs) == %d, want 2", len(vs))
}
if _, ok := vs[0].(error); !ok {
t.Fatalf("first result is type %T, expected error", vs[0])
}
if _, ok := vs[1].([]byte); !ok {
t.Fatalf("second result is type %T, expected []byte", vs[1])
}
// Execute commands that fail after EXEC is called. The second command
// returns an error.
c.Do("ZADD", "k2", 0, 0)
c.Send("MULTI")
c.Send("ZINCRBY", "k2", 0, 0)
c.Send("HSET", "k2", 0, 0)
v, err = c.Do("EXEC")
if err != nil {
t.Fatalf("EXEC returned error %v", err)
}
vs, err = redis.Values(v, nil)
if err != nil {
t.Fatalf("Values(v) returned error %v", err)
}
if len(vs) != 2 {
t.Fatalf("len(vs) == %d, want 2", len(vs))
}
if _, ok := vs[0].([]byte); !ok {
t.Fatalf("first result is type %T, expected []byte", vs[0])
}
if _, ok := vs[1].(error); !ok {
t.Fatalf("second result is type %T, expected error", vs[2])
}
}
func BenchmarkDoEmpty(b *testing.B) {
b.StopTimer()
c, err := redis.DialDefaultServer()
if err != nil {
b.Fatal(err)
}
defer c.Close()
b.StartTimer()
for i := 0; i < b.N; i++ {
if _, err := c.Do(""); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkDoPing(b *testing.B) {
b.StopTimer()
c, err := redis.DialDefaultServer()
if err != nil {
b.Fatal(err)
}
defer c.Close()
b.StartTimer()
for i := 0; i < b.N; i++ {
if _, err := c.Do("PING"); err != nil {
b.Fatal(err)
}
}
}
var clientTLSConfig, serverTLSConfig tls.Config
func init() {
// The certificate and key for testing TLS dial options was created
// using the command
//
// go run GOROOT/src/crypto/tls/generate_cert.go \
// --rsa-bits 1024 \
// --host 127.0.0.1,::1,example.com --ca \
// --start-date "Jan 1 00:00:00 1970" \
// --duration=1000000h
//
// where GOROOT is the value of GOROOT reported by go env.
localhostCert := []byte(`
-----BEGIN CERTIFICATE-----
MIICFDCCAX2gAwIBAgIRAJfBL4CUxkXcdlFurb3K+iowDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
gYkCgYEArizw8WxMUQ3bGHLeuJ4fDrEpy+L2pqrbYRlKk1DasJ/VkB8bImzIpe6+
LGjiYIxvnDCOJ3f3QplcQuiuMyl6f2irJlJsbFT8Lo/3obnuTKAIaqUdJUqBg6y+
JaL8Auk97FvunfKFv8U1AIhgiLzAfQ/3Eaq1yi87Ra6pMjGbTtcCAwEAAaNoMGYw
DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF
MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA
AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAdZ8daIVkyhVwflt5I19m0oq1TycbGO1+
ach7T6cZiBQeNR/SJtxr/wKPEpmvUgbv2BfFrKJ8QoIHYsbNSURTWSEa02pfw4k9
6RQhij3ZkG79Ituj5OYRORV6Z0HUW32r670BtcuHuAhq7YA6Nxy4FtSt7bAlVdRt
rrKgNsltzMk=
-----END CERTIFICATE-----`)
localhostKey := []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCuLPDxbExRDdsYct64nh8OsSnL4vamqtthGUqTUNqwn9WQHxsi
bMil7r4saOJgjG+cMI4nd/dCmVxC6K4zKXp/aKsmUmxsVPwuj/ehue5MoAhqpR0l
SoGDrL4lovwC6T3sW+6d8oW/xTUAiGCIvMB9D/cRqrXKLztFrqkyMZtO1wIDAQAB
AoGACrc5G6FOEK6JjDeE/Fa+EmlT6PdNtXNNi+vCas3Opo8u1G8VfEi1D4BgstrB
Eq+RLkrOdB8tVyuYQYWPMhabMqF+hhKJN72j0OwfuPlVvTInwb/cKjo/zbH1IA+Y
HenHNK4ywv7/p/9/MvQPJ3I32cQBCgGUW5chVSH5M1sj5gECQQDabQAI1X0uDqCm
KbX9gXVkAgxkFddrt6LBHt57xujFcqEKFE7nwKhDh7DweVs/VEJ+kpid4z+UnLOw
KjtP9JolAkEAzCNBphQ//IsbH5rNs10wIUw3Ks/Oepicvr6kUFbIv+neRzi1iJHa
m6H7EayK3PWgax6BAsR/t0Jc9XV7r2muSwJAVzN09BHnK+ADGtNEKLTqXMbEk6B0
pDhn7ZmZUOkUPN+Kky+QYM11X6Bob1jDqQDGmymDbGUxGO+GfSofC8inUQJAGfci
Eo3g1a6b9JksMPRZeuLG4ZstGErxJRH6tH1Va5PDwitka8qhk8o2tTjNMO3NSdLH
diKoXBcE2/Pll5pJoQJBAIMiiMIzXJhnN4mX8may44J/HvMlMf2xuVH2gNMwmZuc
Bjqn3yoLHaoZVvbWOi0C2TCN4FjXjaLNZGifQPbIcaA=
-----END RSA PRIVATE KEY-----`)
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
if err != nil {
panic(fmt.Sprintf("error creating key pair: %v", err))
}
serverTLSConfig.Certificates = []tls.Certificate{cert}
certificate, err := x509.ParseCertificate(serverTLSConfig.Certificates[0].Certificate[0])
if err != nil {
panic(fmt.Sprintf("error parsing x509 certificate: %v", err))
}
clientTLSConfig.RootCAs = x509.NewCertPool()
clientTLSConfig.RootCAs.AddCert(certificate)
}

177
vendor/github.com/garyburd/redigo/redis/doc.go generated vendored Normal file
View File

@@ -0,0 +1,177 @@
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// Package redis is a client for the Redis database.
//
// The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more
// documentation about this package.
//
// Connections
//
// The Conn interface is the primary interface for working with Redis.
// Applications create connections by calling the Dial, DialWithTimeout or
// NewConn functions. In the future, functions will be added for creating
// sharded and other types of connections.
//
// The application must call the connection Close method when the application
// is done with the connection.
//
// Executing Commands
//
// The Conn interface has a generic method for executing Redis commands:
//
// Do(commandName string, args ...interface{}) (reply interface{}, err error)
//
// The Redis command reference (http://redis.io/commands) lists the available
// commands. An example of using the Redis APPEND command is:
//
// n, err := conn.Do("APPEND", "key", "value")
//
// The Do method converts command arguments to bulk strings for transmission
// to the server as follows:
//
// Go Type Conversion
// []byte Sent as is
// string Sent as is
// int, int64 strconv.FormatInt(v)
// float64 strconv.FormatFloat(v, 'g', -1, 64)
// bool true -> "1", false -> "0"
// nil ""
// all other types fmt.Fprint(w, v)
//
// Redis command reply types are represented using the following Go types:
//
// Redis type Go type
// error redis.Error
// integer int64
// simple string string
// bulk string []byte or nil if value not present.
// array []interface{} or nil if value not present.
//
// Use type assertions or the reply helper functions to convert from
// interface{} to the specific Go type for the command result.
//
// Pipelining
//
// Connections support pipelining using the Send, Flush and Receive methods.
//
// Send(commandName string, args ...interface{}) error
// Flush() error
// Receive() (reply interface{}, err error)
//
// Send writes the command to the connection's output buffer. Flush flushes the
// connection's output buffer to the server. Receive reads a single reply from
// the server. The following example shows a simple pipeline.
//
// c.Send("SET", "foo", "bar")
// c.Send("GET", "foo")
// c.Flush()
// c.Receive() // reply from SET
// v, err = c.Receive() // reply from GET
//
// The Do method combines the functionality of the Send, Flush and Receive
// methods. The Do method starts by writing the command and flushing the output
// buffer. Next, the Do method receives all pending replies including the reply
// for the command just sent by Do. If any of the received replies is an error,
// then Do returns the error. If there are no errors, then Do returns the last
// reply. If the command argument to the Do method is "", then the Do method
// will flush the output buffer and receive pending replies without sending a
// command.
//
// Use the Send and Do methods to implement pipelined transactions.
//
// c.Send("MULTI")
// c.Send("INCR", "foo")
// c.Send("INCR", "bar")
// r, err := c.Do("EXEC")
// fmt.Println(r) // prints [1, 1]
//
// Concurrency
//
// Connections support one concurrent caller to the Receive method and one
// concurrent caller to the Send and Flush methods. No other concurrency is
// supported including concurrent calls to the Do method.
//
// For full concurrent access to Redis, use the thread-safe Pool to get, use
// and release a connection from within a goroutine. Connections returned from
// a Pool have the concurrency restrictions described in the previous
// paragraph.
//
// Publish and Subscribe
//
// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers.
//
// c.Send("SUBSCRIBE", "example")
// c.Flush()
// for {
// reply, err := c.Receive()
// if err != nil {
// return err
// }
// // process pushed message
// }
//
// The PubSubConn type wraps a Conn with convenience methods for implementing
// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods
// send and flush a subscription management command. The receive method
// converts a pushed message to convenient types for use in a type switch.
//
// psc := redis.PubSubConn{Conn: c}
// psc.Subscribe("example")
// for {
// switch v := psc.Receive().(type) {
// case redis.Message:
// fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
// case redis.Subscription:
// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
// case error:
// return v
// }
// }
//
// Reply Helpers
//
// The Bool, Int, Bytes, String, Strings and Values functions convert a reply
// to a value of a specific type. To allow convenient wrapping of calls to the
// connection Do and Receive methods, the functions take a second argument of
// type error. If the error is non-nil, then the helper function returns the
// error. If the error is nil, the function converts the reply to the specified
// type:
//
// exists, err := redis.Bool(c.Do("EXISTS", "foo"))
// if err != nil {
// // handle error return from c.Do or type conversion error.
// }
//
// The Scan function converts elements of a array reply to Go types:
//
// var value1 int
// var value2 string
// reply, err := redis.Values(c.Do("MGET", "key1", "key2"))
// if err != nil {
// // handle error
// }
// if _, err := redis.Scan(reply, &value1, &value2); err != nil {
// // handle error
// }
//
// Errors
//
// Connection methods return error replies from the server as type redis.Error.
//
// Call the connection Err() method to determine if the connection encountered
// non-recoverable error such as a network error or protocol parsing error. If
// Err() returns a non-nil value, then the connection is not usable and should
// be closed.
package redis // import "github.com/garyburd/redigo/redis"

33
vendor/github.com/garyburd/redigo/redis/go17.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
// +build go1.7
package redis
import "crypto/tls"
// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case
func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config {
if cfg == nil {
return &tls.Config{InsecureSkipVerify: skipVerify}
}
return &tls.Config{
Rand: cfg.Rand,
Time: cfg.Time,
Certificates: cfg.Certificates,
NameToCertificate: cfg.NameToCertificate,
GetCertificate: cfg.GetCertificate,
RootCAs: cfg.RootCAs,
NextProtos: cfg.NextProtos,
ServerName: cfg.ServerName,
ClientAuth: cfg.ClientAuth,
ClientCAs: cfg.ClientCAs,
InsecureSkipVerify: cfg.InsecureSkipVerify,
CipherSuites: cfg.CipherSuites,
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
ClientSessionCache: cfg.ClientSessionCache,
MinVersion: cfg.MinVersion,
MaxVersion: cfg.MaxVersion,
CurvePreferences: cfg.CurvePreferences,
DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled,
Renegotiation: cfg.Renegotiation,
}
}

Some files were not shown because too many files have changed in this diff Show More