commit
13c3edde05
@ -1,152 +0,0 @@ |
||||
hash: 1d5fcf2a90f7621ecbc0b1abed548e11d13bda3fea49b4326c829a523268e5cf |
||||
updated: 2016-06-12T17:35:14.27036884+08:00 |
||||
imports: |
||||
- name: github.com/bradfitz/gomemcache |
||||
version: fb1f79c6b65acda83063cbc69f6bba1522558bfc |
||||
subpackages: |
||||
- memcache |
||||
- name: github.com/urfave/cli |
||||
version: 1efa31f08b9333f1bd4882d61f9d668a70cd902e |
||||
- name: github.com/go-macaron/binding |
||||
version: 9440f336b443056c90d7d448a0a55ad8c7599880 |
||||
- name: github.com/go-macaron/cache |
||||
version: 56173531277692bc2925924d51fda1cd0a6b8178 |
||||
subpackages: |
||||
- memcache |
||||
- redis |
||||
- name: github.com/go-macaron/captcha |
||||
version: 8aa5919789ab301e865595eb4b1114d6b9847deb |
||||
- name: github.com/go-macaron/csrf |
||||
version: 6a9a7df172cc1fcd81e4585f44b09200b6087cc0 |
||||
- name: github.com/go-macaron/gzip |
||||
version: cad1c6580a07c56f5f6bc52d66002a05985c5854 |
||||
- name: github.com/go-macaron/i18n |
||||
version: ef57533c3b0fc2d8581deda14937e52f11a203ab |
||||
- name: github.com/go-macaron/inject |
||||
version: c5ab7bf3a307593cd44cb272d1a5beea473dd072 |
||||
- name: github.com/go-macaron/session |
||||
version: 66031fcb37a0fff002a1f028eb0b3a815c78306b |
||||
subpackages: |
||||
- redis |
||||
- name: github.com/go-macaron/toolbox |
||||
version: 82b511550b0aefc36b3a28062ad3a52e812bee38 |
||||
- name: github.com/go-sql-driver/mysql |
||||
version: 0b58b37b664c21f3010e836f1b931e1d0b0b0685 |
||||
- name: github.com/go-xorm/core |
||||
version: 5bf745d7d163f4380e6c2bba8c4afa60534dd087 |
||||
- name: github.com/go-xorm/xorm |
||||
version: c6c705684057842d9854e8299dd51abb06ae29f5 |
||||
- name: github.com/gogits/chardet |
||||
version: 2404f777256163ea3eadb273dada5dcb037993c0 |
||||
- name: github.com/gogits/cron |
||||
version: 7f3990acf1833faa5ebd0e86f0a4c72a4b5eba3c |
||||
- name: github.com/gogits/git-module |
||||
version: 5e0c1330d7853d1affbc193885d517db0f8d1ca5 |
||||
- name: github.com/gogits/go-gogs-client |
||||
version: c52f7ee0cc58d3cd6e379025552873a8df6de322 |
||||
- name: github.com/issue9/identicon |
||||
version: d36b54562f4cf70c83653e13dc95c220c79ef521 |
||||
- name: github.com/jaytaylor/html2text |
||||
version: 52d9b785554a1918cb09909b89a1509a98b853fd |
||||
- name: github.com/kardianos/minwinsvc |
||||
version: cad6b2b879b0970e4245a20ebf1a81a756e2bb70 |
||||
- name: github.com/klauspost/compress |
||||
version: 14eb9c4951195779ecfbec34431a976de7335b0a |
||||
subpackages: |
||||
- gzip |
||||
- flate |
||||
- name: github.com/klauspost/cpuid |
||||
version: 09cded8978dc9e80714c4d85b0322337b0a1e5e0 |
||||
- name: github.com/klauspost/crc32 |
||||
version: 19b0b332c9e4516a6370a0456e6182c3b5036720 |
||||
- name: github.com/lib/pq |
||||
version: 80f8150043c80fb52dee6bc863a709cdac7ec8f8 |
||||
subpackages: |
||||
- oid |
||||
- name: github.com/mattn/go-sqlite3 |
||||
version: e118d4451349065b8e7ce0f0af32e033995363f8 |
||||
- name: github.com/mcuadros/go-version |
||||
version: d52711f8d6bea8dc01efafdb68ad95a4e2606630 |
||||
- name: github.com/microcosm-cc/bluemonday |
||||
version: 9dc199233bf72cc1aad9b61f73daf2f0075b9ee4 |
||||
- name: github.com/msteinert/pam |
||||
version: 02ccfbfaf0cc627aa3aec8ef7ed5cfeec5b43f63 |
||||
- name: github.com/nfnt/resize |
||||
version: 891127d8d1b52734debe1b3c3d7e747502b6c366 |
||||
- name: github.com/russross/blackfriday |
||||
version: 93622da34e54fb6529bfb7c57e710f37a8d9cbd8 |
||||
- name: github.com/satori/go.uuid |
||||
version: 0aa62d5ddceb50dbcb909d790b5345affd3669b6 |
||||
- name: github.com/sergi/go-diff |
||||
version: ec7fdbb58eb3e300c8595ad5ac74a5aa50019cc7 |
||||
subpackages: |
||||
- diffmatchpatch |
||||
- name: strk.kbt.io/projects/go/libravatar |
||||
version: 5eed7bff870ae19ef51c5773dbc8f3e9fcbd0982 |
||||
- name: github.com/shurcooL/sanitized_anchor_name |
||||
version: 10ef21a441db47d8b13ebcc5fd2310f636973c77 |
||||
- name: github.com/Unknwon/cae |
||||
version: 7f5e046bc8a6c3cde743c233b96ee4fd84ee6ecd |
||||
subpackages: |
||||
- zip |
||||
- name: github.com/Unknwon/com |
||||
version: 28b053d5a2923b87ce8c5a08f3af779894a72758 |
||||
- name: github.com/Unknwon/i18n |
||||
version: 39d6f2727e0698b1021ceb6a77c1801aa92e7d5d |
||||
- name: github.com/Unknwon/paginater |
||||
version: 7748a72e01415173a27d79866b984328e7b0c12b |
||||
- name: golang.org/x/crypto |
||||
version: bc89c496413265e715159bdc8478ee9a92fdc265 |
||||
subpackages: |
||||
- ssh |
||||
- curve25519 |
||||
- ed25519 |
||||
- ed25519/internal/edwards25519 |
||||
- name: golang.org/x/net |
||||
version: 57bfaa875b96fb91b4766077f34470528d4b03e9 |
||||
subpackages: |
||||
- html |
||||
- html/charset |
||||
- html/atom |
||||
- name: golang.org/x/sys |
||||
version: a646d33e2ee3172a661fc09bca23bb4889a41bc8 |
||||
subpackages: |
||||
- windows/svc |
||||
- windows |
||||
- name: golang.org/x/text |
||||
version: 2910a502d2bf9e43193af9d68ca516529614eed3 |
||||
subpackages: |
||||
- transform |
||||
- language |
||||
- encoding |
||||
- encoding/charmap |
||||
- encoding/htmlindex |
||||
- internal/tag |
||||
- encoding/internal/identifier |
||||
- encoding/internal |
||||
- encoding/japanese |
||||
- encoding/korean |
||||
- encoding/simplifiedchinese |
||||
- encoding/traditionalchinese |
||||
- encoding/unicode |
||||
- internal/utf8internal |
||||
- runes |
||||
- name: gopkg.in/alexcesaro/quotedprintable.v3 |
||||
version: 2caba252f4dc53eaf6b553000885530023f54623 |
||||
- name: gopkg.in/asn1-ber.v1 |
||||
version: 4e86f4367175e39f69d9358a5f17b4dda270378d |
||||
- name: gopkg.in/bufio.v1 |
||||
version: 567b2bfa514e796916c4747494d6ff5132a1dfce |
||||
- name: gopkg.in/editorconfig/editorconfig-core-go.v1 |
||||
version: a872f05c2e34b37b567401384d202aff11ba06d4 |
||||
- name: gopkg.in/gomail.v2 |
||||
version: 81ebce5c23dfd25c6c67194b37d3dd3f338c98b1 |
||||
- name: gopkg.in/ini.v1 |
||||
version: cf53f9204df4fbdd7ec4164b57fa6184ba168292 |
||||
- name: gopkg.in/ldap.v2 |
||||
version: d0a5ced67b4dc310b9158d63a2c6f9c5ec13f105 |
||||
- name: gopkg.in/macaron.v1 |
||||
version: 7564489a79f3f96b7ac8034652b35eeebb468eb4 |
||||
- name: gopkg.in/redis.v2 |
||||
version: e6179049628164864e6e84e973cfb56335748dea |
||||
devImports: [] |
@ -1,59 +0,0 @@ |
||||
package: github.com/go-gitea/gitea |
||||
import: |
||||
- package: github.com/Unknwon/cae |
||||
subpackages: |
||||
- zip |
||||
- package: github.com/Unknwon/com |
||||
- package: github.com/Unknwon/i18n |
||||
- package: github.com/Unknwon/paginater |
||||
- package: github.com/urfave/cli |
||||
- package: github.com/go-macaron/binding |
||||
- package: github.com/go-macaron/cache |
||||
subpackages: |
||||
- memcache |
||||
- redis |
||||
- package: github.com/go-macaron/captcha |
||||
- package: github.com/go-macaron/csrf |
||||
- package: github.com/go-macaron/gzip |
||||
- package: github.com/go-macaron/i18n |
||||
- package: github.com/go-macaron/session |
||||
subpackages: |
||||
- redis |
||||
- package: github.com/go-macaron/toolbox |
||||
- package: github.com/go-sql-driver/mysql |
||||
- package: github.com/go-xorm/core |
||||
- package: github.com/go-xorm/xorm |
||||
- package: github.com/gogits/chardet |
||||
- package: github.com/gogits/cron |
||||
- package: github.com/gogits/git-module |
||||
- package: github.com/gogits/go-gogs-client |
||||
- package: github.com/issue9/identicon |
||||
- package: github.com/kardianos/minwinsvc |
||||
- package: github.com/lib/pq |
||||
- package: github.com/mattn/go-sqlite3 |
||||
- package: github.com/mcuadros/go-version |
||||
- package: github.com/microcosm-cc/bluemonday |
||||
- package: github.com/msteinert/pam |
||||
- package: github.com/nfnt/resize |
||||
- package: github.com/russross/blackfriday |
||||
- package: github.com/satori/go.uuid |
||||
- package: github.com/sergi/go-diff |
||||
subpackages: |
||||
- diffmatchpatch |
||||
- package: strk.kbt.io/projects/go/libravatar |
||||
- package: golang.org/x/crypto |
||||
subpackages: |
||||
- ssh |
||||
- package: golang.org/x/net |
||||
subpackages: |
||||
- html |
||||
- html/charset |
||||
- package: golang.org/x/text |
||||
subpackages: |
||||
- transform |
||||
- language |
||||
- package: gopkg.in/editorconfig/editorconfig-core-go.v1 |
||||
- package: gopkg.in/gomail.v2 |
||||
- package: gopkg.in/ini.v1 |
||||
- package: gopkg.in/ldap.v2 |
||||
- package: gopkg.in/macaron.v1 |
@ -0,0 +1,191 @@ |
||||
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: |
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of |
||||
this License; and |
||||
You must cause any modified files to carry prominent notices stating that You |
||||
changed the files; and |
||||
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 |
||||
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. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work |
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate |
||||
notice, with the fields enclosed by brackets "[]" replaced with your own |
||||
identifying information. (Don't include the brackets!) The text should be |
||||
enclosed in the appropriate comment syntax for the file format. We also |
||||
recommend that a file or class name and description of purpose be included on |
||||
the same "printed page" as the copyright notice for easier identification within |
||||
third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
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. |
@ -0,0 +1,37 @@ |
||||
Compression and Archive Extensions |
||||
================================== |
||||
|
||||
[![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/Unknwon/cae) |
||||
|
||||
[中文文档](README_ZH.md) |
||||
|
||||
Package cae implements PHP-like Compression and Archive Extensions. |
||||
|
||||
But this package has some modifications depends on Go-style. |
||||
|
||||
Reference: [PHP:Compression and Archive Extensions](http://www.php.net/manual/en/refs.compression.php). |
||||
|
||||
Code Convention: based on [Go Code Convention](https://github.com/Unknwon/go-code-convention). |
||||
|
||||
### Implementations |
||||
|
||||
Package `zip`([Go Walker](http://gowalker.org/github.com/Unknwon/cae/zip)) and `tz`([Go Walker](http://gowalker.org/github.com/Unknwon/cae/tz)) both enable you to transparently read or write ZIP/TAR.GZ compressed archives and the files inside them. |
||||
|
||||
- Features: |
||||
- Add file or directory from everywhere to archive, no one-to-one limitation. |
||||
- Extract part of entries, not all at once. |
||||
- Stream data directly into `io.Writer` without any file system storage. |
||||
|
||||
### Test cases and Coverage |
||||
|
||||
All subpackages use [GoConvey](http://goconvey.co/) to write test cases, and coverage is more than 80 percent. |
||||
|
||||
### Use cases |
||||
|
||||
- [Gogs](https://github.com/gogits/gogs): self hosted Git service in the Go Programming Language. |
||||
- [GoBlog](https://github.com/fuxiaohei/GoBlog): personal blogging application. |
||||
- [GoBuild](https://github.com/shxsun/gobuild/): online Go cross-platform compilation and download service. |
||||
|
||||
## License |
||||
|
||||
This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text. |
@ -0,0 +1,29 @@ |
||||
压缩与打包扩展 |
||||
============= |
||||
|
||||
[![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/Unknwon/cae) |
||||
|
||||
包 cae 实现了 PHP 风格的压缩与打包扩展。 |
||||
|
||||
但本包依据 Go 语言的风格进行了一些修改。 |
||||
|
||||
引用:[PHP:Compression and Archive Extensions](http://www.php.net/manual/en/refs.compression.php) |
||||
|
||||
编码规范:基于 [Go 编码规范](https://github.com/Unknwon/go-code-convention) |
||||
|
||||
### 实现 |
||||
|
||||
包 `zip`([Go Walker](http://gowalker.org/github.com/Unknwon/cae/zip)) 和 `tz`([Go Walker](http://gowalker.org/github.com/Unknwon/cae/tz)) 都允许你轻易的读取或写入 ZIP/TAR.GZ 压缩档案和其内部文件。 |
||||
|
||||
- 特性: |
||||
- 将任意位置的文件或目录加入档案,没有一对一的操作限制。 |
||||
- 只解压部分文件,而非一次性解压全部。 |
||||
- 将数据以流的形式直接写入 `io.Writer` 而不需经过文件系统的存储。 |
||||
|
||||
### 测试用例与覆盖率 |
||||
|
||||
所有子包均采用 [GoConvey](http://goconvey.co/) 来书写测试用例,覆盖率均超过 80%。 |
||||
|
||||
## 授权许可 |
||||
|
||||
本项目采用 Apache v2 开源授权许可证,完整的授权说明已放置在 [LICENSE](LICENSE) 文件中。 |
@ -0,0 +1,108 @@ |
||||
// Copyright 2013 Unknown
|
||||
//
|
||||
// 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 cae implements PHP-like Compression and Archive Extensions.
|
||||
package cae |
||||
|
||||
import ( |
||||
"io" |
||||
"os" |
||||
"strings" |
||||
) |
||||
|
||||
// A Streamer describes an streamable archive object.
|
||||
type Streamer interface { |
||||
StreamFile(string, os.FileInfo, []byte) error |
||||
StreamReader(string, os.FileInfo, io.Reader) error |
||||
Close() error |
||||
} |
||||
|
||||
// A HookFunc represents a middleware for packing and extracting archive.
|
||||
type HookFunc func(string, os.FileInfo) error |
||||
|
||||
// HasPrefix returns true if name has any string in given slice as prefix.
|
||||
func HasPrefix(name string, prefixes []string) bool { |
||||
for _, prefix := range prefixes { |
||||
if strings.HasPrefix(name, prefix) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// IsEntry returns true if name equals to any string in given slice.
|
||||
func IsEntry(name string, entries []string) bool { |
||||
for _, e := range entries { |
||||
if e == name { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// IsFilter returns true if given name matches any of global filter rule.
|
||||
func IsFilter(name string) bool { |
||||
if strings.Contains(name, ".DS_Store") { |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// IsExist returns true if given path is a file or directory.
|
||||
func IsExist(path string) bool { |
||||
_, err := os.Stat(path) |
||||
return err == nil || os.IsExist(err) |
||||
} |
||||
|
||||
// Copy copies file from source to target path.
|
||||
func Copy(dest, src string) error { |
||||
// Gather file information to set back later.
|
||||
si, err := os.Lstat(src) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Handle symbolic link.
|
||||
if si.Mode()&os.ModeSymlink != 0 { |
||||
target, err := os.Readlink(src) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
// NOTE: os.Chmod and os.Chtimes don't recoganize symbolic link,
|
||||
// which will lead "no such file or directory" error.
|
||||
return os.Symlink(target, dest) |
||||
} |
||||
|
||||
sr, err := os.Open(src) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer sr.Close() |
||||
|
||||
dw, err := os.Create(dest) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer dw.Close() |
||||
|
||||
if _, err = io.Copy(dw, sr); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Set back file information.
|
||||
if err = os.Chtimes(dest, si.ModTime(), si.ModTime()); err != nil { |
||||
return err |
||||
} |
||||
return os.Chmod(dest, si.Mode()) |
||||
} |
@ -0,0 +1,67 @@ |
||||
// Copyright 2013 Unknown
|
||||
//
|
||||
// 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 zip |
||||
|
||||
import ( |
||||
"archive/zip" |
||||
"os" |
||||
"strings" |
||||
) |
||||
|
||||
// OpenFile is the generalized open call; most users will use Open
|
||||
// instead. It opens the named zip file with specified flag
|
||||
// (O_RDONLY etc.) if applicable. If successful,
|
||||
// methods on the returned ZipArchive can be used for I/O.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func (z *ZipArchive) Open(name string, flag int, perm os.FileMode) error { |
||||
// Create a new archive if it's specified and not exist.
|
||||
if flag&os.O_CREATE != 0 { |
||||
f, err := os.Create(name) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
zw := zip.NewWriter(f) |
||||
if err = zw.Close(); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
rc, err := zip.OpenReader(name) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
z.ReadCloser = rc |
||||
z.FileName = name |
||||
z.Comment = rc.Comment |
||||
z.NumFiles = len(rc.File) |
||||
z.Flag = flag |
||||
z.Permission = perm |
||||
z.isHasChanged = false |
||||
|
||||
z.files = make([]*File, z.NumFiles) |
||||
for i, f := range rc.File { |
||||
z.files[i] = &File{} |
||||
z.files[i].FileHeader, err = zip.FileInfoHeader(f.FileInfo()) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
z.files[i].Name = strings.Replace(f.Name, "\\", "/", -1) |
||||
if f.FileInfo().IsDir() && !strings.HasSuffix(z.files[i].Name, "/") { |
||||
z.files[i].Name += "/" |
||||
} |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,77 @@ |
||||
// Copyright 2014 Unknown
|
||||
//
|
||||
// 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 zip |
||||
|
||||
import ( |
||||
"archive/zip" |
||||
"io" |
||||
"os" |
||||
"path/filepath" |
||||
) |
||||
|
||||
// A StreamArchive represents a streamable archive.
|
||||
type StreamArchive struct { |
||||
*zip.Writer |
||||
} |
||||
|
||||
// NewStreamArachive returns a new streamable archive with given io.Writer.
|
||||
// It's caller's responsibility to close io.Writer and streamer after operation.
|
||||
func NewStreamArachive(w io.Writer) *StreamArchive { |
||||
return &StreamArchive{zip.NewWriter(w)} |
||||
} |
||||
|
||||
// StreamFile streams a file or directory entry into StreamArchive.
|
||||
func (s *StreamArchive) StreamFile(relPath string, fi os.FileInfo, data []byte) error { |
||||
if fi.IsDir() { |
||||
fh, err := zip.FileInfoHeader(fi) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
fh.Name = relPath + "/" |
||||
if _, err = s.Writer.CreateHeader(fh); err != nil { |
||||
return err |
||||
} |
||||
} else { |
||||
fh, err := zip.FileInfoHeader(fi) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
fh.Name = filepath.Join(relPath, fi.Name()) |
||||
fh.Method = zip.Deflate |
||||
fw, err := s.Writer.CreateHeader(fh) |
||||
if err != nil { |
||||
return err |
||||
} else if _, err = fw.Write(data); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// StreamReader streams data from io.Reader to StreamArchive.
|
||||
func (s *StreamArchive) StreamReader(relPath string, fi os.FileInfo, r io.Reader) (err error) { |
||||
fh, err := zip.FileInfoHeader(fi) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
fh.Name = filepath.Join(relPath, fi.Name()) |
||||
|
||||
fw, err := s.Writer.CreateHeader(fh) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
_, err = io.Copy(fw, r) |
||||
return err |
||||
} |
@ -0,0 +1,364 @@ |
||||
// Copyright 2013 Unknown
|
||||
//
|
||||
// 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 zip |
||||
|
||||
import ( |
||||
"archive/zip" |
||||
"fmt" |
||||
"io" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
"strings" |
||||
|
||||
"github.com/Unknwon/cae" |
||||
) |
||||
|
||||
// Switcher of printing trace information when pack and extract.
|
||||
var Verbose = true |
||||
|
||||
// extractFile extracts zip.File to file system.
|
||||
func extractFile(f *zip.File, destPath string) error { |
||||
filePath := path.Join(destPath, f.Name) |
||||
os.MkdirAll(path.Dir(filePath), os.ModePerm) |
||||
|
||||
rc, err := f.Open() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer rc.Close() |
||||
|
||||
fw, err := os.Create(filePath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer fw.Close() |
||||
|
||||
if _, err = io.Copy(fw, rc); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Skip symbolic links.
|
||||
if f.FileInfo().Mode()&os.ModeSymlink != 0 { |
||||
return nil |
||||
} |
||||
// Set back file information.
|
||||
if err = os.Chtimes(filePath, f.ModTime(), f.ModTime()); err != nil { |
||||
return err |
||||
} |
||||
return os.Chmod(filePath, f.FileInfo().Mode()) |
||||
} |
||||
|
||||
var defaultExtractFunc = func(fullName string, fi os.FileInfo) error { |
||||
if !Verbose { |
||||
return nil |
||||
} |
||||
|
||||
fmt.Println("Extracting file..." + fullName) |
||||
return nil |
||||
} |
||||
|
||||
// ExtractToFunc extracts the whole archive or the given files to the
|
||||
// specified destination.
|
||||
// It accepts a function as a middleware for custom operations.
|
||||
func (z *ZipArchive) ExtractToFunc(destPath string, fn cae.HookFunc, entries ...string) (err error) { |
||||
destPath = strings.Replace(destPath, "\\", "/", -1) |
||||
isHasEntry := len(entries) > 0 |
||||
if Verbose { |
||||
fmt.Println("Unzipping " + z.FileName + "...") |
||||
} |
||||
os.MkdirAll(destPath, os.ModePerm) |
||||
for _, f := range z.File { |
||||
f.Name = strings.Replace(f.Name, "\\", "/", -1) |
||||
|
||||
// Directory.
|
||||
if strings.HasSuffix(f.Name, "/") { |
||||
if isHasEntry { |
||||
if cae.IsEntry(f.Name, entries) { |
||||
if err = fn(f.Name, f.FileInfo()); err != nil { |
||||
continue |
||||
} |
||||
os.MkdirAll(path.Join(destPath, f.Name), os.ModePerm) |
||||
} |
||||
continue |
||||
} |
||||
if err = fn(f.Name, f.FileInfo()); err != nil { |
||||
continue |
||||
} |
||||
os.MkdirAll(path.Join(destPath, f.Name), os.ModePerm) |
||||
continue |
||||
} |
||||
|
||||
// File.
|
||||
if isHasEntry { |
||||
if cae.IsEntry(f.Name, entries) { |
||||
if err = fn(f.Name, f.FileInfo()); err != nil { |
||||
continue |
||||
} |
||||
err = extractFile(f, destPath) |
||||
} |
||||
} else { |
||||
if err = fn(f.Name, f.FileInfo()); err != nil { |
||||
continue |
||||
} |
||||
err = extractFile(f, destPath) |
||||
} |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// ExtractToFunc extracts the whole archive or the given files to the
|
||||
// specified destination.
|
||||
// It accepts a function as a middleware for custom operations.
|
||||
func ExtractToFunc(srcPath, destPath string, fn cae.HookFunc, entries ...string) (err error) { |
||||
z, err := Open(srcPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer z.Close() |
||||
return z.ExtractToFunc(destPath, fn, entries...) |
||||
} |
||||
|
||||
// ExtractTo extracts the whole archive or the given files to the
|
||||
// specified destination.
|
||||
// Call Flush() to apply changes before this.
|
||||
func (z *ZipArchive) ExtractTo(destPath string, entries ...string) (err error) { |
||||
return z.ExtractToFunc(destPath, defaultExtractFunc, entries...) |
||||
} |
||||
|
||||
// ExtractTo extracts given archive or the given files to the
|
||||
// specified destination.
|
||||
func ExtractTo(srcPath, destPath string, entries ...string) (err error) { |
||||
return ExtractToFunc(srcPath, destPath, defaultExtractFunc, entries...) |
||||
} |
||||
|
||||
// extractFile extracts file from ZipArchive to file system.
|
||||
func (z *ZipArchive) extractFile(f *File) error { |
||||
if !z.isHasWriter { |
||||
for _, zf := range z.ReadCloser.File { |
||||
if f.Name == zf.Name { |
||||
return extractFile(zf, path.Dir(f.tmpPath)) |
||||
} |
||||
} |
||||
} |
||||
return cae.Copy(f.tmpPath, f.absPath) |
||||
} |
||||
|
||||
// Flush saves changes to original zip file if any.
|
||||
func (z *ZipArchive) Flush() error { |
||||
if !z.isHasChanged || (z.ReadCloser == nil && !z.isHasWriter) { |
||||
return nil |
||||
} |
||||
|
||||
// Extract to tmp path and pack back.
|
||||
tmpPath := path.Join(os.TempDir(), "cae", path.Base(z.FileName)) |
||||
os.RemoveAll(tmpPath) |
||||
defer os.RemoveAll(tmpPath) |
||||
|
||||
for _, f := range z.files { |
||||
if strings.HasSuffix(f.Name, "/") { |
||||
os.MkdirAll(path.Join(tmpPath, f.Name), os.ModePerm) |
||||
continue |
||||
} |
||||
|
||||
// Relative path inside zip temporary changed.
|
||||
f.tmpPath = path.Join(tmpPath, f.Name) |
||||
if err := z.extractFile(f); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
if z.isHasWriter { |
||||
return packToWriter(tmpPath, z.writer, defaultPackFunc, true) |
||||
} |
||||
|
||||
if err := PackTo(tmpPath, z.FileName); err != nil { |
||||
return err |
||||
} |
||||
return z.Open(z.FileName, os.O_RDWR|os.O_TRUNC, z.Permission) |
||||
} |
||||
|
||||
// packFile packs a file or directory to zip.Writer.
|
||||
func packFile(srcFile string, recPath string, zw *zip.Writer, fi os.FileInfo) error { |
||||
if fi.IsDir() { |
||||
fh, err := zip.FileInfoHeader(fi) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
fh.Name = recPath + "/" |
||||
if _, err = zw.CreateHeader(fh); err != nil { |
||||
return err |
||||
} |
||||
} else { |
||||
fh, err := zip.FileInfoHeader(fi) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
fh.Name = recPath |
||||
fh.Method = zip.Deflate |
||||
|
||||
fw, err := zw.CreateHeader(fh) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if fi.Mode()&os.ModeSymlink != 0 { |
||||
target, err := os.Readlink(srcFile) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if _, err = fw.Write([]byte(target)); err != nil { |
||||
return err |
||||
} |
||||
} else { |
||||
f, err := os.Open(srcFile) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer f.Close() |
||||
|
||||
if _, err = io.Copy(fw, f); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// packDir packs a directory and its subdirectories and files
|
||||
// recursively to zip.Writer.
|
||||
func packDir(srcPath string, recPath string, zw *zip.Writer, fn cae.HookFunc) error { |
||||
dir, err := os.Open(srcPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer dir.Close() |
||||
|
||||
fis, err := dir.Readdir(0) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
for _, fi := range fis { |
||||
if cae.IsFilter(fi.Name()) { |
||||
continue |
||||
} |
||||
curPath := srcPath + "/" + fi.Name() |
||||
tmpRecPath := filepath.Join(recPath, fi.Name()) |
||||
if err = fn(curPath, fi); err != nil { |
||||
continue |
||||
} |
||||
|
||||
if fi.IsDir() { |
||||
if err = packFile(srcPath, tmpRecPath, zw, fi); err != nil { |
||||
return err |
||||
} |
||||
err = packDir(curPath, tmpRecPath, zw, fn) |
||||
} else { |
||||
err = packFile(curPath, tmpRecPath, zw, fi) |
||||
} |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// packToWriter packs given path object to io.Writer.
|
||||
func packToWriter(srcPath string, w io.Writer, fn func(fullName string, fi os.FileInfo) error, includeDir bool) error { |
||||
zw := zip.NewWriter(w) |
||||
defer zw.Close() |
||||
|
||||
f, err := os.Open(srcPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer f.Close() |
||||
|
||||
fi, err := f.Stat() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
basePath := path.Base(srcPath) |
||||
if fi.IsDir() { |
||||
if includeDir { |
||||
if err = packFile(srcPath, basePath, zw, fi); err != nil { |
||||
return err |
||||
} |
||||
} else { |
||||
basePath = "" |
||||
} |
||||
return packDir(srcPath, basePath, zw, fn) |
||||
} |
||||
return packFile(srcPath, basePath, zw, fi) |
||||
} |
||||
|
||||
// packTo packs given source path object to target path.
|
||||
func packTo(srcPath, destPath string, fn cae.HookFunc, includeDir bool) error { |
||||
fw, err := os.Create(destPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer fw.Close() |
||||
|
||||
return packToWriter(srcPath, fw, fn, includeDir) |
||||
} |
||||
|
||||
// PackToFunc packs the complete archive to the specified destination.
|
||||
// It accepts a function as a middleware for custom operations.
|
||||
func PackToFunc(srcPath, destPath string, fn func(fullName string, fi os.FileInfo) error, includeDir ...bool) error { |
||||
isIncludeDir := false |
||||
if len(includeDir) > 0 && includeDir[0] { |
||||
isIncludeDir = true |
||||
} |
||||
return packTo(srcPath, destPath, fn, isIncludeDir) |
||||
} |
||||
|
||||
var defaultPackFunc = func(fullName string, fi os.FileInfo) error { |
||||
if !Verbose { |
||||
return nil |
||||
} |
||||
|
||||
if fi.IsDir() { |
||||
fmt.Printf("Adding dir...%s\n", fullName) |
||||
} else { |
||||
fmt.Printf("Adding file...%s\n", fullName) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// PackTo packs the whole archive to the specified destination.
|
||||
// Call Flush() will automatically call this in the end.
|
||||
func PackTo(srcPath, destPath string, includeDir ...bool) error { |
||||
return PackToFunc(srcPath, destPath, defaultPackFunc, includeDir...) |
||||
} |
||||
|
||||
// Close opens or creates archive and save changes.
|
||||
func (z *ZipArchive) Close() (err error) { |
||||
if err = z.Flush(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if z.ReadCloser != nil { |
||||
if err = z.ReadCloser.Close(); err != nil { |
||||
return err |
||||
} |
||||
z.ReadCloser = nil |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,238 @@ |
||||
// Copyright 2013 Unknown
|
||||
//
|
||||
// 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 zip enables you to transparently read or write ZIP compressed archives and the files inside them.
|
||||
package zip |
||||
|
||||
import ( |
||||
"archive/zip" |
||||
"errors" |
||||
"io" |
||||
"os" |
||||
"path" |
||||
"strings" |
||||
|
||||
"github.com/Unknwon/cae" |
||||
) |
||||
|
||||
// A File represents a file or directory entry in archive.
|
||||
type File struct { |
||||
*zip.FileHeader |
||||
oldName string // NOTE: unused, for future change name feature.
|
||||
oldComment string // NOTE: unused, for future change comment feature.
|
||||
absPath string // Absolute path of local file system.
|
||||
tmpPath string |
||||
} |
||||
|
||||
// A ZipArchive represents a file archive, compressed with Zip.
|
||||
type ZipArchive struct { |
||||
*zip.ReadCloser |
||||
FileName string |
||||
Comment string |
||||
NumFiles int |
||||
Flag int |
||||
Permission os.FileMode |
||||
|
||||
files []*File |
||||
isHasChanged bool |
||||
|
||||
// For supporting flushing to io.Writer.
|
||||
writer io.Writer |
||||
isHasWriter bool |
||||
} |
||||
|
||||
// OpenFile is the generalized open call; most users will use Open
|
||||
// instead. It opens the named zip file with specified flag
|
||||
// (O_RDONLY etc.) if applicable. If successful,
|
||||
// methods on the returned ZipArchive can be used for I/O.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func OpenFile(name string, flag int, perm os.FileMode) (*ZipArchive, error) { |
||||
z := new(ZipArchive) |
||||
err := z.Open(name, flag, perm) |
||||
return z, err |
||||
} |
||||
|
||||
// Create creates the named zip file, truncating
|
||||
// it if it already exists. If successful, methods on the returned
|
||||
// ZipArchive can be used for I/O; the associated file descriptor has mode
|
||||
// O_RDWR.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func Create(name string) (*ZipArchive, error) { |
||||
os.MkdirAll(path.Dir(name), os.ModePerm) |
||||
return OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) |
||||
} |
||||
|
||||
// Open opens the named zip file for reading. If successful, methods on
|
||||
// the returned ZipArchive can be used for reading; the associated file
|
||||
// descriptor has mode O_RDONLY.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func Open(name string) (*ZipArchive, error) { |
||||
return OpenFile(name, os.O_RDONLY, 0) |
||||
} |
||||
|
||||
// New accepts a variable that implemented interface io.Writer
|
||||
// for write-only purpose operations.
|
||||
func New(w io.Writer) *ZipArchive { |
||||
return &ZipArchive{ |
||||
writer: w, |
||||
isHasWriter: true, |
||||
} |
||||
} |
||||
|
||||
// List returns a string slice of files' name in ZipArchive.
|
||||
// Specify prefixes will be used as filters.
|
||||
func (z *ZipArchive) List(prefixes ...string) []string { |
||||
isHasPrefix := len(prefixes) > 0 |
||||
names := make([]string, 0, z.NumFiles) |
||||
for _, f := range z.files { |
||||
if isHasPrefix && !cae.HasPrefix(f.Name, prefixes) { |
||||
continue |
||||
} |
||||
names = append(names, f.Name) |
||||
} |
||||
return names |
||||
} |
||||
|
||||
// AddEmptyDir adds a raw directory entry to ZipArchive,
|
||||
// it returns false if same directory enry already existed.
|
||||
func (z *ZipArchive) AddEmptyDir(dirPath string) bool { |
||||
dirPath = strings.Replace(dirPath, "\\", "/", -1) |
||||
|
||||
if !strings.HasSuffix(dirPath, "/") { |
||||
dirPath += "/" |
||||
} |
||||
|
||||
for _, f := range z.files { |
||||
if dirPath == f.Name { |
||||
return false |
||||
} |
||||
} |
||||
|
||||
dirPath = strings.TrimSuffix(dirPath, "/") |
||||
if strings.Contains(dirPath, "/") { |
||||
// Auto add all upper level directories.
|
||||
z.AddEmptyDir(path.Dir(dirPath)) |
||||
} |
||||
z.files = append(z.files, &File{ |
||||
FileHeader: &zip.FileHeader{ |
||||
Name: dirPath + "/", |
||||
UncompressedSize: 0, |
||||
}, |
||||
}) |
||||
z.updateStat() |
||||
return true |
||||
} |
||||
|
||||
// AddDir adds a directory and subdirectories entries to ZipArchive.
|
||||
func (z *ZipArchive) AddDir(dirPath, absPath string) error { |
||||
dir, err := os.Open(absPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer dir.Close() |
||||
|
||||
// Make sure we have all upper level directories.
|
||||
z.AddEmptyDir(dirPath) |
||||
|
||||
fis, err := dir.Readdir(0) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
for _, fi := range fis { |
||||
curPath := absPath + "/" + fi.Name() |
||||
tmpRecPath := path.Join(dirPath, fi.Name()) |
||||
if fi.IsDir() { |
||||
if err = z.AddDir(tmpRecPath, curPath); err != nil { |
||||
return err |
||||
} |
||||
} else { |
||||
if err = z.AddFile(tmpRecPath, curPath); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// updateStat should be called after every change for rebuilding statistic.
|
||||
func (z *ZipArchive) updateStat() { |
||||
z.NumFiles = len(z.files) |
||||
z.isHasChanged = true |
||||
} |
||||
|
||||
// AddFile adds a file entry to ZipArchive.
|
||||
func (z *ZipArchive) AddFile(fileName, absPath string) error { |
||||
fileName = strings.Replace(fileName, "\\", "/", -1) |
||||
absPath = strings.Replace(absPath, "\\", "/", -1) |
||||
|
||||
if cae.IsFilter(absPath) { |
||||
return nil |
||||
} |
||||
|
||||
f, err := os.Open(absPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer f.Close() |
||||
|
||||
fi, err := f.Stat() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
file := new(File) |
||||
file.FileHeader, err = zip.FileInfoHeader(fi) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
file.Name = fileName |
||||
file.absPath = absPath |
||||
|
||||
z.AddEmptyDir(path.Dir(fileName)) |
||||
|
||||
isExist := false |
||||
for _, f := range z.files { |
||||
if fileName == f.Name { |
||||
f = file |
||||
isExist = true |
||||
break |
||||
} |
||||
} |
||||
if !isExist { |
||||
z.files = append(z.files, file) |
||||
} |
||||
|
||||
z.updateStat() |
||||
return nil |
||||
} |
||||
|
||||
// DeleteIndex deletes an entry in the archive by its index.
|
||||
func (z *ZipArchive) DeleteIndex(idx int) error { |
||||
if idx >= z.NumFiles { |
||||
return errors.New("index out of range of number of files") |
||||
} |
||||
|
||||
z.files = append(z.files[:idx], z.files[idx+1:]...) |
||||
return nil |
||||
} |
||||
|
||||
// DeleteName deletes an entry in the archive by its name.
|
||||
func (z *ZipArchive) DeleteName(name string) error { |
||||
for i, f := range z.files { |
||||
if f.Name == name { |
||||
return z.DeleteIndex(i) |
||||
} |
||||
} |
||||
return errors.New("entry with given name not found") |
||||
} |
@ -0,0 +1,191 @@ |
||||
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: |
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of |
||||
this License; and |
||||
You must cause any modified files to carry prominent notices stating that You |
||||
changed the files; and |
||||
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 |
||||
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. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work |
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate |
||||
notice, with the fields enclosed by brackets "[]" replaced with your own |
||||
identifying information. (Don't include the brackets!) The text should be |
||||
enclosed in the appropriate comment syntax for the file format. We also |
||||
recommend that a file or class name and description of purpose be included on |
||||
the same "printed page" as the copyright notice for easier identification within |
||||
third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
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. |
@ -0,0 +1,20 @@ |
||||
Common Functions |
||||
================ |
||||
|
||||
[![Build Status](https://travis-ci.org/Unknwon/com.svg)](https://travis-ci.org/Unknwon/com) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/Unknwon/com) |
||||
|
||||
This is an open source project for commonly used functions for the Go programming language. |
||||
|
||||
This package need >= **go 1.2** |
||||
|
||||
Code Convention: based on [Go Code Convention](https://github.com/Unknwon/go-code-convention). |
||||
|
||||
## Contribute |
||||
|
||||
Your contribute is welcome, but you have to check following steps after you added some functions and commit them: |
||||
|
||||
1. Make sure you wrote user-friendly comments for **all functions** . |
||||
2. Make sure you wrote test cases with any possible condition for **all functions** in file `*_test.go`. |
||||
3. Make sure you wrote benchmarks for **all functions** in file `*_test.go`. |
||||
4. Make sure you wrote useful examples for **all functions** in file `example_test.go`. |
||||
5. Make sure you ran `go test` and got **PASS** . |
@ -0,0 +1,161 @@ |
||||
// +build go1.2
|
||||
|
||||
// Copyright 2013 com authors
|
||||
//
|
||||
// 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 com is an open source project for commonly used functions for the Go programming language.
|
||||
package com |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"os/exec" |
||||
"runtime" |
||||
"strings" |
||||
) |
||||
|
||||
// ExecCmdDirBytes executes system command in given directory
|
||||
// and return stdout, stderr in bytes type, along with possible error.
|
||||
func ExecCmdDirBytes(dir, cmdName string, args ...string) ([]byte, []byte, error) { |
||||
bufOut := new(bytes.Buffer) |
||||
bufErr := new(bytes.Buffer) |
||||
|
||||
cmd := exec.Command(cmdName, args...) |
||||
cmd.Dir = dir |
||||
cmd.Stdout = bufOut |
||||
cmd.Stderr = bufErr |
||||
|
||||
err := cmd.Run() |
||||
return bufOut.Bytes(), bufErr.Bytes(), err |
||||
} |
||||
|
||||
// ExecCmdBytes executes system command
|
||||
// and return stdout, stderr in bytes type, along with possible error.
|
||||
func ExecCmdBytes(cmdName string, args ...string) ([]byte, []byte, error) { |
||||
return ExecCmdDirBytes("", cmdName, args...) |
||||
} |
||||
|
||||
// ExecCmdDir executes system command in given directory
|
||||
// and return stdout, stderr in string type, along with possible error.
|
||||
func ExecCmdDir(dir, cmdName string, args ...string) (string, string, error) { |
||||
bufOut, bufErr, err := ExecCmdDirBytes(dir, cmdName, args...) |
||||
return string(bufOut), string(bufErr), err |
||||
} |
||||
|
||||
// ExecCmd executes system command
|
||||
// and return stdout, stderr in string type, along with possible error.
|
||||
func ExecCmd(cmdName string, args ...string) (string, string, error) { |
||||
return ExecCmdDir("", cmdName, args...) |
||||
} |
||||
|
||||
// _________ .__ .____
|
||||
// \_ ___ \ ____ | | ___________ | | ____ ____
|
||||
// / \ \/ / _ \| | / _ \_ __ \ | | / _ \ / ___\
|
||||
// \ \___( <_> ) |_( <_> ) | \/ | |__( <_> ) /_/ >
|
||||
// \______ /\____/|____/\____/|__| |_______ \____/\___ /
|
||||
// \/ \/ /_____/
|
||||
|
||||
// Color number constants.
|
||||
const ( |
||||
Gray = uint8(iota + 90) |
||||
Red |
||||
Green |
||||
Yellow |
||||
Blue |
||||
Magenta |
||||
//NRed = uint8(31) // Normal
|
||||
EndColor = "\033[0m" |
||||
) |
||||
|
||||
// getColorLevel returns colored level string by given level.
|
||||
func getColorLevel(level string) string { |
||||
level = strings.ToUpper(level) |
||||
switch level { |
||||
case "TRAC": |
||||
return fmt.Sprintf("\033[%dm%s\033[0m", Blue, level) |
||||
case "ERRO": |
||||
return fmt.Sprintf("\033[%dm%s\033[0m", Red, level) |
||||
case "WARN": |
||||
return fmt.Sprintf("\033[%dm%s\033[0m", Magenta, level) |
||||
case "SUCC": |
||||
return fmt.Sprintf("\033[%dm%s\033[0m", Green, level) |
||||
default: |
||||
return level |
||||
} |
||||
} |
||||
|
||||
// ColorLogS colors log and return colored content.
|
||||
// Log format: <level> <content [highlight][path]> [ error ].
|
||||
// Level: TRAC -> blue; ERRO -> red; WARN -> Magenta; SUCC -> green; others -> default.
|
||||
// Content: default; path: yellow; error -> red.
|
||||
// Level has to be surrounded by "[" and "]".
|
||||
// Highlights have to be surrounded by "# " and " #"(space), "#" will be deleted.
|
||||
// Paths have to be surrounded by "( " and " )"(space).
|
||||
// Errors have to be surrounded by "[ " and " ]"(space).
|
||||
// Note: it hasn't support windows yet, contribute is welcome.
|
||||
func ColorLogS(format string, a ...interface{}) string { |
||||
log := fmt.Sprintf(format, a...) |
||||
|
||||
var clog string |
||||
|
||||
if runtime.GOOS != "windows" { |
||||
// Level.
|
||||
i := strings.Index(log, "]") |
||||
if log[0] == '[' && i > -1 { |
||||
clog += "[" + getColorLevel(log[1:i]) + "]" |
||||
} |
||||
|
||||
log = log[i+1:] |
||||
|
||||
// Error.
|
||||
log = strings.Replace(log, "[ ", fmt.Sprintf("[\033[%dm", Red), -1) |
||||
log = strings.Replace(log, " ]", EndColor+"]", -1) |
||||
|
||||
// Path.
|
||||
log = strings.Replace(log, "( ", fmt.Sprintf("(\033[%dm", Yellow), -1) |
||||
log = strings.Replace(log, " )", EndColor+")", -1) |
||||
|
||||
// Highlights.
|
||||
log = strings.Replace(log, "# ", fmt.Sprintf("\033[%dm", Gray), -1) |
||||
log = strings.Replace(log, " #", EndColor, -1) |
||||
|
||||
} else { |
||||
// Level.
|
||||
i := strings.Index(log, "]") |
||||
if log[0] == '[' && i > -1 { |
||||
clog += "[" + log[1:i] + "]" |
||||
} |
||||
|
||||
log = log[i+1:] |
||||
|
||||
// Error.
|
||||
log = strings.Replace(log, "[ ", "[", -1) |
||||
log = strings.Replace(log, " ]", "]", -1) |
||||
|
||||
// Path.
|
||||
log = strings.Replace(log, "( ", "(", -1) |
||||
log = strings.Replace(log, " )", ")", -1) |
||||
|
||||
// Highlights.
|
||||
log = strings.Replace(log, "# ", "", -1) |
||||
log = strings.Replace(log, " #", "", -1) |
||||
} |
||||
return clog + log |
||||
} |
||||
|
||||
// ColorLog prints colored log to stdout.
|
||||
// See color rules in function 'ColorLogS'.
|
||||
func ColorLog(format string, a ...interface{}) { |
||||
fmt.Print(ColorLogS(format, a...)) |
||||
} |
@ -0,0 +1,157 @@ |
||||
// Copyright 2014 com authors
|
||||
//
|
||||
// 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 com |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strconv" |
||||
) |
||||
|
||||
// Convert string to specify type.
|
||||
type StrTo string |
||||
|
||||
func (f StrTo) Exist() bool { |
||||
return string(f) != string(0x1E) |
||||
} |
||||
|
||||
func (f StrTo) Uint8() (uint8, error) { |
||||
v, err := strconv.ParseUint(f.String(), 10, 8) |
||||
return uint8(v), err |
||||
} |
||||
|
||||
func (f StrTo) Int() (int, error) { |
||||
v, err := strconv.ParseInt(f.String(), 10, 0) |
||||
return int(v), err |
||||
} |
||||
|
||||
func (f StrTo) Int64() (int64, error) { |
||||
v, err := strconv.ParseInt(f.String(), 10, 64) |
||||
return int64(v), err |
||||
} |
||||
|
||||
func (f StrTo) MustUint8() uint8 { |
||||
v, _ := f.Uint8() |
||||
return v |
||||
} |
||||
|
||||
func (f StrTo) MustInt() int { |
||||
v, _ := f.Int() |
||||
return v |
||||
} |
||||
|
||||
func (f StrTo) MustInt64() int64 { |
||||
v, _ := f.Int64() |
||||
return v |
||||
} |
||||
|
||||
func (f StrTo) String() string { |
||||
if f.Exist() { |
||||
return string(f) |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
// Convert any type to string.
|
||||
func ToStr(value interface{}, args ...int) (s string) { |
||||
switch v := value.(type) { |
||||
case bool: |
||||
s = strconv.FormatBool(v) |
||||
case float32: |
||||
s = strconv.FormatFloat(float64(v), 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 32)) |
||||
case float64: |
||||
s = strconv.FormatFloat(v, 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 64)) |
||||
case int: |
||||
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) |
||||
case int8: |
||||
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) |
||||
case int16: |
||||
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) |
||||
case int32: |
||||
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) |
||||
case int64: |
||||
s = strconv.FormatInt(v, argInt(args).Get(0, 10)) |
||||
case uint: |
||||
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) |
||||
case uint8: |
||||
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) |
||||
case uint16: |
||||
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) |
||||
case uint32: |
||||
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) |
||||
case uint64: |
||||
s = strconv.FormatUint(v, argInt(args).Get(0, 10)) |
||||
case string: |
||||
s = v |
||||
case []byte: |
||||
s = string(v) |
||||
default: |
||||
s = fmt.Sprintf("%v", v) |
||||
} |
||||
return s |
||||
} |
||||
|
||||
type argInt []int |
||||
|
||||
func (a argInt) Get(i int, args ...int) (r int) { |
||||
if i >= 0 && i < len(a) { |
||||
r = a[i] |
||||
} else if len(args) > 0 { |
||||
r = args[0] |
||||
} |
||||
return |
||||
} |
||||
|
||||
// HexStr2int converts hex format string to decimal number.
|
||||
func HexStr2int(hexStr string) (int, error) { |
||||
num := 0 |
||||
length := len(hexStr) |
||||
for i := 0; i < length; i++ { |
||||
char := hexStr[length-i-1] |
||||
factor := -1 |
||||
|
||||
switch { |
||||
case char >= '0' && char <= '9': |
||||
factor = int(char) - '0' |
||||
case char >= 'a' && char <= 'f': |
||||
factor = int(char) - 'a' + 10 |
||||
default: |
||||
return -1, fmt.Errorf("invalid hex: %s", string(char)) |
||||
} |
||||
|
||||
num += factor * PowInt(16, i) |
||||
} |
||||
return num, nil |
||||
} |
||||
|
||||
// Int2HexStr converts decimal number to hex format string.
|
||||
func Int2HexStr(num int) (hex string) { |
||||
if num == 0 { |
||||
return "0" |
||||
} |
||||
|
||||
for num > 0 { |
||||
r := num % 16 |
||||
|
||||
c := "?" |
||||
if r >= 0 && r <= 9 { |
||||
c = string(r + '0') |
||||
} else { |
||||
c = string(r + 'a' - 10) |
||||
} |
||||
hex = c + hex |
||||
num = num / 16 |
||||
} |
||||
return hex |
||||
} |
@ -0,0 +1,173 @@ |
||||
// Copyright 2013 com authors
|
||||
//
|
||||
// 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 com |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"os" |
||||
"path" |
||||
"strings" |
||||
) |
||||
|
||||
// IsDir returns true if given path is a directory,
|
||||
// or returns false when it's a file or does not exist.
|
||||
func IsDir(dir string) bool { |
||||
f, e := os.Stat(dir) |
||||
if e != nil { |
||||
return false |
||||
} |
||||
return f.IsDir() |
||||
} |
||||
|
||||
func statDir(dirPath, recPath string, includeDir, isDirOnly bool) ([]string, error) { |
||||
dir, err := os.Open(dirPath) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer dir.Close() |
||||
|
||||
fis, err := dir.Readdir(0) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
statList := make([]string, 0) |
||||
for _, fi := range fis { |
||||
if strings.Contains(fi.Name(), ".DS_Store") { |
||||
continue |
||||
} |
||||
|
||||
relPath := path.Join(recPath, fi.Name()) |
||||
curPath := path.Join(dirPath, fi.Name()) |
||||
if fi.IsDir() { |
||||
if includeDir { |
||||
statList = append(statList, relPath+"/") |
||||
} |
||||
s, err := statDir(curPath, relPath, includeDir, isDirOnly) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
statList = append(statList, s...) |
||||
} else if !isDirOnly { |
||||
statList = append(statList, relPath) |
||||
} |
||||
} |
||||
return statList, nil |
||||
} |
||||
|
||||
// StatDir gathers information of given directory by depth-first.
|
||||
// It returns slice of file list and includes subdirectories if enabled;
|
||||
// it returns error and nil slice when error occurs in underlying functions,
|
||||
// or given path is not a directory or does not exist.
|
||||
//
|
||||
// Slice does not include given path itself.
|
||||
// If subdirectories is enabled, they will have suffix '/'.
|
||||
func StatDir(rootPath string, includeDir ...bool) ([]string, error) { |
||||
if !IsDir(rootPath) { |
||||
return nil, errors.New("not a directory or does not exist: " + rootPath) |
||||
} |
||||
|
||||
isIncludeDir := false |
||||
if len(includeDir) >= 1 { |
||||
isIncludeDir = includeDir[0] |
||||
} |
||||
return statDir(rootPath, "", isIncludeDir, false) |
||||
} |
||||
|
||||
// GetAllSubDirs returns all subdirectories of given root path.
|
||||
// Slice does not include given path itself.
|
||||
func GetAllSubDirs(rootPath string) ([]string, error) { |
||||
if !IsDir(rootPath) { |
||||
return nil, errors.New("not a directory or does not exist: " + rootPath) |
||||
} |
||||
return statDir(rootPath, "", true, true) |
||||
} |
||||
|
||||
// GetFileListBySuffix returns an ordered list of file paths.
|
||||
// It recognize if given path is a file, and don't do recursive find.
|
||||
func GetFileListBySuffix(dirPath, suffix string) ([]string, error) { |
||||
if !IsExist(dirPath) { |
||||
return nil, fmt.Errorf("given path does not exist: %s", dirPath) |
||||
} else if IsFile(dirPath) { |
||||
return []string{dirPath}, nil |
||||
} |
||||
|
||||
// Given path is a directory.
|
||||
dir, err := os.Open(dirPath) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
fis, err := dir.Readdir(0) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
files := make([]string, 0, len(fis)) |
||||
for _, fi := range fis { |
||||
if strings.HasSuffix(fi.Name(), suffix) { |
||||
files = append(files, path.Join(dirPath, fi.Name())) |
||||
} |
||||
} |
||||
|
||||
return files, nil |
||||
} |
||||
|
||||
// CopyDir copy files recursively from source to target directory.
|
||||
//
|
||||
// The filter accepts a function that process the path info.
|
||||
// and should return true for need to filter.
|
||||
//
|
||||
// It returns error when error occurs in underlying functions.
|
||||
func CopyDir(srcPath, destPath string, filters ...func(filePath string) bool) error { |
||||
// Check if target directory exists.
|
||||
if IsExist(destPath) { |
||||
return errors.New("file or directory alreay exists: " + destPath) |
||||
} |
||||
|
||||
err := os.MkdirAll(destPath, os.ModePerm) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Gather directory info.
|
||||
infos, err := StatDir(srcPath, true) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
var filter func(filePath string) bool |
||||
if len(filters) > 0 { |
||||
filter = filters[0] |
||||
} |
||||
|
||||
for _, info := range infos { |
||||
if filter != nil && filter(info) { |
||||
continue |
||||
} |
||||
|
||||
curPath := path.Join(destPath, info) |
||||
if strings.HasSuffix(info, "/") { |
||||
err = os.MkdirAll(curPath, os.ModePerm) |
||||
} else { |
||||
err = Copy(path.Join(srcPath, info), curPath) |
||||
} |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,145 @@ |
||||
// Copyright 2013 com authors
|
||||
//
|
||||
// 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 com |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
"io/ioutil" |
||||
"math" |
||||
"os" |
||||
"path" |
||||
) |
||||
|
||||
// Storage unit constants.
|
||||
const ( |
||||
Byte = 1 |
||||
KByte = Byte * 1024 |
||||
MByte = KByte * 1024 |
||||
GByte = MByte * 1024 |
||||
TByte = GByte * 1024 |
||||
PByte = TByte * 1024 |
||||
EByte = PByte * 1024 |
||||
) |
||||
|
||||
func logn(n, b float64) float64 { |
||||
return math.Log(n) / math.Log(b) |
||||
} |
||||
|
||||
func humanateBytes(s uint64, base float64, sizes []string) string { |
||||
if s < 10 { |
||||
return fmt.Sprintf("%dB", s) |
||||
} |
||||
e := math.Floor(logn(float64(s), base)) |
||||
suffix := sizes[int(e)] |
||||
val := float64(s) / math.Pow(base, math.Floor(e)) |
||||
f := "%.0f" |
||||
if val < 10 { |
||||
f = "%.1f" |
||||
} |
||||
|
||||
return fmt.Sprintf(f+"%s", val, suffix) |
||||
} |
||||
|
||||
// HumaneFileSize calculates the file size and generate user-friendly string.
|
||||
func HumaneFileSize(s uint64) string { |
||||
sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"} |
||||
return humanateBytes(s, 1024, sizes) |
||||
} |
||||
|
||||
// FileMTime returns file modified time and possible error.
|
||||
func FileMTime(file string) (int64, error) { |
||||
f, err := os.Stat(file) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
return f.ModTime().Unix(), nil |
||||
} |
||||
|
||||
// FileSize returns file size in bytes and possible error.
|
||||
func FileSize(file string) (int64, error) { |
||||
f, err := os.Stat(file) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
return f.Size(), nil |
||||
} |
||||
|
||||
// Copy copies file from source to target path.
|
||||
func Copy(src, dest string) error { |
||||
// Gather file information to set back later.
|
||||
si, err := os.Lstat(src) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Handle symbolic link.
|
||||
if si.Mode()&os.ModeSymlink != 0 { |
||||
target, err := os.Readlink(src) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
// NOTE: os.Chmod and os.Chtimes don't recoganize symbolic link,
|
||||
// which will lead "no such file or directory" error.
|
||||
return os.Symlink(target, dest) |
||||
} |
||||
|
||||
sr, err := os.Open(src) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer sr.Close() |
||||
|
||||
dw, err := os.Create(dest) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer dw.Close() |
||||
|
||||
if _, err = io.Copy(dw, sr); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Set back file information.
|
||||
if err = os.Chtimes(dest, si.ModTime(), si.ModTime()); err != nil { |
||||
return err |
||||
} |
||||
return os.Chmod(dest, si.Mode()) |
||||
} |
||||
|
||||
// WriteFile writes data to a file named by filename.
|
||||
// If the file does not exist, WriteFile creates it
|
||||
// and its upper level paths.
|
||||
func WriteFile(filename string, data []byte) error { |
||||
os.MkdirAll(path.Dir(filename), os.ModePerm) |
||||
return ioutil.WriteFile(filename, data, 0655) |
||||
} |
||||
|
||||
// IsFile returns true if given path is a file,
|
||||
// or returns false when it's a directory or does not exist.
|
||||
func IsFile(filePath string) bool { |
||||
f, e := os.Stat(filePath) |
||||
if e != nil { |
||||
return false |
||||
} |
||||
return !f.IsDir() |
||||
} |
||||
|
||||
// IsExist checks whether a file or directory exists.
|
||||
// It returns false when the file or directory does not exist.
|
||||
func IsExist(path string) bool { |
||||
_, err := os.Stat(path) |
||||
return err == nil || os.IsExist(err) |
||||
} |
@ -0,0 +1,60 @@ |
||||
// Copyright 2013 com authors
|
||||
//
|
||||
// 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 com |
||||
|
||||
import ( |
||||
"html" |
||||
"regexp" |
||||
"strings" |
||||
) |
||||
|
||||
// Html2JS converts []byte type of HTML content into JS format.
|
||||
func Html2JS(data []byte) []byte { |
||||
s := string(data) |
||||
s = strings.Replace(s, `\`, `\\`, -1) |
||||
s = strings.Replace(s, "\n", `\n`, -1) |
||||
s = strings.Replace(s, "\r", "", -1) |
||||
s = strings.Replace(s, "\"", `\"`, -1) |
||||
s = strings.Replace(s, "<table>", "<table>", -1) |
||||
return []byte(s) |
||||
} |
||||
|
||||
// encode html chars to string
|
||||
func HtmlEncode(str string) string { |
||||
return html.EscapeString(str) |
||||
} |
||||
|
||||
// decode string to html chars
|
||||
func HtmlDecode(str string) string { |
||||
return html.UnescapeString(str) |
||||
} |
||||
|
||||
// strip tags in html string
|
||||
func StripTags(src string) string { |
||||
//去除style,script,html tag
|
||||
re := regexp.MustCompile(`(?s)<(?:style|script)[^<>]*>.*?</(?:style|script)>|</?[a-z][a-z0-9]*[^<>]*>|<!--.*?-->`) |
||||
src = re.ReplaceAllString(src, "") |
||||
|
||||
//trim all spaces(2+) into \n
|
||||
re = regexp.MustCompile(`\s{2,}`) |
||||
src = re.ReplaceAllString(src, "\n") |
||||
|
||||
return strings.TrimSpace(src) |
||||
} |
||||
|
||||
// change \n to <br/>
|
||||
func Nl2br(str string) string { |
||||
return strings.Replace(str, "\n", "<br/>", -1) |
||||
} |
@ -0,0 +1,201 @@ |
||||
// Copyright 2013 com authors
|
||||
//
|
||||
// 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 com |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"os" |
||||
"path" |
||||
) |
||||
|
||||
type NotFoundError struct { |
||||
Message string |
||||
} |
||||
|
||||
func (e NotFoundError) Error() string { |
||||
return e.Message |
||||
} |
||||
|
||||
type RemoteError struct { |
||||
Host string |
||||
Err error |
||||
} |
||||
|
||||
func (e *RemoteError) Error() string { |
||||
return e.Err.Error() |
||||
} |
||||
|
||||
var UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1541.0 Safari/537.36" |
||||
|
||||
// HttpCall makes HTTP method call.
|
||||
func HttpCall(client *http.Client, method, url string, header http.Header, body io.Reader) (io.ReadCloser, error) { |
||||
req, err := http.NewRequest(method, url, body) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
req.Header.Set("User-Agent", UserAgent) |
||||
for k, vs := range header { |
||||
req.Header[k] = vs |
||||
} |
||||
resp, err := client.Do(req) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if resp.StatusCode == 200 { |
||||
return resp.Body, nil |
||||
} |
||||
resp.Body.Close() |
||||
if resp.StatusCode == 404 { // 403 can be rate limit error. || resp.StatusCode == 403 {
|
||||
err = fmt.Errorf("resource not found: %s", url) |
||||
} else { |
||||
err = fmt.Errorf("%s %s -> %d", method, url, resp.StatusCode) |
||||
} |
||||
return nil, err |
||||
} |
||||
|
||||
// HttpGet gets the specified resource.
|
||||
// ErrNotFound is returned if the server responds with status 404.
|
||||
func HttpGet(client *http.Client, url string, header http.Header) (io.ReadCloser, error) { |
||||
return HttpCall(client, "GET", url, header, nil) |
||||
} |
||||
|
||||
// HttpPost posts the specified resource.
|
||||
// ErrNotFound is returned if the server responds with status 404.
|
||||
func HttpPost(client *http.Client, url string, header http.Header, body []byte) (io.ReadCloser, error) { |
||||
return HttpCall(client, "POST", url, header, bytes.NewBuffer(body)) |
||||
} |
||||
|
||||
// HttpGetToFile gets the specified resource and writes to file.
|
||||
// ErrNotFound is returned if the server responds with status 404.
|
||||
func HttpGetToFile(client *http.Client, url string, header http.Header, fileName string) error { |
||||
rc, err := HttpGet(client, url, header) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer rc.Close() |
||||
|
||||
os.MkdirAll(path.Dir(fileName), os.ModePerm) |
||||
f, err := os.Create(fileName) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer f.Close() |
||||
_, err = io.Copy(f, rc) |
||||
return err |
||||
} |
||||
|
||||
// HttpGetBytes gets the specified resource. ErrNotFound is returned if the server
|
||||
// responds with status 404.
|
||||
func HttpGetBytes(client *http.Client, url string, header http.Header) ([]byte, error) { |
||||
rc, err := HttpGet(client, url, header) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer rc.Close() |
||||
return ioutil.ReadAll(rc) |
||||
} |
||||
|
||||
// HttpGetJSON gets the specified resource and mapping to struct.
|
||||
// ErrNotFound is returned if the server responds with status 404.
|
||||
func HttpGetJSON(client *http.Client, url string, v interface{}) error { |
||||
rc, err := HttpGet(client, url, nil) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer rc.Close() |
||||
err = json.NewDecoder(rc).Decode(v) |
||||
if _, ok := err.(*json.SyntaxError); ok { |
||||
return fmt.Errorf("JSON syntax error at %s", url) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// HttpPostJSON posts the specified resource with struct values,
|
||||
// and maps results to struct.
|
||||
// ErrNotFound is returned if the server responds with status 404.
|
||||
func HttpPostJSON(client *http.Client, url string, body, v interface{}) error { |
||||
data, err := json.Marshal(body) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
rc, err := HttpPost(client, url, http.Header{"content-type": []string{"application/json"}}, data) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer rc.Close() |
||||
err = json.NewDecoder(rc).Decode(v) |
||||
if _, ok := err.(*json.SyntaxError); ok { |
||||
return fmt.Errorf("JSON syntax error at %s", url) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// A RawFile describes a file that can be downloaded.
|
||||
type RawFile interface { |
||||
Name() string |
||||
RawUrl() string |
||||
Data() []byte |
||||
SetData([]byte) |
||||
} |
||||
|
||||
// FetchFiles fetches files specified by the rawURL field in parallel.
|
||||
func FetchFiles(client *http.Client, files []RawFile, header http.Header) error { |
||||
ch := make(chan error, len(files)) |
||||
for i := range files { |
||||
go func(i int) { |
||||
p, err := HttpGetBytes(client, files[i].RawUrl(), nil) |
||||
if err != nil { |
||||
ch <- err |
||||
return |
||||
} |
||||
files[i].SetData(p) |
||||
ch <- nil |
||||
}(i) |
||||
} |
||||
for _ = range files { |
||||
if err := <-ch; err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// FetchFiles uses command `curl` to fetch files specified by the rawURL field in parallel.
|
||||
func FetchFilesCurl(files []RawFile, curlOptions ...string) error { |
||||
ch := make(chan error, len(files)) |
||||
for i := range files { |
||||
go func(i int) { |
||||
stdout, _, err := ExecCmd("curl", append(curlOptions, files[i].RawUrl())...) |
||||
if err != nil { |
||||
ch <- err |
||||
return |
||||
} |
||||
|
||||
files[i].SetData([]byte(stdout)) |
||||
ch <- nil |
||||
}(i) |
||||
} |
||||
for _ = range files { |
||||
if err := <-ch; err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,29 @@ |
||||
// Copyright 2014 com authors
|
||||
//
|
||||
// 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 com |
||||
|
||||
// PowInt is int type of math.Pow function.
|
||||
func PowInt(x int, y int) int { |
||||
if y <= 0 { |
||||
return 1 |
||||
} else { |
||||
if y % 2 == 0 { |
||||
sqrt := PowInt(x, y/2) |
||||
return sqrt * sqrt |
||||
} else { |
||||
return PowInt(x, y-1) * x |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,80 @@ |
||||
// Copyright 2013 com authors
|
||||
//
|
||||
// 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 com |
||||
|
||||
import ( |
||||
"errors" |
||||
"os" |
||||
"path/filepath" |
||||
"runtime" |
||||
"strings" |
||||
) |
||||
|
||||
// GetGOPATHs returns all paths in GOPATH variable.
|
||||
func GetGOPATHs() []string { |
||||
gopath := os.Getenv("GOPATH") |
||||
var paths []string |
||||
if runtime.GOOS == "windows" { |
||||
gopath = strings.Replace(gopath, "\\", "/", -1) |
||||
paths = strings.Split(gopath, ";") |
||||
} else { |
||||
paths = strings.Split(gopath, ":") |
||||
} |
||||
return paths |
||||
} |
||||
|
||||
// GetSrcPath returns app. source code path.
|
||||
// It only works when you have src. folder in GOPATH,
|
||||
// it returns error not able to locate source folder path.
|
||||
func GetSrcPath(importPath string) (appPath string, err error) { |
||||
paths := GetGOPATHs() |
||||
for _, p := range paths { |
||||
if IsExist(p + "/src/" + importPath + "/") { |
||||
appPath = p + "/src/" + importPath + "/" |
||||
break |
||||
} |
||||
} |
||||
|
||||
if len(appPath) == 0 { |
||||
return "", errors.New("Unable to locate source folder path") |
||||
} |
||||
|
||||
appPath = filepath.Dir(appPath) + "/" |
||||
if runtime.GOOS == "windows" { |
||||
// Replace all '\' to '/'.
|
||||
appPath = strings.Replace(appPath, "\\", "/", -1) |
||||
} |
||||
|
||||
return appPath, nil |
||||
} |
||||
|
||||
// HomeDir returns path of '~'(in Linux) on Windows,
|
||||
// it returns error when the variable does not exist.
|
||||
func HomeDir() (home string, err error) { |
||||
if runtime.GOOS == "windows" { |
||||
home = os.Getenv("USERPROFILE") |
||||
if len(home) == 0 { |
||||
home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") |
||||
} |
||||
} else { |
||||
home = os.Getenv("HOME") |
||||
} |
||||
|
||||
if len(home) == 0 { |
||||
return "", errors.New("Cannot specify home directory because it's empty") |
||||
} |
||||
|
||||
return home, nil |
||||
} |
@ -0,0 +1,56 @@ |
||||
// Copyright 2013 com authors
|
||||
//
|
||||
// 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 com |
||||
|
||||
import "regexp" |
||||
|
||||
const ( |
||||
regex_email_pattern = `(?i)[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}` |
||||
regex_strict_email_pattern = `(?i)[A-Z0-9!#$%&'*+/=?^_{|}~-]+` + |
||||
`(?:\.[A-Z0-9!#$%&'*+/=?^_{|}~-]+)*` + |
||||
`@(?:[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?\.)+` + |
||||
`[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?` |
||||
regex_url_pattern = `(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?` |
||||
) |
||||
|
||||
var ( |
||||
regex_email *regexp.Regexp |
||||
regex_strict_email *regexp.Regexp |
||||
regex_url *regexp.Regexp |
||||
) |
||||
|
||||
func init() { |
||||
regex_email = regexp.MustCompile(regex_email_pattern) |
||||
regex_strict_email = regexp.MustCompile(regex_strict_email_pattern) |
||||
regex_url = regexp.MustCompile(regex_url_pattern) |
||||
} |
||||
|
||||
// validate string is an email address, if not return false
|
||||
// basically validation can match 99% cases
|
||||
func IsEmail(email string) bool { |
||||
return regex_email.MatchString(email) |
||||
} |
||||
|
||||
// validate string is an email address, if not return false
|
||||
// this validation omits RFC 2822
|
||||
func IsEmailRFC(email string) bool { |
||||
return regex_strict_email.MatchString(email) |
||||
} |
||||
|
||||
// validate string is a url link, if not return false
|
||||
// simple validation can match 99% cases
|
||||
func IsUrl(url string) bool { |
||||
return regex_url.MatchString(url) |
||||
} |
@ -0,0 +1,87 @@ |
||||
// Copyright 2013 com authors
|
||||
//
|
||||
// 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 com |
||||
|
||||
import ( |
||||
"strings" |
||||
) |
||||
|
||||
// AppendStr appends string to slice with no duplicates.
|
||||
func AppendStr(strs []string, str string) []string { |
||||
for _, s := range strs { |
||||
if s == str { |
||||
return strs |
||||
} |
||||
} |
||||
return append(strs, str) |
||||
} |
||||
|
||||
// CompareSliceStr compares two 'string' type slices.
|
||||
// It returns true if elements and order are both the same.
|
||||
func CompareSliceStr(s1, s2 []string) bool { |
||||
if len(s1) != len(s2) { |
||||
return false |
||||
} |
||||
|
||||
for i := range s1 { |
||||
if s1[i] != s2[i] { |
||||
return false |
||||
} |
||||
} |
||||
|
||||
return true |
||||
} |
||||
|
||||
// CompareSliceStr compares two 'string' type slices.
|
||||
// It returns true if elements are the same, and ignores the order.
|
||||
func CompareSliceStrU(s1, s2 []string) bool { |
||||
if len(s1) != len(s2) { |
||||
return false |
||||
} |
||||
|
||||
for i := range s1 { |
||||
for j := len(s2) - 1; j >= 0; j-- { |
||||
if s1[i] == s2[j] { |
||||
s2 = append(s2[:j], s2[j+1:]...) |
||||
break |
||||
} |
||||
} |
||||
} |
||||
if len(s2) > 0 { |
||||
return false |
||||
} |
||||
return true |
||||
} |
||||
|
||||
// IsSliceContainsStr returns true if the string exists in given slice, ignore case.
|
||||
func IsSliceContainsStr(sl []string, str string) bool { |
||||
str = strings.ToLower(str) |
||||
for _, s := range sl { |
||||
if strings.ToLower(s) == str { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// IsSliceContainsInt64 returns true if the int64 exists in given slice.
|
||||
func IsSliceContainsInt64(sl []int64, i int64) bool { |
||||
for _, s := range sl { |
||||
if s == i { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
@ -0,0 +1,243 @@ |
||||
// Copyright 2013 com authors
|
||||
//
|
||||
// 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 com |
||||
|
||||
import ( |
||||
"bytes" |
||||
"crypto/aes" |
||||
"crypto/cipher" |
||||
"crypto/rand" |
||||
"encoding/base64" |
||||
"errors" |
||||
"io" |
||||
r "math/rand" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
"unicode" |
||||
"unicode/utf8" |
||||
) |
||||
|
||||
// AESEncrypt encrypts text and given key with AES.
|
||||
func AESEncrypt(key, text []byte) ([]byte, error) { |
||||
block, err := aes.NewCipher(key) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
b := base64.StdEncoding.EncodeToString(text) |
||||
ciphertext := make([]byte, aes.BlockSize+len(b)) |
||||
iv := ciphertext[:aes.BlockSize] |
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil { |
||||
return nil, err |
||||
} |
||||
cfb := cipher.NewCFBEncrypter(block, iv) |
||||
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b)) |
||||
return ciphertext, nil |
||||
} |
||||
|
||||
// AESDecrypt decrypts text and given key with AES.
|
||||
func AESDecrypt(key, text []byte) ([]byte, error) { |
||||
block, err := aes.NewCipher(key) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if len(text) < aes.BlockSize { |
||||
return nil, errors.New("ciphertext too short") |
||||
} |
||||
iv := text[:aes.BlockSize] |
||||
text = text[aes.BlockSize:] |
||||
cfb := cipher.NewCFBDecrypter(block, iv) |
||||
cfb.XORKeyStream(text, text) |
||||
data, err := base64.StdEncoding.DecodeString(string(text)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return data, nil |
||||
} |
||||
|
||||
// IsLetter returns true if the 'l' is an English letter.
|
||||
func IsLetter(l uint8) bool { |
||||
n := (l | 0x20) - 'a' |
||||
if n >= 0 && n < 26 { |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// Expand replaces {k} in template with match[k] or subs[atoi(k)] if k is not in match.
|
||||
func Expand(template string, match map[string]string, subs ...string) string { |
||||
var p []byte |
||||
var i int |
||||
for { |
||||
i = strings.Index(template, "{") |
||||
if i < 0 { |
||||
break |
||||
} |
||||
p = append(p, template[:i]...) |
||||
template = template[i+1:] |
||||
i = strings.Index(template, "}") |
||||
if s, ok := match[template[:i]]; ok { |
||||
p = append(p, s...) |
||||
} else { |
||||
j, _ := strconv.Atoi(template[:i]) |
||||
if j >= len(subs) { |
||||
p = append(p, []byte("Missing")...) |
||||
} else { |
||||
p = append(p, subs[j]...) |
||||
} |
||||
} |
||||
template = template[i+1:] |
||||
} |
||||
p = append(p, template...) |
||||
return string(p) |
||||
} |
||||
|
||||
// Reverse s string, support unicode
|
||||
func Reverse(s string) string { |
||||
n := len(s) |
||||
runes := make([]rune, n) |
||||
for _, rune := range s { |
||||
n-- |
||||
runes[n] = rune |
||||
} |
||||
return string(runes[n:]) |
||||
} |
||||
|
||||
// RandomCreateBytes generate random []byte by specify chars.
|
||||
func RandomCreateBytes(n int, alphabets ...byte) []byte { |
||||
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" |
||||
var bytes = make([]byte, n) |
||||
var randby bool |
||||
if num, err := rand.Read(bytes); num != n || err != nil { |
||||
r.Seed(time.Now().UnixNano()) |
||||
randby = true |
||||
} |
||||
for i, b := range bytes { |
||||
if len(alphabets) == 0 { |
||||
if randby { |
||||
bytes[i] = alphanum[r.Intn(len(alphanum))] |
||||
} else { |
||||
bytes[i] = alphanum[b%byte(len(alphanum))] |
||||
} |
||||
} else { |
||||
if randby { |
||||
bytes[i] = alphabets[r.Intn(len(alphabets))] |
||||
} else { |
||||
bytes[i] = alphabets[b%byte(len(alphabets))] |
||||
} |
||||
} |
||||
} |
||||
return bytes |
||||
} |
||||
|
||||
// ToSnakeCase can convert all upper case characters in a string to
|
||||
// underscore format.
|
||||
//
|
||||
// Some samples.
|
||||
// "FirstName" => "first_name"
|
||||
// "HTTPServer" => "http_server"
|
||||
// "NoHTTPS" => "no_https"
|
||||
// "GO_PATH" => "go_path"
|
||||
// "GO PATH" => "go_path" // space is converted to underscore.
|
||||
// "GO-PATH" => "go_path" // hyphen is converted to underscore.
|
||||
//
|
||||
// From https://github.com/huandu/xstrings
|
||||
func ToSnakeCase(str string) string { |
||||
if len(str) == 0 { |
||||
return "" |
||||
} |
||||
|
||||
buf := &bytes.Buffer{} |
||||
var prev, r0, r1 rune |
||||
var size int |
||||
|
||||
r0 = '_' |
||||
|
||||
for len(str) > 0 { |
||||
prev = r0 |
||||
r0, size = utf8.DecodeRuneInString(str) |
||||
str = str[size:] |
||||
|
||||
switch { |
||||
case r0 == utf8.RuneError: |
||||
buf.WriteByte(byte(str[0])) |
||||
|
||||
case unicode.IsUpper(r0): |
||||
if prev != '_' { |
||||
buf.WriteRune('_') |
||||
} |
||||
|
||||
buf.WriteRune(unicode.ToLower(r0)) |
||||
|
||||
if len(str) == 0 { |
||||
break |
||||
} |
||||
|
||||
r0, size = utf8.DecodeRuneInString(str) |
||||
str = str[size:] |
||||
|
||||
if !unicode.IsUpper(r0) { |
||||
buf.WriteRune(r0) |
||||
break |
||||
} |
||||
|
||||
// find next non-upper-case character and insert `_` properly.
|
||||
// it's designed to convert `HTTPServer` to `http_server`.
|
||||
// if there are more than 2 adjacent upper case characters in a word,
|
||||
// treat them as an abbreviation plus a normal word.
|
||||
for len(str) > 0 { |
||||
r1 = r0 |
||||
r0, size = utf8.DecodeRuneInString(str) |
||||
str = str[size:] |
||||
|
||||
if r0 == utf8.RuneError { |
||||
buf.WriteRune(unicode.ToLower(r1)) |
||||
buf.WriteByte(byte(str[0])) |
||||
break |
||||
} |
||||
|
||||
if !unicode.IsUpper(r0) { |
||||
if r0 == '_' || r0 == ' ' || r0 == '-' { |
||||
r0 = '_' |
||||
|
||||
buf.WriteRune(unicode.ToLower(r1)) |
||||
} else { |
||||
buf.WriteRune('_') |
||||
buf.WriteRune(unicode.ToLower(r1)) |
||||
buf.WriteRune(r0) |
||||
} |
||||
|
||||
break |
||||
} |
||||
|
||||
buf.WriteRune(unicode.ToLower(r1)) |
||||
} |
||||
|
||||
if len(str) == 0 || r0 == '_' { |
||||
buf.WriteRune(unicode.ToLower(r0)) |
||||
break |
||||
} |
||||
|
||||
default: |
||||
if r0 == ' ' || r0 == '-' { |
||||
r0 = '_' |
||||
} |
||||
|
||||
buf.WriteRune(r0) |
||||
} |
||||
} |
||||
|
||||
return buf.String() |
||||
} |
@ -0,0 +1,115 @@ |
||||
// Copyright 2013 com authors
|
||||
//
|
||||
// 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 com |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// Format unix time int64 to string
|
||||
func Date(ti int64, format string) string { |
||||
t := time.Unix(int64(ti), 0) |
||||
return DateT(t, format) |
||||
} |
||||
|
||||
// Format unix time string to string
|
||||
func DateS(ts string, format string) string { |
||||
i, _ := strconv.ParseInt(ts, 10, 64) |
||||
return Date(i, format) |
||||
} |
||||
|
||||
// Format time.Time struct to string
|
||||
// MM - month - 01
|
||||
// M - month - 1, single bit
|
||||
// DD - day - 02
|
||||
// D - day 2
|
||||
// YYYY - year - 2006
|
||||
// YY - year - 06
|
||||
// HH - 24 hours - 03
|
||||
// H - 24 hours - 3
|
||||
// hh - 12 hours - 03
|
||||
// h - 12 hours - 3
|
||||
// mm - minute - 04
|
||||
// m - minute - 4
|
||||
// ss - second - 05
|
||||
// s - second = 5
|
||||
func DateT(t time.Time, format string) string { |
||||
res := strings.Replace(format, "MM", t.Format("01"), -1) |
||||
res = strings.Replace(res, "M", t.Format("1"), -1) |
||||
res = strings.Replace(res, "DD", t.Format("02"), -1) |
||||
res = strings.Replace(res, "D", t.Format("2"), -1) |
||||
res = strings.Replace(res, "YYYY", t.Format("2006"), -1) |
||||
res = strings.Replace(res, "YY", t.Format("06"), -1) |
||||
res = strings.Replace(res, "HH", fmt.Sprintf("%02d", t.Hour()), -1) |
||||
res = strings.Replace(res, "H", fmt.Sprintf("%d", t.Hour()), -1) |
||||
res = strings.Replace(res, "hh", t.Format("03"), -1) |
||||
res = strings.Replace(res, "h", t.Format("3"), -1) |
||||
res = strings.Replace(res, "mm", t.Format("04"), -1) |
||||
res = strings.Replace(res, "m", t.Format("4"), -1) |
||||
res = strings.Replace(res, "ss", t.Format("05"), -1) |
||||
res = strings.Replace(res, "s", t.Format("5"), -1) |
||||
return res |
||||
} |
||||
|
||||
// DateFormat pattern rules.
|
||||
var datePatterns = []string{ |
||||
// year
|
||||
"Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003
|
||||
"y", "06", //A two digit representation of a year Examples: 99 or 03
|
||||
|
||||
// month
|
||||
"m", "01", // Numeric representation of a month, with leading zeros 01 through 12
|
||||
"n", "1", // Numeric representation of a month, without leading zeros 1 through 12
|
||||
"M", "Jan", // A short textual representation of a month, three letters Jan through Dec
|
||||
"F", "January", // A full textual representation of a month, such as January or March January through December
|
||||
|
||||
// day
|
||||
"d", "02", // Day of the month, 2 digits with leading zeros 01 to 31
|
||||
"j", "2", // Day of the month without leading zeros 1 to 31
|
||||
|
||||
// week
|
||||
"D", "Mon", // A textual representation of a day, three letters Mon through Sun
|
||||
"l", "Monday", // A full textual representation of the day of the week Sunday through Saturday
|
||||
|
||||
// time
|
||||
"g", "3", // 12-hour format of an hour without leading zeros 1 through 12
|
||||
"G", "15", // 24-hour format of an hour without leading zeros 0 through 23
|
||||
"h", "03", // 12-hour format of an hour with leading zeros 01 through 12
|
||||
"H", "15", // 24-hour format of an hour with leading zeros 00 through 23
|
||||
|
||||
"a", "pm", // Lowercase Ante meridiem and Post meridiem am or pm
|
||||
"A", "PM", // Uppercase Ante meridiem and Post meridiem AM or PM
|
||||
|
||||
"i", "04", // Minutes with leading zeros 00 to 59
|
||||
"s", "05", // Seconds, with leading zeros 00 through 59
|
||||
|
||||
// time zone
|
||||
"T", "MST", |
||||
"P", "-07:00", |
||||
"O", "-0700", |
||||
|
||||
// RFC 2822
|
||||
"r", time.RFC1123Z, |
||||
} |
||||
|
||||
// Parse Date use PHP time format.
|
||||
func DateParse(dateString, format string) (time.Time, error) { |
||||
replacer := strings.NewReplacer(datePatterns...) |
||||
format = replacer.Replace(format) |
||||
return time.ParseInLocation(format, dateString, time.Local) |
||||
} |
@ -0,0 +1,41 @@ |
||||
// Copyright 2013 com authors
|
||||
//
|
||||
// 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 com |
||||
|
||||
import ( |
||||
"encoding/base64" |
||||
"net/url" |
||||
) |
||||
|
||||
// url encode string, is + not %20
|
||||
func UrlEncode(str string) string { |
||||
return url.QueryEscape(str) |
||||
} |
||||
|
||||
// url decode string
|
||||
func UrlDecode(str string) (string, error) { |
||||
return url.QueryUnescape(str) |
||||
} |
||||
|
||||
// base64 encode
|
||||
func Base64Encode(str string) string { |
||||
return base64.StdEncoding.EncodeToString([]byte(str)) |
||||
} |
||||
|
||||
// base64 decode
|
||||
func Base64Decode(str string) (string, error) { |
||||
s, e := base64.StdEncoding.DecodeString(str) |
||||
return string(s), e |
||||
} |
@ -0,0 +1,191 @@ |
||||
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: |
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of |
||||
this License; and |
||||
You must cause any modified files to carry prominent notices stating that You |
||||
changed the files; and |
||||
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 |
||||
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. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work |
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate |
||||
notice, with the fields enclosed by brackets "[]" replaced with your own |
||||
identifying information. (Don't include the brackets!) The text should be |
||||
enclosed in the appropriate comment syntax for the file format. We also |
||||
recommend that a file or class name and description of purpose be included on |
||||
the same "printed page" as the copyright notice for easier identification within |
||||
third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
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. |
@ -0,0 +1,134 @@ |
||||
i18n |
||||
==== |
||||
|
||||
Package i18n is for app Internationalization and Localization. |
||||
|
||||
## Introduction |
||||
|
||||
This package provides multiple-language options to improve user experience. Sites like [Go Walker](http://gowalker.org) and [gogs.io](http://gogs.io) are using this module to implement Chinese and English user interfaces. |
||||
|
||||
You can use following command to install this module: |
||||
|
||||
go get github.com/Unknwon/i18n |
||||
|
||||
## Usage |
||||
|
||||
First of all, you have to import this package: |
||||
|
||||
```go |
||||
import "github.com/Unknwon/i18n" |
||||
``` |
||||
|
||||
The format of locale files is very like INI format configuration file, which is basically key-value pairs. But this module has some improvements. Every language corresponding to a locale file, for example, under `conf/locale` folder of [gogsweb](https://github.com/gogits/gogsweb/tree/master/conf/locale), there are two files called `locale_en-US.ini` and `locale_zh-CN.ini`. |
||||
|
||||
The name and extensions of locale files can be anything, but we strongly recommend you to follow the style of gogsweb. |
||||
|
||||
## Minimal example |
||||
|
||||
Here are two simplest locale file examples: |
||||
|
||||
File `locale_en-US.ini`: |
||||
|
||||
```ini |
||||
hi = hello, %s |
||||
bye = goodbye |
||||
``` |
||||
|
||||
File `locale_zh-CN.ini`: |
||||
|
||||
```ini |
||||
hi = 您好,%s |
||||
bye = 再见 |
||||
``` |
||||
|
||||
### Do Translation |
||||
|
||||
There are two ways to do translation depends on which way is the best fit for your application or framework. |
||||
|
||||
Directly use package function to translate: |
||||
|
||||
```go |
||||
i18n.Tr("en-US", "hi", "Unknwon") |
||||
i18n.Tr("en-US", "bye") |
||||
``` |
||||
|
||||
Or create a struct and embed it: |
||||
|
||||
```go |
||||
type MyController struct{ |
||||
// ...other fields |
||||
i18n.Locale |
||||
} |
||||
|
||||
//... |
||||
|
||||
func ... { |
||||
c := &MyController{ |
||||
Locale: i18n.Locale{"en-US"}, |
||||
} |
||||
_ = c.Tr("hi", "Unknwon") |
||||
_ = c.Tr("bye") |
||||
} |
||||
``` |
||||
|
||||
Code above will produce correspondingly: |
||||
|
||||
- English `en-US`:`hello, Unknwon`, `goodbye` |
||||
- Chinese `zh-CN`:`您好,Unknwon`, `再见` |
||||
|
||||
## Section |
||||
|
||||
For different pages, one key may map to different values. Therefore, i18n module also uses the section feature of INI format configuration to achieve section. |
||||
|
||||
For example, the key name is `about`, and we want to show `About` in the home page and `About Us` in about page. Then you can do following: |
||||
|
||||
Content in locale file: |
||||
|
||||
```ini |
||||
about = About |
||||
|
||||
[about] |
||||
about = About Us |
||||
``` |
||||
|
||||
Get `about` in home page: |
||||
|
||||
```go |
||||
i18n.Tr("en-US", "about") |
||||
``` |
||||
|
||||
Get `about` in about page: |
||||
|
||||
```go |
||||
i18n.Tr("en-US", "about.about") |
||||
``` |
||||
|
||||
### Ambiguity |
||||
|
||||
Because dot `.` is sign of section in both [INI parser](https://github.com/go-ini/ini) and locale files, so when your key name contains `.` will cause ambiguity. At this point, you just need to add one more `.` in front of the key. |
||||
|
||||
For example, the key name is `about.`, then we can use: |
||||
|
||||
```go |
||||
i18n.Tr("en-US", ".about.") |
||||
``` |
||||
|
||||
to get expect result. |
||||
|
||||
## Helper tool |
||||
|
||||
Module i18n provides a command line helper tool beei18n for simplify steps of your development. You can install it as follows: |
||||
|
||||
go get github.com/Unknwon/i18n/ui18n |
||||
|
||||
### Sync locale files |
||||
|
||||
Command `sync` allows you use a exist local file as the template to create or sync other locale files: |
||||
|
||||
ui18n sync srouce_file.ini other1.ini other2.ini |
||||
|
||||
This command can operate 1 or more files in one command. |
||||
|
||||
## More information |
||||
|
||||
If the key does not exist, then i18n will return the key string to caller. For instance, when key name is `hi` and it does not exist in locale file, simply return `hi` as output. |
@ -0,0 +1,225 @@ |
||||
// Copyright 2013 Unknwon
|
||||
//
|
||||
// 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 i18n is for app Internationalization and Localization.
|
||||
package i18n |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"reflect" |
||||
"strings" |
||||
|
||||
"gopkg.in/ini.v1" |
||||
) |
||||
|
||||
var ( |
||||
ErrLangAlreadyExist = errors.New("Lang already exists") |
||||
|
||||
locales = &localeStore{store: make(map[string]*locale)} |
||||
) |
||||
|
||||
type locale struct { |
||||
id int |
||||
lang string |
||||
langDesc string |
||||
message *ini.File |
||||
} |
||||
|
||||
type localeStore struct { |
||||
langs []string |
||||
langDescs []string |
||||
store map[string]*locale |
||||
defaultLang string |
||||
} |
||||
|
||||
// Get target language string
|
||||
func (d *localeStore) Get(lang, section, format string) (string, bool) { |
||||
if locale, ok := d.store[lang]; ok { |
||||
if key, err := locale.message.Section(section).GetKey(format); err == nil { |
||||
return key.Value(), true |
||||
} |
||||
} |
||||
|
||||
if len(d.defaultLang) > 0 && lang != d.defaultLang { |
||||
return d.Get(d.defaultLang, section, format) |
||||
} |
||||
|
||||
return "", false |
||||
} |
||||
|
||||
func (d *localeStore) Add(lc *locale) bool { |
||||
if _, ok := d.store[lc.lang]; ok { |
||||
return false |
||||
} |
||||
|
||||
lc.id = len(d.langs) |
||||
d.langs = append(d.langs, lc.lang) |
||||
d.langDescs = append(d.langDescs, lc.langDesc) |
||||
d.store[lc.lang] = lc |
||||
|
||||
return true |
||||
} |
||||
|
||||
func (d *localeStore) Reload(langs ...string) (err error) { |
||||
if len(langs) == 0 { |
||||
for _, lc := range d.store { |
||||
if err = lc.message.Reload(); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} else { |
||||
for _, lang := range langs { |
||||
if lc, ok := d.store[lang]; ok { |
||||
if err = lc.message.Reload(); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// SetDefaultLang sets default language which is a indicator that
|
||||
// when target language is not found, try find in default language again.
|
||||
func SetDefaultLang(lang string) { |
||||
locales.defaultLang = lang |
||||
} |
||||
|
||||
// ReloadLangs reloads locale files.
|
||||
func ReloadLangs(langs ...string) error { |
||||
return locales.Reload(langs...) |
||||
} |
||||
|
||||
// Count returns number of languages that are registered.
|
||||
func Count() int { |
||||
return len(locales.langs) |
||||
} |
||||
|
||||
// ListLangs returns list of all locale languages.
|
||||
func ListLangs() []string { |
||||
langs := make([]string, len(locales.langs)) |
||||
copy(langs, locales.langs) |
||||
return langs |
||||
} |
||||
|
||||
func ListLangDescs() []string { |
||||
langDescs := make([]string, len(locales.langDescs)) |
||||
copy(langDescs, locales.langDescs) |
||||
return langDescs |
||||
} |
||||
|
||||
// IsExist returns true if given language locale exists.
|
||||
func IsExist(lang string) bool { |
||||
_, ok := locales.store[lang] |
||||
return ok |
||||
} |
||||
|
||||
// IndexLang returns index of language locale,
|
||||
// it returns -1 if locale not exists.
|
||||
func IndexLang(lang string) int { |
||||
if lc, ok := locales.store[lang]; ok { |
||||
return lc.id |
||||
} |
||||
return -1 |
||||
} |
||||
|
||||
// GetLangByIndex return language by given index.
|
||||
func GetLangByIndex(index int) string { |
||||
if index < 0 || index >= len(locales.langs) { |
||||
return "" |
||||
} |
||||
return locales.langs[index] |
||||
} |
||||
|
||||
func GetDescriptionByIndex(index int) string { |
||||
if index < 0 || index >= len(locales.langDescs) { |
||||
return "" |
||||
} |
||||
|
||||
return locales.langDescs[index] |
||||
} |
||||
|
||||
func GetDescriptionByLang(lang string) string { |
||||
return GetDescriptionByIndex(IndexLang(lang)) |
||||
} |
||||
|
||||
func SetMessageWithDesc(lang, langDesc string, localeFile interface{}, otherLocaleFiles ...interface{}) error { |
||||
message, err := ini.Load(localeFile, otherLocaleFiles...) |
||||
if err == nil { |
||||
message.BlockMode = false |
||||
lc := new(locale) |
||||
lc.lang = lang |
||||
lc.langDesc = langDesc |
||||
lc.message = message |
||||
|
||||
if locales.Add(lc) == false { |
||||
return ErrLangAlreadyExist |
||||
} |
||||
} |
||||
return err |
||||
} |
||||
|
||||
// SetMessage sets the message file for localization.
|
||||
func SetMessage(lang string, localeFile interface{}, otherLocaleFiles ...interface{}) error { |
||||
return SetMessageWithDesc(lang, lang, localeFile, otherLocaleFiles...) |
||||
} |
||||
|
||||
// Locale represents the information of localization.
|
||||
type Locale struct { |
||||
Lang string |
||||
} |
||||
|
||||
// Tr translates content to target language.
|
||||
func (l Locale) Tr(format string, args ...interface{}) string { |
||||
return Tr(l.Lang, format, args...) |
||||
} |
||||
|
||||
// Index returns lang index of LangStore.
|
||||
func (l Locale) Index() int { |
||||
return IndexLang(l.Lang) |
||||
} |
||||
|
||||
// Tr translates content to target language.
|
||||
func Tr(lang, format string, args ...interface{}) string { |
||||
var section string |
||||
parts := strings.SplitN(format, ".", 2) |
||||
if len(parts) == 2 { |
||||
section = parts[0] |
||||
format = parts[1] |
||||
} |
||||
|
||||
value, ok := locales.Get(lang, section, format) |
||||
if ok { |
||||
format = value |
||||
} |
||||
|
||||
if len(args) > 0 { |
||||
params := make([]interface{}, 0, len(args)) |
||||
for _, arg := range args { |
||||
if arg != nil { |
||||
val := reflect.ValueOf(arg) |
||||
if val.Kind() == reflect.Slice { |
||||
for i := 0; i < val.Len(); i++ { |
||||
params = append(params, val.Index(i).Interface()) |
||||
} |
||||
} else { |
||||
params = append(params, arg) |
||||
} |
||||
} |
||||
} |
||||
return fmt.Sprintf(format, params...) |
||||
} |
||||
return format |
||||
} |
@ -0,0 +1,202 @@ |
||||
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. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work. |
||||
|
||||
To apply the Apache License to your work, attach the following |
||||
boilerplate notice, with the fields enclosed by brackets "{}" |
||||
replaced with your own identifying information. (Don't include |
||||
the brackets!) The text should be enclosed in the appropriate |
||||
comment syntax for the file format. We also recommend that a |
||||
file or class name and description of purpose be included on the |
||||
same "printed page" as the copyright notice for easier |
||||
identification within third-party archives. |
||||
|
||||
Copyright {yyyy} {name of copyright owner} |
||||
|
||||
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. |
||||
|
@ -0,0 +1,65 @@ |
||||
Paginater [![Build Status](https://drone.io/github.com/Unknwon/paginater/status.png)](https://drone.io/github.com/Unknwon/paginater/latest) [![](http://gocover.io/_badge/github.com/Unknwon/paginater)](http://gocover.io/github.com/Unknwon/paginater) |
||||
========= |
||||
|
||||
Package paginater is a helper module for custom pagination calculation. |
||||
|
||||
## Installation |
||||
|
||||
go get github.com/Unknwon/paginater |
||||
|
||||
## Getting Started |
||||
|
||||
The following code shows an example of how to use paginater: |
||||
|
||||
```go |
||||
package main |
||||
|
||||
import "github.com/Unknwon/paginater" |
||||
|
||||
func main() { |
||||
// Arguments: |
||||
// - Total number of rows |
||||
// - Number of rows in one page |
||||
// - Current page number |
||||
// - Number of page links |
||||
p := paginater.New(45, 10, 3, 3) |
||||
|
||||
// Then use p as a template object named "Page" in "demo.html" |
||||
// ... |
||||
} |
||||
``` |
||||
|
||||
`demo.html` |
||||
|
||||
```html |
||||
{{if not .Page.IsFirst}}[First](1){{end}} |
||||
{{if .Page.HasPrevious}}[Previous]({{.Page.Previous}}){{end}} |
||||
|
||||
{{range .Page.Pages}} |
||||
{{if eq .Num -1}} |
||||
... |
||||
{{else}} |
||||
{{.Num}}{{if .IsCurrent}}(current){{end}} |
||||
{{end}} |
||||
{{end}} |
||||
|
||||
{{if .Page.HasNext}}[Next]({{.Page.Next}}){{end}} |
||||
{{if not .Page.IsLast}}[Last]({{.Page.TotalPages}}){{end}} |
||||
``` |
||||
|
||||
Possible output: |
||||
|
||||
``` |
||||
[First](1) [Previous](2) ... 2 3(current) 4 ... [Next](4) [Last](5) |
||||
``` |
||||
|
||||
As you may guess, if the `Page` value is `-1`, you should print `...` in the HTML as common practice. |
||||
|
||||
## Getting Help |
||||
|
||||
- [API Documentation](https://gowalker.org/github.com/Unknwon/paginater) |
||||
- [File An Issue](https://github.com/Unknwon/paginater/issues/new) |
||||
|
||||
## License |
||||
|
||||
This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text. |
@ -0,0 +1,192 @@ |
||||
// Copyright 2015 Unknwon
|
||||
//
|
||||
// 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 paginater is a helper module for custom pagination calculation.
|
||||
package paginater |
||||
|
||||
// Paginater represents a set of results of pagination calculations.
|
||||
type Paginater struct { |
||||
total int |
||||
pagingNum int |
||||
current int |
||||
numPages int |
||||
} |
||||
|
||||
// New initialize a new pagination calculation and returns a Paginater as result.
|
||||
func New(total, pagingNum, current, numPages int) *Paginater { |
||||
if pagingNum <= 0 { |
||||
pagingNum = 1 |
||||
} |
||||
if current <= 0 { |
||||
current = 1 |
||||
} |
||||
p := &Paginater{total, pagingNum, current, numPages} |
||||
if p.current > p.TotalPages() { |
||||
p.current = p.TotalPages() |
||||
} |
||||
return p |
||||
} |
||||
|
||||
// IsFirst returns true if current page is the first page.
|
||||
func (p *Paginater) IsFirst() bool { |
||||
return p.current == 1 |
||||
} |
||||
|
||||
// HasPrevious returns true if there is a previous page relative to current page.
|
||||
func (p *Paginater) HasPrevious() bool { |
||||
return p.current > 1 |
||||
} |
||||
|
||||
func (p *Paginater) Previous() int { |
||||
if !p.HasPrevious() { |
||||
return p.current |
||||
} |
||||
return p.current - 1 |
||||
} |
||||
|
||||
// HasNext returns true if there is a next page relative to current page.
|
||||
func (p *Paginater) HasNext() bool { |
||||
return p.total > p.current*p.pagingNum |
||||
} |
||||
|
||||
func (p *Paginater) Next() int { |
||||
if !p.HasNext() { |
||||
return p.current |
||||
} |
||||
return p.current + 1 |
||||
} |
||||
|
||||
// IsLast returns true if current page is the last page.
|
||||
func (p *Paginater) IsLast() bool { |
||||
if p.total == 0 { |
||||
return true |
||||
} |
||||
return p.total > (p.current-1)*p.pagingNum && !p.HasNext() |
||||
} |
||||
|
||||
// Total returns number of total rows.
|
||||
func (p *Paginater) Total() int { |
||||
return p.total |
||||
} |
||||
|
||||
// TotalPage returns number of total pages.
|
||||
func (p *Paginater) TotalPages() int { |
||||
if p.total == 0 { |
||||
return 1 |
||||
} |
||||
if p.total%p.pagingNum == 0 { |
||||
return p.total / p.pagingNum |
||||
} |
||||
return p.total/p.pagingNum + 1 |
||||
} |
||||
|
||||
// Current returns current page number.
|
||||
func (p *Paginater) Current() int { |
||||
return p.current |
||||
} |
||||
|
||||
// Page presents a page in the paginater.
|
||||
type Page struct { |
||||
num int |
||||
isCurrent bool |
||||
} |
||||
|
||||
func (p *Page) Num() int { |
||||
return p.num |
||||
} |
||||
|
||||
func (p *Page) IsCurrent() bool { |
||||
return p.isCurrent |
||||
} |
||||
|
||||
func getMiddleIdx(numPages int) int { |
||||
if numPages%2 == 0 { |
||||
return numPages / 2 |
||||
} |
||||
return numPages/2 + 1 |
||||
} |
||||
|
||||
// Pages returns a list of nearby page numbers relative to current page.
|
||||
// If value is -1 means "..." that more pages are not showing.
|
||||
func (p *Paginater) Pages() []*Page { |
||||
if p.numPages == 0 { |
||||
return []*Page{} |
||||
} else if p.numPages == 1 && p.TotalPages() == 1 { |
||||
// Only show current page.
|
||||
return []*Page{{1, true}} |
||||
} |
||||
|
||||
// Total page number is less or equal.
|
||||
if p.TotalPages() <= p.numPages { |
||||
pages := make([]*Page, p.TotalPages()) |
||||
for i := range pages { |
||||
pages[i] = &Page{i + 1, i+1 == p.current} |
||||
} |
||||
return pages |
||||
} |
||||
|
||||
numPages := p.numPages |
||||
maxIdx := numPages - 1 |
||||
offsetIdx := 0 |
||||
hasMoreNext := false |
||||
|
||||
// Check more previous and next pages.
|
||||
previousNum := getMiddleIdx(p.numPages) - 1 |
||||
if previousNum > p.current-1 { |
||||
previousNum -= previousNum - (p.current - 1) |
||||
} |
||||
nextNum := p.numPages - previousNum - 1 |
||||
if p.current+nextNum > p.TotalPages() { |
||||
delta := nextNum - (p.TotalPages() - p.current) |
||||
nextNum -= delta |
||||
previousNum += delta |
||||
} |
||||
|
||||
offsetVal := p.current - previousNum |
||||
if offsetVal > 1 { |
||||
numPages++ |
||||
maxIdx++ |
||||
offsetIdx = 1 |
||||
} |
||||
|
||||
if p.current+nextNum < p.TotalPages() { |
||||
numPages++ |
||||
hasMoreNext = true |
||||
} |
||||
|
||||
pages := make([]*Page, numPages) |
||||
|
||||
// There are more previous pages.
|
||||
if offsetIdx == 1 { |
||||
pages[0] = &Page{-1, false} |
||||
} |
||||
// There are more next pages.
|
||||
if hasMoreNext { |
||||
pages[len(pages)-1] = &Page{-1, false} |
||||
} |
||||
|
||||
// Check previous pages.
|
||||
for i := 0; i < previousNum; i++ { |
||||
pages[offsetIdx+i] = &Page{i + offsetVal, false} |
||||
} |
||||
|
||||
pages[offsetIdx+previousNum] = &Page{p.current, true} |
||||
|
||||
// Check next pages.
|
||||
for i := 1; i <= nextNum; i++ { |
||||
pages[offsetIdx+previousNum+i] = &Page{p.current + i, false} |
||||
} |
||||
|
||||
return pages |
||||
} |
@ -0,0 +1,202 @@ |
||||
|
||||
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. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work. |
||||
|
||||
To apply the Apache License to your work, attach the following |
||||
boilerplate notice, with the fields enclosed by brackets "[]" |
||||
replaced with your own identifying information. (Don't include |
||||
the brackets!) The text should be enclosed in the appropriate |
||||
comment syntax for the file format. We also recommend that a |
||||
file or class name and description of purpose be included on the |
||||
same "printed page" as the copyright notice for easier |
||||
identification within third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
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. |
@ -0,0 +1,666 @@ |
||||
/* |
||||
Copyright 2011 Google Inc. |
||||
|
||||
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 memcache provides a client for the memcached cache server.
|
||||
package memcache |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"io/ioutil" |
||||
"net" |
||||
|
||||
"strconv" |
||||
"strings" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
// Similar to:
|
||||
// http://code.google.com/appengine/docs/go/memcache/reference.html
|
||||
|
||||
var ( |
||||
// ErrCacheMiss means that a Get failed because the item wasn't present.
|
||||
ErrCacheMiss = errors.New("memcache: cache miss") |
||||
|
||||
// ErrCASConflict means that a CompareAndSwap call failed due to the
|
||||
// cached value being modified between the Get and the CompareAndSwap.
|
||||
// If the cached value was simply evicted rather than replaced,
|
||||
// ErrNotStored will be returned instead.
|
||||
ErrCASConflict = errors.New("memcache: compare-and-swap conflict") |
||||
|
||||
// ErrNotStored means that a conditional write operation (i.e. Add or
|
||||
// CompareAndSwap) failed because the condition was not satisfied.
|
||||
ErrNotStored = errors.New("memcache: item not stored") |
||||
|
||||
// ErrServer means that a server error occurred.
|
||||
ErrServerError = errors.New("memcache: server error") |
||||
|
||||
// ErrNoStats means that no statistics were available.
|
||||
ErrNoStats = errors.New("memcache: no statistics available") |
||||
|
||||
// ErrMalformedKey is returned when an invalid key is used.
|
||||
// Keys must be at maximum 250 bytes long, ASCII, and not
|
||||
// contain whitespace or control characters.
|
||||
ErrMalformedKey = errors.New("malformed: key is too long or contains invalid characters") |
||||
|
||||
// ErrNoServers is returned when no servers are configured or available.
|
||||
ErrNoServers = errors.New("memcache: no servers configured or available") |
||||
) |
||||
|
||||
// DefaultTimeout is the default socket read/write timeout.
|
||||
const DefaultTimeout = 100 * time.Millisecond |
||||
|
||||
const ( |
||||
buffered = 8 // arbitrary buffered channel size, for readability
|
||||
maxIdleConnsPerAddr = 2 // TODO(bradfitz): make this configurable?
|
||||
) |
||||
|
||||
// resumableError returns true if err is only a protocol-level cache error.
|
||||
// This is used to determine whether or not a server connection should
|
||||
// be re-used or not. If an error occurs, by default we don't reuse the
|
||||
// connection, unless it was just a cache error.
|
||||
func resumableError(err error) bool { |
||||
switch err { |
||||
case ErrCacheMiss, ErrCASConflict, ErrNotStored, ErrMalformedKey: |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func legalKey(key string) bool { |
||||
if len(key) > 250 { |
||||
return false |
||||
} |
||||
for i := 0; i < len(key); i++ { |
||||
if key[i] <= ' ' || key[i] > 0x7e { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
var ( |
||||
crlf = []byte("\r\n") |
||||
space = []byte(" ") |
||||
resultOK = []byte("OK\r\n") |
||||
resultStored = []byte("STORED\r\n") |
||||
resultNotStored = []byte("NOT_STORED\r\n") |
||||
resultExists = []byte("EXISTS\r\n") |
||||
resultNotFound = []byte("NOT_FOUND\r\n") |
||||
resultDeleted = []byte("DELETED\r\n") |
||||
resultEnd = []byte("END\r\n") |
||||
resultOk = []byte("OK\r\n") |
||||
resultTouched = []byte("TOUCHED\r\n") |
||||
|
||||
resultClientErrorPrefix = []byte("CLIENT_ERROR ") |
||||
) |
||||
|
||||
// New returns a memcache client using the provided server(s)
|
||||
// with equal weight. If a server is listed multiple times,
|
||||
// it gets a proportional amount of weight.
|
||||
func New(server ...string) *Client { |
||||
ss := new(ServerList) |
||||
ss.SetServers(server...) |
||||
return NewFromSelector(ss) |
||||
} |
||||
|
||||
// NewFromSelector returns a new Client using the provided ServerSelector.
|
||||
func NewFromSelector(ss ServerSelector) *Client { |
||||
return &Client{selector: ss} |
||||
} |
||||
|
||||
// Client is a memcache client.
|
||||
// It is safe for unlocked use by multiple concurrent goroutines.
|
||||
type Client struct { |
||||
// Timeout specifies the socket read/write timeout.
|
||||
// If zero, DefaultTimeout is used.
|
||||
Timeout time.Duration |
||||
|
||||
selector ServerSelector |
||||
|
||||
lk sync.Mutex |
||||
freeconn map[string][]*conn |
||||
} |
||||
|
||||
// Item is an item to be got or stored in a memcached server.
|
||||
type Item struct { |
||||
// Key is the Item's key (250 bytes maximum).
|
||||
Key string |
||||
|
||||
// Value is the Item's value.
|
||||
Value []byte |
||||
|
||||
// Flags are server-opaque flags whose semantics are entirely
|
||||
// up to the app.
|
||||
Flags uint32 |
||||
|
||||
// Expiration is the cache expiration time, in seconds: either a relative
|
||||
// time from now (up to 1 month), or an absolute Unix epoch time.
|
||||
// Zero means the Item has no expiration time.
|
||||
Expiration int32 |
||||
|
||||
// Compare and swap ID.
|
||||
casid uint64 |
||||
} |
||||
|
||||
// conn is a connection to a server.
|
||||
type conn struct { |
||||
nc net.Conn |
||||
rw *bufio.ReadWriter |
||||
addr net.Addr |
||||
c *Client |
||||
} |
||||
|
||||
// release returns this connection back to the client's free pool
|
||||
func (cn *conn) release() { |
||||
cn.c.putFreeConn(cn.addr, cn) |
||||
} |
||||
|
||||
func (cn *conn) extendDeadline() { |
||||
cn.nc.SetDeadline(time.Now().Add(cn.c.netTimeout())) |
||||
} |
||||
|
||||
// condRelease releases this connection if the error pointed to by err
|
||||
// is nil (not an error) or is only a protocol level error (e.g. a
|
||||
// cache miss). The purpose is to not recycle TCP connections that
|
||||
// are bad.
|
||||
func (cn *conn) condRelease(err *error) { |
||||
if *err == nil || resumableError(*err) { |
||||
cn.release() |
||||
} else { |
||||
cn.nc.Close() |
||||
} |
||||
} |
||||
|
||||
func (c *Client) putFreeConn(addr net.Addr, cn *conn) { |
||||
c.lk.Lock() |
||||
defer c.lk.Unlock() |
||||
if c.freeconn == nil { |
||||
c.freeconn = make(map[string][]*conn) |
||||
} |
||||
freelist := c.freeconn[addr.String()] |
||||
if len(freelist) >= maxIdleConnsPerAddr { |
||||
cn.nc.Close() |
||||
return |
||||
} |
||||
c.freeconn[addr.String()] = append(freelist, cn) |
||||
} |
||||
|
||||
func (c *Client) getFreeConn(addr net.Addr) (cn *conn, ok bool) { |
||||
c.lk.Lock() |
||||
defer c.lk.Unlock() |
||||
if c.freeconn == nil { |
||||
return nil, false |
||||
} |
||||
freelist, ok := c.freeconn[addr.String()] |
||||
if !ok || len(freelist) == 0 { |
||||
return nil, false |
||||
} |
||||
cn = freelist[len(freelist)-1] |
||||
c.freeconn[addr.String()] = freelist[:len(freelist)-1] |
||||
return cn, true |
||||
} |
||||
|
||||
func (c *Client) netTimeout() time.Duration { |
||||
if c.Timeout != 0 { |
||||
return c.Timeout |
||||
} |
||||
return DefaultTimeout |
||||
} |
||||
|
||||
// ConnectTimeoutError is the error type used when it takes
|
||||
// too long to connect to the desired host. This level of
|
||||
// detail can generally be ignored.
|
||||
type ConnectTimeoutError struct { |
||||
Addr net.Addr |
||||
} |
||||
|
||||
func (cte *ConnectTimeoutError) Error() string { |
||||
return "memcache: connect timeout to " + cte.Addr.String() |
||||
} |
||||
|
||||
func (c *Client) dial(addr net.Addr) (net.Conn, error) { |
||||
type connError struct { |
||||
cn net.Conn |
||||
err error |
||||
} |
||||
|
||||
nc, err := net.DialTimeout(addr.Network(), addr.String(), c.netTimeout()) |
||||
if err == nil { |
||||
return nc, nil |
||||
} |
||||
|
||||
if ne, ok := err.(net.Error); ok && ne.Timeout() { |
||||
return nil, &ConnectTimeoutError{addr} |
||||
} |
||||
|
||||
return nil, err |
||||
} |
||||
|
||||
func (c *Client) getConn(addr net.Addr) (*conn, error) { |
||||
cn, ok := c.getFreeConn(addr) |
||||
if ok { |
||||
cn.extendDeadline() |
||||
return cn, nil |
||||
} |
||||
nc, err := c.dial(addr) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
cn = &conn{ |
||||
nc: nc, |
||||
addr: addr, |
||||
rw: bufio.NewReadWriter(bufio.NewReader(nc), bufio.NewWriter(nc)), |
||||
c: c, |
||||
} |
||||
cn.extendDeadline() |
||||
return cn, nil |
||||
} |
||||
|
||||
func (c *Client) onItem(item *Item, fn func(*Client, *bufio.ReadWriter, *Item) error) error { |
||||
addr, err := c.selector.PickServer(item.Key) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
cn, err := c.getConn(addr) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer cn.condRelease(&err) |
||||
if err = fn(c, cn.rw, item); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (c *Client) FlushAll() error { |
||||
return c.selector.Each(c.flushAllFromAddr) |
||||
} |
||||
|
||||
// Get gets the item for the given key. ErrCacheMiss is returned for a
|
||||
// memcache cache miss. The key must be at most 250 bytes in length.
|
||||
func (c *Client) Get(key string) (item *Item, err error) { |
||||
err = c.withKeyAddr(key, func(addr net.Addr) error { |
||||
return c.getFromAddr(addr, []string{key}, func(it *Item) { item = it }) |
||||
}) |
||||
if err == nil && item == nil { |
||||
err = ErrCacheMiss |
||||
} |
||||
return |
||||
} |
||||
|
||||
// Touch updates the expiry for the given key. The seconds parameter is either
|
||||
// a Unix timestamp or, if seconds is less than 1 month, the number of seconds
|
||||
// into the future at which time the item will expire. ErrCacheMiss is returned if the
|
||||
// key is not in the cache. The key must be at most 250 bytes in length.
|
||||
func (c *Client) Touch(key string, seconds int32) (err error) { |
||||
return c.withKeyAddr(key, func(addr net.Addr) error { |
||||
return c.touchFromAddr(addr, []string{key}, seconds) |
||||
}) |
||||
} |
||||
|
||||
func (c *Client) withKeyAddr(key string, fn func(net.Addr) error) (err error) { |
||||
if !legalKey(key) { |
||||
return ErrMalformedKey |
||||
} |
||||
addr, err := c.selector.PickServer(key) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return fn(addr) |
||||
} |
||||
|
||||
func (c *Client) withAddrRw(addr net.Addr, fn func(*bufio.ReadWriter) error) (err error) { |
||||
cn, err := c.getConn(addr) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer cn.condRelease(&err) |
||||
return fn(cn.rw) |
||||
} |
||||
|
||||
func (c *Client) withKeyRw(key string, fn func(*bufio.ReadWriter) error) error { |
||||
return c.withKeyAddr(key, func(addr net.Addr) error { |
||||
return c.withAddrRw(addr, fn) |
||||
}) |
||||
} |
||||
|
||||
func (c *Client) getFromAddr(addr net.Addr, keys []string, cb func(*Item)) error { |
||||
return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error { |
||||
if _, err := fmt.Fprintf(rw, "gets %s\r\n", strings.Join(keys, " ")); err != nil { |
||||
return err |
||||
} |
||||
if err := rw.Flush(); err != nil { |
||||
return err |
||||
} |
||||
if err := parseGetResponse(rw.Reader, cb); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
}) |
||||
} |
||||
|
||||
// flushAllFromAddr send the flush_all command to the given addr
|
||||
func (c *Client) flushAllFromAddr(addr net.Addr) error { |
||||
return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error { |
||||
if _, err := fmt.Fprintf(rw, "flush_all\r\n"); err != nil { |
||||
return err |
||||
} |
||||
if err := rw.Flush(); err != nil { |
||||
return err |
||||
} |
||||
line, err := rw.ReadSlice('\n') |
||||
if err != nil { |
||||
return err |
||||
} |
||||
switch { |
||||
case bytes.Equal(line, resultOk): |
||||
break |
||||
default: |
||||
return fmt.Errorf("memcache: unexpected response line from flush_all: %q", string(line)) |
||||
} |
||||
return nil |
||||
}) |
||||
} |
||||
|
||||
func (c *Client) touchFromAddr(addr net.Addr, keys []string, expiration int32) error { |
||||
return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error { |
||||
for _, key := range keys { |
||||
if _, err := fmt.Fprintf(rw, "touch %s %d\r\n", key, expiration); err != nil { |
||||
return err |
||||
} |
||||
if err := rw.Flush(); err != nil { |
||||
return err |
||||
} |
||||
line, err := rw.ReadSlice('\n') |
||||
if err != nil { |
||||
return err |
||||
} |
||||
switch { |
||||
case bytes.Equal(line, resultTouched): |
||||
break |
||||
case bytes.Equal(line, resultNotFound): |
||||
return ErrCacheMiss |
||||
default: |
||||
return fmt.Errorf("memcache: unexpected response line from touch: %q", string(line)) |
||||
} |
||||
} |
||||
return nil |
||||
}) |
||||
} |
||||
|
||||
// GetMulti is a batch version of Get. The returned map from keys to
|
||||
// items may have fewer elements than the input slice, due to memcache
|
||||
// cache misses. Each key must be at most 250 bytes in length.
|
||||
// If no error is returned, the returned map will also be non-nil.
|
||||
func (c *Client) GetMulti(keys []string) (map[string]*Item, error) { |
||||
var lk sync.Mutex |
||||
m := make(map[string]*Item) |
||||
addItemToMap := func(it *Item) { |
||||
lk.Lock() |
||||
defer lk.Unlock() |
||||
m[it.Key] = it |
||||
} |
||||
|
||||
keyMap := make(map[net.Addr][]string) |
||||
for _, key := range keys { |
||||
if !legalKey(key) { |
||||
return nil, ErrMalformedKey |
||||
} |
||||
addr, err := c.selector.PickServer(key) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
keyMap[addr] = append(keyMap[addr], key) |
||||
} |
||||
|
||||
ch := make(chan error, buffered) |
||||
for addr, keys := range keyMap { |
||||
go func(addr net.Addr, keys []string) { |
||||
ch <- c.getFromAddr(addr, keys, addItemToMap) |
||||
}(addr, keys) |
||||
} |
||||
|
||||
var err error |
||||
for _ = range keyMap { |
||||
if ge := <-ch; ge != nil { |
||||
err = ge |
||||
} |
||||
} |
||||
return m, err |
||||
} |
||||
|
||||
// parseGetResponse reads a GET response from r and calls cb for each
|
||||
// read and allocated Item
|
||||
func parseGetResponse(r *bufio.Reader, cb func(*Item)) error { |
||||
for { |
||||
line, err := r.ReadSlice('\n') |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if bytes.Equal(line, resultEnd) { |
||||
return nil |
||||
} |
||||
it := new(Item) |
||||
size, err := scanGetResponseLine(line, it) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
it.Value, err = ioutil.ReadAll(io.LimitReader(r, int64(size)+2)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if !bytes.HasSuffix(it.Value, crlf) { |
||||
return fmt.Errorf("memcache: corrupt get result read") |
||||
} |
||||
it.Value = it.Value[:size] |
||||
cb(it) |
||||
} |
||||
} |
||||
|
||||
// scanGetResponseLine populates it and returns the declared size of the item.
|
||||
// It does not read the bytes of the item.
|
||||
func scanGetResponseLine(line []byte, it *Item) (size int, err error) { |
||||
pattern := "VALUE %s %d %d %d\r\n" |
||||
dest := []interface{}{&it.Key, &it.Flags, &size, &it.casid} |
||||
if bytes.Count(line, space) == 3 { |
||||
pattern = "VALUE %s %d %d\r\n" |
||||
dest = dest[:3] |
||||
} |
||||
n, err := fmt.Sscanf(string(line), pattern, dest...) |
||||
if err != nil || n != len(dest) { |
||||
return -1, fmt.Errorf("memcache: unexpected line in get response: %q", line) |
||||
} |
||||
return size, nil |
||||
} |
||||
|
||||
// Set writes the given item, unconditionally.
|
||||
func (c *Client) Set(item *Item) error { |
||||
return c.onItem(item, (*Client).set) |
||||
} |
||||
|
||||
func (c *Client) set(rw *bufio.ReadWriter, item *Item) error { |
||||
return c.populateOne(rw, "set", item) |
||||
} |
||||
|
||||
// Add writes the given item, if no value already exists for its
|
||||
// key. ErrNotStored is returned if that condition is not met.
|
||||
func (c *Client) Add(item *Item) error { |
||||
return c.onItem(item, (*Client).add) |
||||
} |
||||
|
||||
func (c *Client) add(rw *bufio.ReadWriter, item *Item) error { |
||||
return c.populateOne(rw, "add", item) |
||||
} |
||||
|
||||
// Replace writes the given item, but only if the server *does*
|
||||
// already hold data for this key
|
||||
func (c *Client) Replace(item *Item) error { |
||||
return c.onItem(item, (*Client).replace) |
||||
} |
||||
|
||||
func (c *Client) replace(rw *bufio.ReadWriter, item *Item) error { |
||||
return c.populateOne(rw, "replace", item) |
||||
} |
||||
|
||||
// CompareAndSwap writes the given item that was previously returned
|
||||
// by Get, if the value was neither modified or evicted between the
|
||||
// Get and the CompareAndSwap calls. The item's Key should not change
|
||||
// between calls but all other item fields may differ. ErrCASConflict
|
||||
// is returned if the value was modified in between the
|
||||
// calls. ErrNotStored is returned if the value was evicted in between
|
||||
// the calls.
|
||||
func (c *Client) CompareAndSwap(item *Item) error { |
||||
return c.onItem(item, (*Client).cas) |
||||
} |
||||
|
||||
func (c *Client) cas(rw *bufio.ReadWriter, item *Item) error { |
||||
return c.populateOne(rw, "cas", item) |
||||
} |
||||
|
||||
func (c *Client) populateOne(rw *bufio.ReadWriter, verb string, item *Item) error { |
||||
if !legalKey(item.Key) { |
||||
return ErrMalformedKey |
||||
} |
||||
var err error |
||||
if verb == "cas" { |
||||
_, err = fmt.Fprintf(rw, "%s %s %d %d %d %d\r\n", |
||||
verb, item.Key, item.Flags, item.Expiration, len(item.Value), item.casid) |
||||
} else { |
||||
_, err = fmt.Fprintf(rw, "%s %s %d %d %d\r\n", |
||||
verb, item.Key, item.Flags, item.Expiration, len(item.Value)) |
||||
} |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if _, err = rw.Write(item.Value); err != nil { |
||||
return err |
||||
} |
||||
if _, err := rw.Write(crlf); err != nil { |
||||
return err |
||||
} |
||||
if err := rw.Flush(); err != nil { |
||||
return err |
||||
} |
||||
line, err := rw.ReadSlice('\n') |
||||
if err != nil { |
||||
return err |
||||
} |
||||
switch { |
||||
case bytes.Equal(line, resultStored): |
||||
return nil |
||||
case bytes.Equal(line, resultNotStored): |
||||
return ErrNotStored |
||||
case bytes.Equal(line, resultExists): |
||||
return ErrCASConflict |
||||
case bytes.Equal(line, resultNotFound): |
||||
return ErrCacheMiss |
||||
} |
||||
return fmt.Errorf("memcache: unexpected response line from %q: %q", verb, string(line)) |
||||
} |
||||
|
||||
func writeReadLine(rw *bufio.ReadWriter, format string, args ...interface{}) ([]byte, error) { |
||||
_, err := fmt.Fprintf(rw, format, args...) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if err := rw.Flush(); err != nil { |
||||
return nil, err |
||||
} |
||||
line, err := rw.ReadSlice('\n') |
||||
return line, err |
||||
} |
||||
|
||||
func writeExpectf(rw *bufio.ReadWriter, expect []byte, format string, args ...interface{}) error { |
||||
line, err := writeReadLine(rw, format, args...) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
switch { |
||||
case bytes.Equal(line, resultOK): |
||||
return nil |
||||
case bytes.Equal(line, expect): |
||||
return nil |
||||
case bytes.Equal(line, resultNotStored): |
||||
return ErrNotStored |
||||
case bytes.Equal(line, resultExists): |
||||
return ErrCASConflict |
||||
case bytes.Equal(line, resultNotFound): |
||||
return ErrCacheMiss |
||||
} |
||||
return fmt.Errorf("memcache: unexpected response line: %q", string(line)) |
||||
} |
||||
|
||||
// Delete deletes the item with the provided key. The error ErrCacheMiss is
|
||||
// returned if the item didn't already exist in the cache.
|
||||
func (c *Client) Delete(key string) error { |
||||
return c.withKeyRw(key, func(rw *bufio.ReadWriter) error { |
||||
return writeExpectf(rw, resultDeleted, "delete %s\r\n", key) |
||||
}) |
||||
} |
||||
|
||||
// DeleteAll deletes all items in the cache.
|
||||
func (c *Client) DeleteAll() error { |
||||
return c.withKeyRw("", func(rw *bufio.ReadWriter) error { |
||||
return writeExpectf(rw, resultDeleted, "flush_all\r\n") |
||||
}) |
||||
} |
||||
|
||||
// Increment atomically increments key by delta. The return value is
|
||||
// the new value after being incremented or an error. If the value
|
||||
// didn't exist in memcached the error is ErrCacheMiss. The value in
|
||||
// memcached must be an decimal number, or an error will be returned.
|
||||
// On 64-bit overflow, the new value wraps around.
|
||||
func (c *Client) Increment(key string, delta uint64) (newValue uint64, err error) { |
||||
return c.incrDecr("incr", key, delta) |
||||
} |
||||
|
||||
// Decrement atomically decrements key by delta. The return value is
|
||||
// the new value after being decremented or an error. If the value
|
||||
// didn't exist in memcached the error is ErrCacheMiss. The value in
|
||||
// memcached must be an decimal number, or an error will be returned.
|
||||
// On underflow, the new value is capped at zero and does not wrap
|
||||
// around.
|
||||
func (c *Client) Decrement(key string, delta uint64) (newValue uint64, err error) { |
||||
return c.incrDecr("decr", key, delta) |
||||
} |
||||
|
||||
func (c *Client) incrDecr(verb, key string, delta uint64) (uint64, error) { |
||||
var val uint64 |
||||
err := c.withKeyRw(key, func(rw *bufio.ReadWriter) error { |
||||
line, err := writeReadLine(rw, "%s %s %d\r\n", verb, key, delta) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
switch { |
||||
case bytes.Equal(line, resultNotFound): |
||||
return ErrCacheMiss |
||||
case bytes.HasPrefix(line, resultClientErrorPrefix): |
||||
errMsg := line[len(resultClientErrorPrefix) : len(line)-2] |
||||
return errors.New("memcache: client error: " + string(errMsg)) |
||||
} |
||||
val, err = strconv.ParseUint(string(line[:len(line)-2]), 10, 64) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
}) |
||||
return val, err |
||||
} |
@ -0,0 +1,114 @@ |
||||
/* |
||||
Copyright 2011 Google Inc. |
||||
|
||||
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 memcache |
||||
|
||||
import ( |
||||
"hash/crc32" |
||||
"net" |
||||
"strings" |
||||
"sync" |
||||
) |
||||
|
||||
// ServerSelector is the interface that selects a memcache server
|
||||
// as a function of the item's key.
|
||||
//
|
||||
// All ServerSelector implementations must be safe for concurrent use
|
||||
// by multiple goroutines.
|
||||
type ServerSelector interface { |
||||
// PickServer returns the server address that a given item
|
||||
// should be shared onto.
|
||||
PickServer(key string) (net.Addr, error) |
||||
Each(func(net.Addr) error) error |
||||
} |
||||
|
||||
// ServerList is a simple ServerSelector. Its zero value is usable.
|
||||
type ServerList struct { |
||||
mu sync.RWMutex |
||||
addrs []net.Addr |
||||
} |
||||
|
||||
// SetServers changes a ServerList's set of servers at runtime and is
|
||||
// safe for concurrent use by multiple goroutines.
|
||||
//
|
||||
// Each server is given equal weight. A server is given more weight
|
||||
// if it's listed multiple times.
|
||||
//
|
||||
// SetServers returns an error if any of the server names fail to
|
||||
// resolve. No attempt is made to connect to the server. If any error
|
||||
// is returned, no changes are made to the ServerList.
|
||||
func (ss *ServerList) SetServers(servers ...string) error { |
||||
naddr := make([]net.Addr, len(servers)) |
||||
for i, server := range servers { |
||||
if strings.Contains(server, "/") { |
||||
addr, err := net.ResolveUnixAddr("unix", server) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
naddr[i] = addr |
||||
} else { |
||||
tcpaddr, err := net.ResolveTCPAddr("tcp", server) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
naddr[i] = tcpaddr |
||||
} |
||||
} |
||||
|
||||
ss.mu.Lock() |
||||
defer ss.mu.Unlock() |
||||
ss.addrs = naddr |
||||
return nil |
||||
} |
||||
|
||||
// Each iterates over each server calling the given function
|
||||
func (ss *ServerList) Each(f func(net.Addr) error) error { |
||||
ss.mu.RLock() |
||||
defer ss.mu.RUnlock() |
||||
for _, a := range ss.addrs { |
||||
if err := f(a); nil != err { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// keyBufPool returns []byte buffers for use by PickServer's call to
|
||||
// crc32.ChecksumIEEE to avoid allocations. (but doesn't avoid the
|
||||
// copies, which at least are bounded in size and small)
|
||||
var keyBufPool = sync.Pool{ |
||||
New: func() interface{} { |
||||
b := make([]byte, 256) |
||||
return &b |
||||
}, |
||||
} |
||||
|
||||
func (ss *ServerList) PickServer(key string) (net.Addr, error) { |
||||
ss.mu.RLock() |
||||
defer ss.mu.RUnlock() |
||||
if len(ss.addrs) == 0 { |
||||
return nil, ErrNoServers |
||||
} |
||||
if len(ss.addrs) == 1 { |
||||
return ss.addrs[0], nil |
||||
} |
||||
bufp := keyBufPool.Get().(*[]byte) |
||||
n := copy(*bufp, key) |
||||
cs := crc32.ChecksumIEEE((*bufp)[:n]) |
||||
keyBufPool.Put(bufp) |
||||
|
||||
return ss.addrs[cs%uint32(len(ss.addrs))], nil |
||||
} |
@ -0,0 +1,191 @@ |
||||
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: |
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of |
||||
this License; and |
||||
You must cause any modified files to carry prominent notices stating that You |
||||
changed the files; and |
||||
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 |
||||
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. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work |
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate |
||||
notice, with the fields enclosed by brackets "[]" replaced with your own |
||||
identifying information. (Don't include the brackets!) The text should be |
||||
enclosed in the appropriate comment syntax for the file format. We also |
||||
recommend that a file or class name and description of purpose be included on |
||||
the same "printed page" as the copyright notice for easier identification within |
||||
third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
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. |
@ -0,0 +1,20 @@ |
||||
# binding [![Build Status](https://travis-ci.org/go-macaron/binding.svg?branch=master)](https://travis-ci.org/go-macaron/binding) [![](http://gocover.io/_badge/github.com/go-macaron/binding)](http://gocover.io/github.com/go-macaron/binding) |
||||
|
||||
Middleware binding provides request data binding and validation for [Macaron](https://github.com/go-macaron/macaron). |
||||
|
||||
### Installation |
||||
|
||||
go get github.com/go-macaron/binding |
||||
|
||||
## Getting Help |
||||
|
||||
- [API Reference](https://gowalker.org/github.com/go-macaron/binding) |
||||
- [Documentation](http://go-macaron.com/docs/middlewares/binding) |
||||
|
||||
## Credits |
||||
|
||||
This package is a modified version of [martini-contrib/binding](https://github.com/martini-contrib/binding). |
||||
|
||||
## License |
||||
|
||||
This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. |
@ -0,0 +1,669 @@ |
||||
// Copyright 2014 Martini Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 binding is a middleware that provides request data binding and validation for Macaron.
|
||||
package binding |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"mime/multipart" |
||||
"net/http" |
||||
"reflect" |
||||
"regexp" |
||||
"strconv" |
||||
"strings" |
||||
"unicode/utf8" |
||||
|
||||
"github.com/Unknwon/com" |
||||
"gopkg.in/macaron.v1" |
||||
) |
||||
|
||||
const _VERSION = "0.3.2" |
||||
|
||||
func Version() string { |
||||
return _VERSION |
||||
} |
||||
|
||||
func bind(ctx *macaron.Context, obj interface{}, ifacePtr ...interface{}) { |
||||
contentType := ctx.Req.Header.Get("Content-Type") |
||||
if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || len(contentType) > 0 { |
||||
switch { |
||||
case strings.Contains(contentType, "form-urlencoded"): |
||||
ctx.Invoke(Form(obj, ifacePtr...)) |
||||
case strings.Contains(contentType, "multipart/form-data"): |
||||
ctx.Invoke(MultipartForm(obj, ifacePtr...)) |
||||
case strings.Contains(contentType, "json"): |
||||
ctx.Invoke(Json(obj, ifacePtr...)) |
||||
default: |
||||
var errors Errors |
||||
if contentType == "" { |
||||
errors.Add([]string{}, ERR_CONTENT_TYPE, "Empty Content-Type") |
||||
} else { |
||||
errors.Add([]string{}, ERR_CONTENT_TYPE, "Unsupported Content-Type") |
||||
} |
||||
ctx.Map(errors) |
||||
ctx.Map(obj) // Map a fake struct so handler won't panic.
|
||||
} |
||||
} else { |
||||
ctx.Invoke(Form(obj, ifacePtr...)) |
||||
} |
||||
} |
||||
|
||||
const ( |
||||
_JSON_CONTENT_TYPE = "application/json; charset=utf-8" |
||||
STATUS_UNPROCESSABLE_ENTITY = 422 |
||||
) |
||||
|
||||
// errorHandler simply counts the number of errors in the
|
||||
// context and, if more than 0, writes a response with an
|
||||
// error code and a JSON payload describing the errors.
|
||||
// The response will have a JSON content-type.
|
||||
// Middleware remaining on the stack will not even see the request
|
||||
// if, by this point, there are any errors.
|
||||
// This is a "default" handler, of sorts, and you are
|
||||
// welcome to use your own instead. The Bind middleware
|
||||
// invokes this automatically for convenience.
|
||||
func errorHandler(errs Errors, rw http.ResponseWriter) { |
||||
if len(errs) > 0 { |
||||
rw.Header().Set("Content-Type", _JSON_CONTENT_TYPE) |
||||
if errs.Has(ERR_DESERIALIZATION) { |
||||
rw.WriteHeader(http.StatusBadRequest) |
||||
} else if errs.Has(ERR_CONTENT_TYPE) { |
||||
rw.WriteHeader(http.StatusUnsupportedMediaType) |
||||
} else { |
||||
rw.WriteHeader(STATUS_UNPROCESSABLE_ENTITY) |
||||
} |
||||
errOutput, _ := json.Marshal(errs) |
||||
rw.Write(errOutput) |
||||
return |
||||
} |
||||
} |
||||
|
||||
// Bind wraps up the functionality of the Form and Json middleware
|
||||
// according to the Content-Type and verb of the request.
|
||||
// A Content-Type is required for POST and PUT requests.
|
||||
// Bind invokes the ErrorHandler middleware to bail out if errors
|
||||
// occurred. If you want to perform your own error handling, use
|
||||
// Form or Json middleware directly. An interface pointer can
|
||||
// be added as a second argument in order to map the struct to
|
||||
// a specific interface.
|
||||
func Bind(obj interface{}, ifacePtr ...interface{}) macaron.Handler { |
||||
return func(ctx *macaron.Context) { |
||||
bind(ctx, obj, ifacePtr...) |
||||
if handler, ok := obj.(ErrorHandler); ok { |
||||
ctx.Invoke(handler.Error) |
||||
} else { |
||||
ctx.Invoke(errorHandler) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// BindIgnErr will do the exactly same thing as Bind but without any
|
||||
// error handling, which user has freedom to deal with them.
|
||||
// This allows user take advantages of validation.
|
||||
func BindIgnErr(obj interface{}, ifacePtr ...interface{}) macaron.Handler { |
||||
return func(ctx *macaron.Context) { |
||||
bind(ctx, obj, ifacePtr...) |
||||
} |
||||
} |
||||
|
||||
// Form is middleware to deserialize form-urlencoded data from the request.
|
||||
// It gets data from the form-urlencoded body, if present, or from the
|
||||
// query string. It uses the http.Request.ParseForm() method
|
||||
// to perform deserialization, then reflection is used to map each field
|
||||
// into the struct with the proper type. Structs with primitive slice types
|
||||
// (bool, float, int, string) can support deserialization of repeated form
|
||||
// keys, for example: key=val1&key=val2&key=val3
|
||||
// An interface pointer can be added as a second argument in order
|
||||
// to map the struct to a specific interface.
|
||||
func Form(formStruct interface{}, ifacePtr ...interface{}) macaron.Handler { |
||||
return func(ctx *macaron.Context) { |
||||
var errors Errors |
||||
|
||||
ensureNotPointer(formStruct) |
||||
formStruct := reflect.New(reflect.TypeOf(formStruct)) |
||||
parseErr := ctx.Req.ParseForm() |
||||
|
||||
// Format validation of the request body or the URL would add considerable overhead,
|
||||
// and ParseForm does not complain when URL encoding is off.
|
||||
// Because an empty request body or url can also mean absence of all needed values,
|
||||
// it is not in all cases a bad request, so let's return 422.
|
||||
if parseErr != nil { |
||||
errors.Add([]string{}, ERR_DESERIALIZATION, parseErr.Error()) |
||||
} |
||||
mapForm(formStruct, ctx.Req.Form, nil, errors) |
||||
validateAndMap(formStruct, ctx, errors, ifacePtr...) |
||||
} |
||||
} |
||||
|
||||
// Maximum amount of memory to use when parsing a multipart form.
|
||||
// Set this to whatever value you prefer; default is 10 MB.
|
||||
var MaxMemory = int64(1024 * 1024 * 10) |
||||
|
||||
// MultipartForm works much like Form, except it can parse multipart forms
|
||||
// and handle file uploads. Like the other deserialization middleware handlers,
|
||||
// you can pass in an interface to make the interface available for injection
|
||||
// into other handlers later.
|
||||
func MultipartForm(formStruct interface{}, ifacePtr ...interface{}) macaron.Handler { |
||||
return func(ctx *macaron.Context) { |
||||
var errors Errors |
||||
ensureNotPointer(formStruct) |
||||
formStruct := reflect.New(reflect.TypeOf(formStruct)) |
||||
// This if check is necessary due to https://github.com/martini-contrib/csrf/issues/6
|
||||
if ctx.Req.MultipartForm == nil { |
||||
// Workaround for multipart forms returning nil instead of an error
|
||||
// when content is not multipart; see https://code.google.com/p/go/issues/detail?id=6334
|
||||
if multipartReader, err := ctx.Req.MultipartReader(); err != nil { |
||||
errors.Add([]string{}, ERR_DESERIALIZATION, err.Error()) |
||||
} else { |
||||
form, parseErr := multipartReader.ReadForm(MaxMemory) |
||||
if parseErr != nil { |
||||
errors.Add([]string{}, ERR_DESERIALIZATION, parseErr.Error()) |
||||
} |
||||
|
||||
if ctx.Req.Form == nil { |
||||
ctx.Req.ParseForm() |
||||
} |
||||
for k, v := range form.Value { |
||||
ctx.Req.Form[k] = append(ctx.Req.Form[k], v...) |
||||
} |
||||
|
||||
ctx.Req.MultipartForm = form |
||||
} |
||||
} |
||||
mapForm(formStruct, ctx.Req.MultipartForm.Value, ctx.Req.MultipartForm.File, errors) |
||||
validateAndMap(formStruct, ctx, errors, ifacePtr...) |
||||
} |
||||
} |
||||
|
||||
// Json is middleware to deserialize a JSON payload from the request
|
||||
// into the struct that is passed in. The resulting struct is then
|
||||
// validated, but no error handling is actually performed here.
|
||||
// An interface pointer can be added as a second argument in order
|
||||
// to map the struct to a specific interface.
|
||||
func Json(jsonStruct interface{}, ifacePtr ...interface{}) macaron.Handler { |
||||
return func(ctx *macaron.Context) { |
||||
var errors Errors |
||||
ensureNotPointer(jsonStruct) |
||||
jsonStruct := reflect.New(reflect.TypeOf(jsonStruct)) |
||||
if ctx.Req.Request.Body != nil { |
||||
defer ctx.Req.Request.Body.Close() |
||||
err := json.NewDecoder(ctx.Req.Request.Body).Decode(jsonStruct.Interface()) |
||||
if err != nil && err != io.EOF { |
||||
errors.Add([]string{}, ERR_DESERIALIZATION, err.Error()) |
||||
} |
||||
} |
||||
validateAndMap(jsonStruct, ctx, errors, ifacePtr...) |
||||
} |
||||
} |
||||
|
||||
// Validate is middleware to enforce required fields. If the struct
|
||||
// passed in implements Validator, then the user-defined Validate method
|
||||
// is executed, and its errors are mapped to the context. This middleware
|
||||
// performs no error handling: it merely detects errors and maps them.
|
||||
func Validate(obj interface{}) macaron.Handler { |
||||
return func(ctx *macaron.Context) { |
||||
var errors Errors |
||||
v := reflect.ValueOf(obj) |
||||
k := v.Kind() |
||||
if k == reflect.Interface || k == reflect.Ptr { |
||||
v = v.Elem() |
||||
k = v.Kind() |
||||
} |
||||
if k == reflect.Slice || k == reflect.Array { |
||||
for i := 0; i < v.Len(); i++ { |
||||
e := v.Index(i).Interface() |
||||
errors = validateStruct(errors, e) |
||||
if validator, ok := e.(Validator); ok { |
||||
errors = validator.Validate(ctx, errors) |
||||
} |
||||
} |
||||
} else { |
||||
errors = validateStruct(errors, obj) |
||||
if validator, ok := obj.(Validator); ok { |
||||
errors = validator.Validate(ctx, errors) |
||||
} |
||||
} |
||||
ctx.Map(errors) |
||||
} |
||||
} |
||||
|
||||
var ( |
||||
AlphaDashPattern = regexp.MustCompile("[^\\d\\w-_]") |
||||
AlphaDashDotPattern = regexp.MustCompile("[^\\d\\w-_\\.]") |
||||
EmailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?") |
||||
URLPattern = regexp.MustCompile(`(http|https):\/\/(?:\\S+(?::\\S*)?@)?[\w\-_]+(\.[\w\-_]+)*([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?`) |
||||
) |
||||
|
||||
type ( |
||||
// Rule represents a validation rule.
|
||||
Rule struct { |
||||
// IsMatch checks if rule matches.
|
||||
IsMatch func(string) bool |
||||
// IsValid applies validation rule to condition.
|
||||
IsValid func(Errors, string, interface{}) (bool, Errors) |
||||
} |
||||
// RuleMapper represents a validation rule mapper,
|
||||
// it allwos users to add custom validation rules.
|
||||
RuleMapper []*Rule |
||||
) |
||||
|
||||
var ruleMapper RuleMapper |
||||
|
||||
// AddRule adds new validation rule.
|
||||
func AddRule(r *Rule) { |
||||
ruleMapper = append(ruleMapper, r) |
||||
} |
||||
|
||||
func in(fieldValue interface{}, arr string) bool { |
||||
val := fmt.Sprintf("%v", fieldValue) |
||||
vals := strings.Split(arr, ",") |
||||
isIn := false |
||||
for _, v := range vals { |
||||
if v == val { |
||||
isIn = true |
||||
break |
||||
} |
||||
} |
||||
return isIn |
||||
} |
||||
|
||||
func parseFormName(raw, actual string) string { |
||||
if len(actual) > 0 { |
||||
return actual |
||||
} |
||||
return nameMapper(raw) |
||||
} |
||||
|
||||
// Performs required field checking on a struct
|
||||
func validateStruct(errors Errors, obj interface{}) Errors { |
||||
typ := reflect.TypeOf(obj) |
||||
val := reflect.ValueOf(obj) |
||||
|
||||
if typ.Kind() == reflect.Ptr { |
||||
typ = typ.Elem() |
||||
val = val.Elem() |
||||
} |
||||
|
||||
for i := 0; i < typ.NumField(); i++ { |
||||
field := typ.Field(i) |
||||
|
||||
// Allow ignored fields in the struct
|
||||
if field.Tag.Get("form") == "-" || !val.Field(i).CanInterface() { |
||||
continue |
||||
} |
||||
|
||||
fieldVal := val.Field(i) |
||||
fieldValue := fieldVal.Interface() |
||||
zero := reflect.Zero(field.Type).Interface() |
||||
|
||||
// Validate nested and embedded structs (if pointer, only do so if not nil)
|
||||
if field.Type.Kind() == reflect.Struct || |
||||
(field.Type.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, fieldValue) && |
||||
field.Type.Elem().Kind() == reflect.Struct) { |
||||
errors = validateStruct(errors, fieldValue) |
||||
} |
||||
errors = validateField(errors, zero, field, fieldVal, fieldValue) |
||||
} |
||||
return errors |
||||
} |
||||
|
||||
func validateField(errors Errors, zero interface{}, field reflect.StructField, fieldVal reflect.Value, fieldValue interface{}) Errors { |
||||
if fieldVal.Kind() == reflect.Slice { |
||||
for i := 0; i < fieldVal.Len(); i++ { |
||||
sliceVal := fieldVal.Index(i) |
||||
if sliceVal.Kind() == reflect.Ptr { |
||||
sliceVal = sliceVal.Elem() |
||||
} |
||||
|
||||
sliceValue := sliceVal.Interface() |
||||
zero := reflect.Zero(sliceVal.Type()).Interface() |
||||
if sliceVal.Kind() == reflect.Struct || |
||||
(sliceVal.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, sliceValue) && |
||||
sliceVal.Elem().Kind() == reflect.Struct) { |
||||
errors = validateStruct(errors, sliceValue) |
||||
} |
||||
/* Apply validation rules to each item in a slice. ISSUE #3 |
||||
else { |
||||
errors = validateField(errors, zero, field, sliceVal, sliceValue) |
||||
}*/ |
||||
} |
||||
} |
||||
|
||||
VALIDATE_RULES: |
||||
for _, rule := range strings.Split(field.Tag.Get("binding"), ";") { |
||||
if len(rule) == 0 { |
||||
continue |
||||
} |
||||
|
||||
switch { |
||||
case rule == "OmitEmpty": |
||||
if reflect.DeepEqual(zero, fieldValue) { |
||||
break VALIDATE_RULES |
||||
} |
||||
case rule == "Required": |
||||
if reflect.DeepEqual(zero, fieldValue) { |
||||
errors.Add([]string{field.Name}, ERR_REQUIRED, "Required") |
||||
break VALIDATE_RULES |
||||
} |
||||
case rule == "AlphaDash": |
||||
if AlphaDashPattern.MatchString(fmt.Sprintf("%v", fieldValue)) { |
||||
errors.Add([]string{field.Name}, ERR_ALPHA_DASH, "AlphaDash") |
||||
break VALIDATE_RULES |
||||
} |
||||
case rule == "AlphaDashDot": |
||||
if AlphaDashDotPattern.MatchString(fmt.Sprintf("%v", fieldValue)) { |
||||
errors.Add([]string{field.Name}, ERR_ALPHA_DASH_DOT, "AlphaDashDot") |
||||
break VALIDATE_RULES |
||||
} |
||||
case strings.HasPrefix(rule, "Size("): |
||||
size, _ := strconv.Atoi(rule[5 : len(rule)-1]) |
||||
if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) != size { |
||||
errors.Add([]string{field.Name}, ERR_SIZE, "Size") |
||||
break VALIDATE_RULES |
||||
} |
||||
v := reflect.ValueOf(fieldValue) |
||||
if v.Kind() == reflect.Slice && v.Len() != size { |
||||
errors.Add([]string{field.Name}, ERR_SIZE, "Size") |
||||
break VALIDATE_RULES |
||||
} |
||||
case strings.HasPrefix(rule, "MinSize("): |
||||
min, _ := strconv.Atoi(rule[8 : len(rule)-1]) |
||||
if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) < min { |
||||
errors.Add([]string{field.Name}, ERR_MIN_SIZE, "MinSize") |
||||
break VALIDATE_RULES |
||||
} |
||||
v := reflect.ValueOf(fieldValue) |
||||
if v.Kind() == reflect.Slice && v.Len() < min { |
||||
errors.Add([]string{field.Name}, ERR_MIN_SIZE, "MinSize") |
||||
break VALIDATE_RULES |
||||
} |
||||
case strings.HasPrefix(rule, "MaxSize("): |
||||
max, _ := strconv.Atoi(rule[8 : len(rule)-1]) |
||||
if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) > max { |
||||
errors.Add([]string{field.Name}, ERR_MAX_SIZE, "MaxSize") |
||||
break VALIDATE_RULES |
||||
} |
||||
v := reflect.ValueOf(fieldValue) |
||||
if v.Kind() == reflect.Slice && v.Len() > max { |
||||
errors.Add([]string{field.Name}, ERR_MAX_SIZE, "MaxSize") |
||||
break VALIDATE_RULES |
||||
} |
||||
case strings.HasPrefix(rule, "Range("): |
||||
nums := strings.Split(rule[6:len(rule)-1], ",") |
||||
if len(nums) != 2 { |
||||
break VALIDATE_RULES |
||||
} |
||||
val := com.StrTo(fmt.Sprintf("%v", fieldValue)).MustInt() |
||||
if val < com.StrTo(nums[0]).MustInt() || val > com.StrTo(nums[1]).MustInt() { |
||||
errors.Add([]string{field.Name}, ERR_RANGE, "Range") |
||||
break VALIDATE_RULES |
||||
} |
||||
case rule == "Email": |
||||
if !EmailPattern.MatchString(fmt.Sprintf("%v", fieldValue)) { |
||||
errors.Add([]string{field.Name}, ERR_EMAIL, "Email") |
||||
break VALIDATE_RULES |
||||
} |
||||
case rule == "Url": |
||||
str := fmt.Sprintf("%v", fieldValue) |
||||
if len(str) == 0 { |
||||
continue |
||||
} else if !URLPattern.MatchString(str) { |
||||
errors.Add([]string{field.Name}, ERR_URL, "Url") |
||||
break VALIDATE_RULES |
||||
} |
||||
case strings.HasPrefix(rule, "In("): |
||||
if !in(fieldValue, rule[3:len(rule)-1]) { |
||||
errors.Add([]string{field.Name}, ERR_IN, "In") |
||||
break VALIDATE_RULES |
||||
} |
||||
case strings.HasPrefix(rule, "NotIn("): |
||||
if in(fieldValue, rule[6:len(rule)-1]) { |
||||
errors.Add([]string{field.Name}, ERR_NOT_INT, "NotIn") |
||||
break VALIDATE_RULES |
||||
} |
||||
case strings.HasPrefix(rule, "Include("): |
||||
if !strings.Contains(fmt.Sprintf("%v", fieldValue), rule[8:len(rule)-1]) { |
||||
errors.Add([]string{field.Name}, ERR_INCLUDE, "Include") |
||||
break VALIDATE_RULES |
||||
} |
||||
case strings.HasPrefix(rule, "Exclude("): |
||||
if strings.Contains(fmt.Sprintf("%v", fieldValue), rule[8:len(rule)-1]) { |
||||
errors.Add([]string{field.Name}, ERR_EXCLUDE, "Exclude") |
||||
break VALIDATE_RULES |
||||
} |
||||
case strings.HasPrefix(rule, "Default("): |
||||
if reflect.DeepEqual(zero, fieldValue) { |
||||
if fieldVal.CanAddr() { |
||||
setWithProperType(field.Type.Kind(), rule[8:len(rule)-1], fieldVal, field.Tag.Get("form"), errors) |
||||
} else { |
||||
errors.Add([]string{field.Name}, ERR_EXCLUDE, "Default") |
||||
break VALIDATE_RULES |
||||
} |
||||
} |
||||
default: |
||||
// Apply custom validation rules.
|
||||
var isValid bool |
||||
for i := range ruleMapper { |
||||
if ruleMapper[i].IsMatch(rule) { |
||||
isValid, errors = ruleMapper[i].IsValid(errors, field.Name, fieldValue) |
||||
if !isValid { |
||||
break VALIDATE_RULES |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return errors |
||||
} |
||||
|
||||
// NameMapper represents a form tag name mapper.
|
||||
type NameMapper func(string) string |
||||
|
||||
var ( |
||||
nameMapper = func(field string) string { |
||||
newstr := make([]rune, 0, len(field)) |
||||
for i, chr := range field { |
||||
if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { |
||||
if i > 0 { |
||||
newstr = append(newstr, '_') |
||||
} |
||||
chr -= ('A' - 'a') |
||||
} |
||||
newstr = append(newstr, chr) |
||||
} |
||||
return string(newstr) |
||||
} |
||||
) |
||||
|
||||
// SetNameMapper sets name mapper.
|
||||
func SetNameMapper(nm NameMapper) { |
||||
nameMapper = nm |
||||
} |
||||
|
||||
// Takes values from the form data and puts them into a struct
|
||||
func mapForm(formStruct reflect.Value, form map[string][]string, |
||||
formfile map[string][]*multipart.FileHeader, errors Errors) { |
||||
|
||||
if formStruct.Kind() == reflect.Ptr { |
||||
formStruct = formStruct.Elem() |
||||
} |
||||
typ := formStruct.Type() |
||||
|
||||
for i := 0; i < typ.NumField(); i++ { |
||||
typeField := typ.Field(i) |
||||
structField := formStruct.Field(i) |
||||
|
||||
if typeField.Type.Kind() == reflect.Ptr && typeField.Anonymous { |
||||
structField.Set(reflect.New(typeField.Type.Elem())) |
||||
mapForm(structField.Elem(), form, formfile, errors) |
||||
if reflect.DeepEqual(structField.Elem().Interface(), reflect.Zero(structField.Elem().Type()).Interface()) { |
||||
structField.Set(reflect.Zero(structField.Type())) |
||||
} |
||||
} else if typeField.Type.Kind() == reflect.Struct { |
||||
mapForm(structField, form, formfile, errors) |
||||
} |
||||
|
||||
inputFieldName := parseFormName(typeField.Name, typeField.Tag.Get("form")) |
||||
if len(inputFieldName) == 0 || !structField.CanSet() { |
||||
continue |
||||
} |
||||
|
||||
inputValue, exists := form[inputFieldName] |
||||
if exists { |
||||
numElems := len(inputValue) |
||||
if structField.Kind() == reflect.Slice && numElems > 0 { |
||||
sliceOf := structField.Type().Elem().Kind() |
||||
slice := reflect.MakeSlice(structField.Type(), numElems, numElems) |
||||
for i := 0; i < numElems; i++ { |
||||
setWithProperType(sliceOf, inputValue[i], slice.Index(i), inputFieldName, errors) |
||||
} |
||||
formStruct.Field(i).Set(slice) |
||||
} else { |
||||
setWithProperType(typeField.Type.Kind(), inputValue[0], structField, inputFieldName, errors) |
||||
} |
||||
continue |
||||
} |
||||
|
||||
inputFile, exists := formfile[inputFieldName] |
||||
if !exists { |
||||
continue |
||||
} |
||||
fhType := reflect.TypeOf((*multipart.FileHeader)(nil)) |
||||
numElems := len(inputFile) |
||||
if structField.Kind() == reflect.Slice && numElems > 0 && structField.Type().Elem() == fhType { |
||||
slice := reflect.MakeSlice(structField.Type(), numElems, numElems) |
||||
for i := 0; i < numElems; i++ { |
||||
slice.Index(i).Set(reflect.ValueOf(inputFile[i])) |
||||
} |
||||
structField.Set(slice) |
||||
} else if structField.Type() == fhType { |
||||
structField.Set(reflect.ValueOf(inputFile[0])) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// This sets the value in a struct of an indeterminate type to the
|
||||
// matching value from the request (via Form middleware) in the
|
||||
// same type, so that not all deserialized values have to be strings.
|
||||
// Supported types are string, int, float, and bool.
|
||||
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value, nameInTag string, errors Errors) { |
||||
switch valueKind { |
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
||||
if val == "" { |
||||
val = "0" |
||||
} |
||||
intVal, err := strconv.ParseInt(val, 10, 64) |
||||
if err != nil { |
||||
errors.Add([]string{nameInTag}, ERR_INTERGER_TYPE, "Value could not be parsed as integer") |
||||
} else { |
||||
structField.SetInt(intVal) |
||||
} |
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
||||
if val == "" { |
||||
val = "0" |
||||
} |
||||
uintVal, err := strconv.ParseUint(val, 10, 64) |
||||
if err != nil { |
||||
errors.Add([]string{nameInTag}, ERR_INTERGER_TYPE, "Value could not be parsed as unsigned integer") |
||||
} else { |
||||
structField.SetUint(uintVal) |
||||
} |
||||
case reflect.Bool: |
||||
if val == "on" { |
||||
structField.SetBool(true) |
||||
return |
||||
} |
||||
|
||||
if val == "" { |
||||
val = "false" |
||||
} |
||||
boolVal, err := strconv.ParseBool(val) |
||||
if err != nil { |
||||
errors.Add([]string{nameInTag}, ERR_BOOLEAN_TYPE, "Value could not be parsed as boolean") |
||||
} else if boolVal { |
||||
structField.SetBool(true) |
||||
} |
||||
case reflect.Float32: |
||||
if val == "" { |
||||
val = "0.0" |
||||
} |
||||
floatVal, err := strconv.ParseFloat(val, 32) |
||||
if err != nil { |
||||
errors.Add([]string{nameInTag}, ERR_FLOAT_TYPE, "Value could not be parsed as 32-bit float") |
||||
} else { |
||||
structField.SetFloat(floatVal) |
||||
} |
||||
case reflect.Float64: |
||||
if val == "" { |
||||
val = "0.0" |
||||
} |
||||
floatVal, err := strconv.ParseFloat(val, 64) |
||||
if err != nil { |
||||
errors.Add([]string{nameInTag}, ERR_FLOAT_TYPE, "Value could not be parsed as 64-bit float") |
||||
} else { |
||||
structField.SetFloat(floatVal) |
||||
} |
||||
case reflect.String: |
||||
structField.SetString(val) |
||||
} |
||||
} |
||||
|
||||
// Don't pass in pointers to bind to. Can lead to bugs.
|
||||
func ensureNotPointer(obj interface{}) { |
||||
if reflect.TypeOf(obj).Kind() == reflect.Ptr { |
||||
panic("Pointers are not accepted as binding models") |
||||
} |
||||
} |
||||
|
||||
// Performs validation and combines errors from validation
|
||||
// with errors from deserialization, then maps both the
|
||||
// resulting struct and the errors to the context.
|
||||
func validateAndMap(obj reflect.Value, ctx *macaron.Context, errors Errors, ifacePtr ...interface{}) { |
||||
ctx.Invoke(Validate(obj.Interface())) |
||||
errors = append(errors, getErrors(ctx)...) |
||||
ctx.Map(errors) |
||||
ctx.Map(obj.Elem().Interface()) |
||||
if len(ifacePtr) > 0 { |
||||
ctx.MapTo(obj.Elem().Interface(), ifacePtr[0]) |
||||
} |
||||
} |
||||
|
||||
// getErrors simply gets the errors from the context (it's kind of a chore)
|
||||
func getErrors(ctx *macaron.Context) Errors { |
||||
return ctx.GetVal(reflect.TypeOf(Errors{})).Interface().(Errors) |
||||
} |
||||
|
||||
type ( |
||||
// ErrorHandler is the interface that has custom error handling process.
|
||||
ErrorHandler interface { |
||||
// Error handles validation errors with custom process.
|
||||
Error(*macaron.Context, Errors) |
||||
} |
||||
|
||||
// Validator is the interface that handles some rudimentary
|
||||
// request validation logic so your application doesn't have to.
|
||||
Validator interface { |
||||
// Validate validates that the request is OK. It is recommended
|
||||
// that validation be limited to checking values for syntax and
|
||||
// semantics, enough to know that you can make sense of the request
|
||||
// in your application. For example, you might verify that a credit
|
||||
// card number matches a valid pattern, but you probably wouldn't
|
||||
// perform an actual credit card authorization here.
|
||||
Validate(*macaron.Context, Errors) Errors |
||||
} |
||||
) |
@ -0,0 +1,159 @@ |
||||
// Copyright 2014 Martini Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 binding |
||||
|
||||
const ( |
||||
// Type mismatch errors.
|
||||
ERR_CONTENT_TYPE = "ContentTypeError" |
||||
ERR_DESERIALIZATION = "DeserializationError" |
||||
ERR_INTERGER_TYPE = "IntegerTypeError" |
||||
ERR_BOOLEAN_TYPE = "BooleanTypeError" |
||||
ERR_FLOAT_TYPE = "FloatTypeError" |
||||
|
||||
// Validation errors.
|
||||
ERR_REQUIRED = "RequiredError" |
||||
ERR_ALPHA_DASH = "AlphaDashError" |
||||
ERR_ALPHA_DASH_DOT = "AlphaDashDotError" |
||||
ERR_SIZE = "SizeError" |
||||
ERR_MIN_SIZE = "MinSizeError" |
||||
ERR_MAX_SIZE = "MaxSizeError" |
||||
ERR_RANGE = "RangeError" |
||||
ERR_EMAIL = "EmailError" |
||||
ERR_URL = "UrlError" |
||||
ERR_IN = "InError" |
||||
ERR_NOT_INT = "NotInError" |
||||
ERR_INCLUDE = "IncludeError" |
||||
ERR_EXCLUDE = "ExcludeError" |
||||
ERR_DEFAULT = "DefaultError" |
||||
) |
||||
|
||||
type ( |
||||
// Errors may be generated during deserialization, binding,
|
||||
// or validation. This type is mapped to the context so you
|
||||
// can inject it into your own handlers and use it in your
|
||||
// application if you want all your errors to look the same.
|
||||
Errors []Error |
||||
|
||||
Error struct { |
||||
// An error supports zero or more field names, because an
|
||||
// error can morph three ways: (1) it can indicate something
|
||||
// wrong with the request as a whole, (2) it can point to a
|
||||
// specific problem with a particular input field, or (3) it
|
||||
// can span multiple related input fields.
|
||||
FieldNames []string `json:"fieldNames,omitempty"` |
||||
|
||||
// The classification is like an error code, convenient to
|
||||
// use when processing or categorizing an error programmatically.
|
||||
// It may also be called the "kind" of error.
|
||||
Classification string `json:"classification,omitempty"` |
||||
|
||||
// Message should be human-readable and detailed enough to
|
||||
// pinpoint and resolve the problem, but it should be brief. For
|
||||
// example, a payload of 100 objects in a JSON array might have
|
||||
// an error in the 41st object. The message should help the
|
||||
// end user find and fix the error with their request.
|
||||
Message string `json:"message,omitempty"` |
||||
} |
||||
) |
||||
|
||||
// Add adds an error associated with the fields indicated
|
||||
// by fieldNames, with the given classification and message.
|
||||
func (e *Errors) Add(fieldNames []string, classification, message string) { |
||||
*e = append(*e, Error{ |
||||
FieldNames: fieldNames, |
||||
Classification: classification, |
||||
Message: message, |
||||
}) |
||||
} |
||||
|
||||
// Len returns the number of errors.
|
||||
func (e *Errors) Len() int { |
||||
return len(*e) |
||||
} |
||||
|
||||
// Has determines whether an Errors slice has an Error with
|
||||
// a given classification in it; it does not search on messages
|
||||
// or field names.
|
||||
func (e *Errors) Has(class string) bool { |
||||
for _, err := range *e { |
||||
if err.Kind() == class { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
/* |
||||
// WithClass gets a copy of errors that are classified by the
|
||||
// the given classification.
|
||||
func (e *Errors) WithClass(classification string) Errors { |
||||
var errs Errors |
||||
for _, err := range *e { |
||||
if err.Kind() == classification { |
||||
errs = append(errs, err) |
||||
} |
||||
} |
||||
return errs |
||||
} |
||||
|
||||
// ForField gets a copy of errors that are associated with the
|
||||
// field by the given name.
|
||||
func (e *Errors) ForField(name string) Errors { |
||||
var errs Errors |
||||
for _, err := range *e { |
||||
for _, fieldName := range err.Fields() { |
||||
if fieldName == name { |
||||
errs = append(errs, err) |
||||
break |
||||
} |
||||
} |
||||
} |
||||
return errs |
||||
} |
||||
|
||||
// Get gets errors of a particular class for the specified
|
||||
// field name.
|
||||
func (e *Errors) Get(class, fieldName string) Errors { |
||||
var errs Errors |
||||
for _, err := range *e { |
||||
if err.Kind() == class { |
||||
for _, nameOfField := range err.Fields() { |
||||
if nameOfField == fieldName { |
||||
errs = append(errs, err) |
||||
break |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return errs |
||||
} |
||||
*/ |
||||
|
||||
// Fields returns the list of field names this error is
|
||||
// associated with.
|
||||
func (e Error) Fields() []string { |
||||
return e.FieldNames |
||||
} |
||||
|
||||
// Kind returns this error's classification.
|
||||
func (e Error) Kind() string { |
||||
return e.Classification |
||||
} |
||||
|
||||
// Error returns this error's message.
|
||||
func (e Error) Error() string { |
||||
return e.Message |
||||
} |
@ -0,0 +1,191 @@ |
||||
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: |
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of |
||||
this License; and |
||||
You must cause any modified files to carry prominent notices stating that You |
||||
changed the files; and |
||||
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 |
||||
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. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work |
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate |
||||
notice, with the fields enclosed by brackets "[]" replaced with your own |
||||
identifying information. (Don't include the brackets!) The text should be |
||||
enclosed in the appropriate comment syntax for the file format. We also |
||||
recommend that a file or class name and description of purpose be included on |
||||
the same "printed page" as the copyright notice for easier identification within |
||||
third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
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. |
@ -0,0 +1,20 @@ |
||||
# cache [![Build Status](https://travis-ci.org/go-macaron/cache.svg?branch=master)](https://travis-ci.org/go-macaron/cache) [![](http://gocover.io/_badge/github.com/go-macaron/cache)](http://gocover.io/github.com/go-macaron/cache) |
||||
|
||||
Middleware cache provides cache management for [Macaron](https://github.com/go-macaron/macaron). It can use many cache adapters, including memory, file, Redis, Memcache, PostgreSQL, MySQL, Ledis and Nodb. |
||||
|
||||
### Installation |
||||
|
||||
go get github.com/go-macaron/cache |
||||
|
||||
## Getting Help |
||||
|
||||
- [API Reference](https://gowalker.org/github.com/go-macaron/cache) |
||||
- [Documentation](http://go-macaron.com/docs/middlewares/cache) |
||||
|
||||
## Credits |
||||
|
||||
This package is a modified version of [beego/cache](https://github.com/astaxie/beego/tree/master/cache). |
||||
|
||||
## License |
||||
|
||||
This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. |
@ -0,0 +1,122 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 cache is a middleware that provides the cache management of Macaron.
|
||||
package cache |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"gopkg.in/macaron.v1" |
||||
) |
||||
|
||||
const _VERSION = "0.3.0" |
||||
|
||||
func Version() string { |
||||
return _VERSION |
||||
} |
||||
|
||||
// Cache is the interface that operates the cache data.
|
||||
type Cache interface { |
||||
// Put puts value into cache with key and expire time.
|
||||
Put(key string, val interface{}, timeout int64) error |
||||
// Get gets cached value by given key.
|
||||
Get(key string) interface{} |
||||
// Delete deletes cached value by given key.
|
||||
Delete(key string) error |
||||
// Incr increases cached int-type value by given key as a counter.
|
||||
Incr(key string) error |
||||
// Decr decreases cached int-type value by given key as a counter.
|
||||
Decr(key string) error |
||||
// IsExist returns true if cached value exists.
|
||||
IsExist(key string) bool |
||||
// Flush deletes all cached data.
|
||||
Flush() error |
||||
// StartAndGC starts GC routine based on config string settings.
|
||||
StartAndGC(opt Options) error |
||||
} |
||||
|
||||
// Options represents a struct for specifying configuration options for the cache middleware.
|
||||
type Options struct { |
||||
// Name of adapter. Default is "memory".
|
||||
Adapter string |
||||
// Adapter configuration, it's corresponding to adapter.
|
||||
AdapterConfig string |
||||
// GC interval time in seconds. Default is 60.
|
||||
Interval int |
||||
// Occupy entire database. Default is false.
|
||||
OccupyMode bool |
||||
// Configuration section name. Default is "cache".
|
||||
Section string |
||||
} |
||||
|
||||
func prepareOptions(options []Options) Options { |
||||
var opt Options |
||||
if len(options) > 0 { |
||||
opt = options[0] |
||||
} |
||||
if len(opt.Section) == 0 { |
||||
opt.Section = "cache" |
||||
} |
||||
sec := macaron.Config().Section(opt.Section) |
||||
|
||||
if len(opt.Adapter) == 0 { |
||||
opt.Adapter = sec.Key("ADAPTER").MustString("memory") |
||||
} |
||||
if opt.Interval == 0 { |
||||
opt.Interval = sec.Key("INTERVAL").MustInt(60) |
||||
} |
||||
if len(opt.AdapterConfig) == 0 { |
||||
opt.AdapterConfig = sec.Key("ADAPTER_CONFIG").MustString("data/caches") |
||||
} |
||||
|
||||
return opt |
||||
} |
||||
|
||||
// NewCacher creates and returns a new cacher by given adapter name and configuration.
|
||||
// It panics when given adapter isn't registered and starts GC automatically.
|
||||
func NewCacher(name string, opt Options) (Cache, error) { |
||||
adapter, ok := adapters[name] |
||||
if !ok { |
||||
return nil, fmt.Errorf("cache: unknown adapter '%s'(forgot to import?)", name) |
||||
} |
||||
return adapter, adapter.StartAndGC(opt) |
||||
} |
||||
|
||||
// Cacher is a middleware that maps a cache.Cache service into the Macaron handler chain.
|
||||
// An single variadic cache.Options struct can be optionally provided to configure.
|
||||
func Cacher(options ...Options) macaron.Handler { |
||||
opt := prepareOptions(options) |
||||
cache, err := NewCacher(opt.Adapter, opt) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
return func(ctx *macaron.Context) { |
||||
ctx.Map(cache) |
||||
} |
||||
} |
||||
|
||||
var adapters = make(map[string]Cache) |
||||
|
||||
// Register registers a adapter.
|
||||
func Register(name string, adapter Cache) { |
||||
if adapter == nil { |
||||
panic("cache: cannot register adapter with nil value") |
||||
} |
||||
if _, dup := adapters[name]; dup { |
||||
panic(fmt.Errorf("cache: cannot register adapter '%s' twice", name)) |
||||
} |
||||
adapters[name] = adapter |
||||
} |
@ -0,0 +1,208 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 cache |
||||
|
||||
import ( |
||||
"crypto/md5" |
||||
"encoding/hex" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"log" |
||||
"os" |
||||
"path/filepath" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/Unknwon/com" |
||||
"gopkg.in/macaron.v1" |
||||
) |
||||
|
||||
// Item represents a cache item.
|
||||
type Item struct { |
||||
Val interface{} |
||||
Created int64 |
||||
Expire int64 |
||||
} |
||||
|
||||
func (item *Item) hasExpired() bool { |
||||
return item.Expire > 0 && |
||||
(time.Now().Unix()-item.Created) >= item.Expire |
||||
} |
||||
|
||||
// FileCacher represents a file cache adapter implementation.
|
||||
type FileCacher struct { |
||||
lock sync.Mutex |
||||
rootPath string |
||||
interval int // GC interval.
|
||||
} |
||||
|
||||
// NewFileCacher creates and returns a new file cacher.
|
||||
func NewFileCacher() *FileCacher { |
||||
return &FileCacher{} |
||||
} |
||||
|
||||
func (c *FileCacher) filepath(key string) string { |
||||
m := md5.Sum([]byte(key)) |
||||
hash := hex.EncodeToString(m[:]) |
||||
return filepath.Join(c.rootPath, string(hash[0]), string(hash[1]), hash) |
||||
} |
||||
|
||||
// Put puts value into cache with key and expire time.
|
||||
// If expired is 0, it will be deleted by next GC operation.
|
||||
func (c *FileCacher) Put(key string, val interface{}, expire int64) error { |
||||
filename := c.filepath(key) |
||||
item := &Item{val, time.Now().Unix(), expire} |
||||
data, err := EncodeGob(item) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
os.MkdirAll(filepath.Dir(filename), os.ModePerm) |
||||
return ioutil.WriteFile(filename, data, os.ModePerm) |
||||
} |
||||
|
||||
func (c *FileCacher) read(key string) (*Item, error) { |
||||
filename := c.filepath(key) |
||||
|
||||
data, err := ioutil.ReadFile(filename) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
item := new(Item) |
||||
return item, DecodeGob(data, item) |
||||
} |
||||
|
||||
// Get gets cached value by given key.
|
||||
func (c *FileCacher) Get(key string) interface{} { |
||||
item, err := c.read(key) |
||||
if err != nil { |
||||
return nil |
||||
} |
||||
|
||||
if item.hasExpired() { |
||||
os.Remove(c.filepath(key)) |
||||
return nil |
||||
} |
||||
return item.Val |
||||
} |
||||
|
||||
// Delete deletes cached value by given key.
|
||||
func (c *FileCacher) Delete(key string) error { |
||||
return os.Remove(c.filepath(key)) |
||||
} |
||||
|
||||
// Incr increases cached int-type value by given key as a counter.
|
||||
func (c *FileCacher) Incr(key string) error { |
||||
item, err := c.read(key) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
item.Val, err = Incr(item.Val) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
return c.Put(key, item.Val, item.Expire) |
||||
} |
||||
|
||||
// Decrease cached int value.
|
||||
func (c *FileCacher) Decr(key string) error { |
||||
item, err := c.read(key) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
item.Val, err = Decr(item.Val) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
return c.Put(key, item.Val, item.Expire) |
||||
} |
||||
|
||||
// IsExist returns true if cached value exists.
|
||||
func (c *FileCacher) IsExist(key string) bool { |
||||
return com.IsExist(c.filepath(key)) |
||||
} |
||||
|
||||
// Flush deletes all cached data.
|
||||
func (c *FileCacher) Flush() error { |
||||
return os.RemoveAll(c.rootPath) |
||||
} |
||||
|
||||
func (c *FileCacher) startGC() { |
||||
c.lock.Lock() |
||||
defer c.lock.Unlock() |
||||
|
||||
if c.interval < 1 { |
||||
return |
||||
} |
||||
|
||||
if err := filepath.Walk(c.rootPath, func(path string, fi os.FileInfo, err error) error { |
||||
if err != nil { |
||||
return fmt.Errorf("Walk: %v", err) |
||||
} |
||||
|
||||
if fi.IsDir() { |
||||
return nil |
||||
} |
||||
|
||||
data, err := ioutil.ReadFile(path) |
||||
if err != nil && !os.IsNotExist(err) { |
||||
fmt.Errorf("ReadFile: %v", err) |
||||
} |
||||
|
||||
item := new(Item) |
||||
if err = DecodeGob(data, item); err != nil { |
||||
return err |
||||
} |
||||
if item.hasExpired() { |
||||
if err = os.Remove(path); err != nil && !os.IsNotExist(err) { |
||||
return fmt.Errorf("Remove: %v", err) |
||||
} |
||||
} |
||||
return nil |
||||
}); err != nil { |
||||
log.Printf("error garbage collecting cache files: %v", err) |
||||
} |
||||
|
||||
time.AfterFunc(time.Duration(c.interval)*time.Second, func() { c.startGC() }) |
||||
} |
||||
|
||||
// StartAndGC starts GC routine based on config string settings.
|
||||
func (c *FileCacher) StartAndGC(opt Options) error { |
||||
c.lock.Lock() |
||||
c.rootPath = opt.AdapterConfig |
||||
c.interval = opt.Interval |
||||
|
||||
if !filepath.IsAbs(c.rootPath) { |
||||
c.rootPath = filepath.Join(macaron.Root, c.rootPath) |
||||
} |
||||
c.lock.Unlock() |
||||
|
||||
if err := os.MkdirAll(c.rootPath, os.ModePerm); err != nil { |
||||
return err |
||||
} |
||||
|
||||
go c.startGC() |
||||
return nil |
||||
} |
||||
|
||||
func init() { |
||||
Register("file", NewFileCacher()) |
||||
} |
@ -0,0 +1,92 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 cache |
||||
|
||||
import ( |
||||
"strings" |
||||
|
||||
"github.com/Unknwon/com" |
||||
"github.com/bradfitz/gomemcache/memcache" |
||||
|
||||
"github.com/go-macaron/cache" |
||||
) |
||||
|
||||
// MemcacheCacher represents a memcache cache adapter implementation.
|
||||
type MemcacheCacher struct { |
||||
c *memcache.Client |
||||
} |
||||
|
||||
func NewItem(key string, data []byte, expire int32) *memcache.Item { |
||||
return &memcache.Item{ |
||||
Key: key, |
||||
Value: data, |
||||
Expiration: expire, |
||||
} |
||||
} |
||||
|
||||
// Put puts value into cache with key and expire time.
|
||||
// If expired is 0, it lives forever.
|
||||
func (c *MemcacheCacher) Put(key string, val interface{}, expire int64) error { |
||||
return c.c.Set(NewItem(key, []byte(com.ToStr(val)), int32(expire))) |
||||
} |
||||
|
||||
// Get gets cached value by given key.
|
||||
func (c *MemcacheCacher) Get(key string) interface{} { |
||||
item, err := c.c.Get(key) |
||||
if err != nil { |
||||
return nil |
||||
} |
||||
return string(item.Value) |
||||
} |
||||
|
||||
// Delete deletes cached value by given key.
|
||||
func (c *MemcacheCacher) Delete(key string) error { |
||||
return c.c.Delete(key) |
||||
} |
||||
|
||||
// Incr increases cached int-type value by given key as a counter.
|
||||
func (c *MemcacheCacher) Incr(key string) error { |
||||
_, err := c.c.Increment(key, 1) |
||||
return err |
||||
} |
||||
|
||||
// Decr decreases cached int-type value by given key as a counter.
|
||||
func (c *MemcacheCacher) Decr(key string) error { |
||||
_, err := c.c.Decrement(key, 1) |
||||
return err |
||||
} |
||||
|
||||
// IsExist returns true if cached value exists.
|
||||
func (c *MemcacheCacher) IsExist(key string) bool { |
||||
_, err := c.c.Get(key) |
||||
return err == nil |
||||
} |
||||
|
||||
// Flush deletes all cached data.
|
||||
func (c *MemcacheCacher) Flush() error { |
||||
return c.c.FlushAll() |
||||
} |
||||
|
||||
// StartAndGC starts GC routine based on config string settings.
|
||||
// AdapterConfig: 127.0.0.1:9090;127.0.0.1:9091
|
||||
func (c *MemcacheCacher) StartAndGC(opt cache.Options) error { |
||||
c.c = memcache.New(strings.Split(opt.AdapterConfig, ";")...) |
||||
return nil |
||||
} |
||||
|
||||
func init() { |
||||
cache.Register("memcache", &MemcacheCacher{}) |
||||
} |
@ -0,0 +1 @@ |
||||
ignore |
@ -0,0 +1,179 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 cache |
||||
|
||||
import ( |
||||
"errors" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
// MemoryItem represents a memory cache item.
|
||||
type MemoryItem struct { |
||||
val interface{} |
||||
created int64 |
||||
expire int64 |
||||
} |
||||
|
||||
func (item *MemoryItem) hasExpired() bool { |
||||
return item.expire > 0 && |
||||
(time.Now().Unix()-item.created) >= item.expire |
||||
} |
||||
|
||||
// MemoryCacher represents a memory cache adapter implementation.
|
||||
type MemoryCacher struct { |
||||
lock sync.RWMutex |
||||
items map[string]*MemoryItem |
||||
interval int // GC interval.
|
||||
} |
||||
|
||||
// NewMemoryCacher creates and returns a new memory cacher.
|
||||
func NewMemoryCacher() *MemoryCacher { |
||||
return &MemoryCacher{items: make(map[string]*MemoryItem)} |
||||
} |
||||
|
||||
// Put puts value into cache with key and expire time.
|
||||
// If expired is 0, it will be deleted by next GC operation.
|
||||
func (c *MemoryCacher) Put(key string, val interface{}, expire int64) error { |
||||
c.lock.Lock() |
||||
defer c.lock.Unlock() |
||||
|
||||
c.items[key] = &MemoryItem{ |
||||
val: val, |
||||
created: time.Now().Unix(), |
||||
expire: expire, |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Get gets cached value by given key.
|
||||
func (c *MemoryCacher) Get(key string) interface{} { |
||||
c.lock.RLock() |
||||
defer c.lock.RUnlock() |
||||
|
||||
item, ok := c.items[key] |
||||
if !ok { |
||||
return nil |
||||
} |
||||
if item.hasExpired() { |
||||
go c.Delete(key) |
||||
return nil |
||||
} |
||||
return item.val |
||||
} |
||||
|
||||
// Delete deletes cached value by given key.
|
||||
func (c *MemoryCacher) Delete(key string) error { |
||||
c.lock.Lock() |
||||
defer c.lock.Unlock() |
||||
|
||||
delete(c.items, key) |
||||
return nil |
||||
} |
||||
|
||||
// Incr increases cached int-type value by given key as a counter.
|
||||
func (c *MemoryCacher) Incr(key string) (err error) { |
||||
c.lock.RLock() |
||||
defer c.lock.RUnlock() |
||||
|
||||
item, ok := c.items[key] |
||||
if !ok { |
||||
return errors.New("key not exist") |
||||
} |
||||
item.val, err = Incr(item.val) |
||||
return err |
||||
} |
||||
|
||||
// Decr decreases cached int-type value by given key as a counter.
|
||||
func (c *MemoryCacher) Decr(key string) (err error) { |
||||
c.lock.RLock() |
||||
defer c.lock.RUnlock() |
||||
|
||||
item, ok := c.items[key] |
||||
if !ok { |
||||
return errors.New("key not exist") |
||||
} |
||||
|
||||
item.val, err = Decr(item.val) |
||||
return err |
||||
} |
||||
|
||||
// IsExist returns true if cached value exists.
|
||||
func (c *MemoryCacher) IsExist(key string) bool { |
||||
c.lock.RLock() |
||||
defer c.lock.RUnlock() |
||||
|
||||
_, ok := c.items[key] |
||||
return ok |
||||
} |
||||
|
||||
// Flush deletes all cached data.
|
||||
func (c *MemoryCacher) Flush() error { |
||||
c.lock.Lock() |
||||
defer c.lock.Unlock() |
||||
|
||||
c.items = make(map[string]*MemoryItem) |
||||
return nil |
||||
} |
||||
|
||||
func (c *MemoryCacher) checkRawExpiration(key string) { |
||||
item, ok := c.items[key] |
||||
if !ok { |
||||
return |
||||
} |
||||
|
||||
if item.hasExpired() { |
||||
delete(c.items, key) |
||||
} |
||||
} |
||||
|
||||
func (c *MemoryCacher) checkExpiration(key string) { |
||||
c.lock.Lock() |
||||
defer c.lock.Unlock() |
||||
|
||||
c.checkRawExpiration(key) |
||||
} |
||||
|
||||
func (c *MemoryCacher) startGC() { |
||||
c.lock.Lock() |
||||
defer c.lock.Unlock() |
||||
|
||||
if c.interval < 1 { |
||||
return |
||||
} |
||||
|
||||
if c.items != nil { |
||||
for key, _ := range c.items { |
||||
c.checkRawExpiration(key) |
||||
} |
||||
} |
||||
|
||||
time.AfterFunc(time.Duration(c.interval)*time.Second, func() { c.startGC() }) |
||||
} |
||||
|
||||
// StartAndGC starts GC routine based on config string settings.
|
||||
func (c *MemoryCacher) StartAndGC(opt Options) error { |
||||
c.lock.Lock() |
||||
c.interval = opt.Interval |
||||
c.lock.Unlock() |
||||
|
||||
go c.startGC() |
||||
return nil |
||||
} |
||||
|
||||
func init() { |
||||
Register("memory", NewMemoryCacher()) |
||||
} |
@ -0,0 +1,178 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 cache |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/Unknwon/com" |
||||
"gopkg.in/ini.v1" |
||||
"gopkg.in/redis.v2" |
||||
|
||||
"github.com/go-macaron/cache" |
||||
) |
||||
|
||||
// RedisCacher represents a redis cache adapter implementation.
|
||||
type RedisCacher struct { |
||||
c *redis.Client |
||||
prefix string |
||||
hsetName string |
||||
occupyMode bool |
||||
} |
||||
|
||||
// Put puts value into cache with key and expire time.
|
||||
// If expired is 0, it lives forever.
|
||||
func (c *RedisCacher) Put(key string, val interface{}, expire int64) error { |
||||
key = c.prefix + key |
||||
if expire == 0 { |
||||
if err := c.c.Set(key, com.ToStr(val)).Err(); err != nil { |
||||
return err |
||||
} |
||||
} else { |
||||
dur, err := time.ParseDuration(com.ToStr(expire) + "s") |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err = c.c.SetEx(key, dur, com.ToStr(val)).Err(); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
if c.occupyMode { |
||||
return nil |
||||
} |
||||
return c.c.HSet(c.hsetName, key, "0").Err() |
||||
} |
||||
|
||||
// Get gets cached value by given key.
|
||||
func (c *RedisCacher) Get(key string) interface{} { |
||||
val, err := c.c.Get(c.prefix + key).Result() |
||||
if err != nil { |
||||
return nil |
||||
} |
||||
return val |
||||
} |
||||
|
||||
// Delete deletes cached value by given key.
|
||||
func (c *RedisCacher) Delete(key string) error { |
||||
key = c.prefix + key |
||||
if err := c.c.Del(key).Err(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if c.occupyMode { |
||||
return nil |
||||
} |
||||
return c.c.HDel(c.hsetName, key).Err() |
||||
} |
||||
|
||||
// Incr increases cached int-type value by given key as a counter.
|
||||
func (c *RedisCacher) Incr(key string) error { |
||||
if !c.IsExist(key) { |
||||
return fmt.Errorf("key '%s' not exist", key) |
||||
} |
||||
return c.c.Incr(c.prefix + key).Err() |
||||
} |
||||
|
||||
// Decr decreases cached int-type value by given key as a counter.
|
||||
func (c *RedisCacher) Decr(key string) error { |
||||
if !c.IsExist(key) { |
||||
return fmt.Errorf("key '%s' not exist", key) |
||||
} |
||||
return c.c.Decr(c.prefix + key).Err() |
||||
} |
||||
|
||||
// IsExist returns true if cached value exists.
|
||||
func (c *RedisCacher) IsExist(key string) bool { |
||||
if c.c.Exists(c.prefix + key).Val() { |
||||
return true |
||||
} |
||||
|
||||
if !c.occupyMode { |
||||
c.c.HDel(c.hsetName, c.prefix+key) |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// Flush deletes all cached data.
|
||||
func (c *RedisCacher) Flush() error { |
||||
if c.occupyMode { |
||||
return c.c.FlushDb().Err() |
||||
} |
||||
|
||||
keys, err := c.c.HKeys(c.hsetName).Result() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err = c.c.Del(keys...).Err(); err != nil { |
||||
return err |
||||
} |
||||
return c.c.Del(c.hsetName).Err() |
||||
} |
||||
|
||||
// StartAndGC starts GC routine based on config string settings.
|
||||
// AdapterConfig: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180,hset_name=MacaronCache,prefix=cache:
|
||||
func (c *RedisCacher) StartAndGC(opts cache.Options) error { |
||||
c.hsetName = "MacaronCache" |
||||
c.occupyMode = opts.OccupyMode |
||||
|
||||
cfg, err := ini.Load([]byte(strings.Replace(opts.AdapterConfig, ",", "\n", -1))) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
opt := &redis.Options{ |
||||
Network: "tcp", |
||||
} |
||||
for k, v := range cfg.Section("").KeysHash() { |
||||
switch k { |
||||
case "network": |
||||
opt.Network = v |
||||
case "addr": |
||||
opt.Addr = v |
||||
case "password": |
||||
opt.Password = v |
||||
case "db": |
||||
opt.DB = com.StrTo(v).MustInt64() |
||||
case "pool_size": |
||||
opt.PoolSize = com.StrTo(v).MustInt() |
||||
case "idle_timeout": |
||||
opt.IdleTimeout, err = time.ParseDuration(v + "s") |
||||
if err != nil { |
||||
return fmt.Errorf("error parsing idle timeout: %v", err) |
||||
} |
||||
case "hset_name": |
||||
c.hsetName = v |
||||
case "prefix": |
||||
c.prefix = v |
||||
default: |
||||
return fmt.Errorf("session/redis: unsupported option '%s'", k) |
||||
} |
||||
} |
||||
|
||||
c.c = redis.NewClient(opt) |
||||
if err = c.c.Ping().Err(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func init() { |
||||
cache.Register("redis", &RedisCacher{}) |
||||
} |
@ -0,0 +1 @@ |
||||
ignore |
@ -0,0 +1,84 @@ |
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 cache |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/gob" |
||||
"errors" |
||||
) |
||||
|
||||
func EncodeGob(item *Item) ([]byte, error) { |
||||
buf := bytes.NewBuffer(nil) |
||||
err := gob.NewEncoder(buf).Encode(item) |
||||
return buf.Bytes(), err |
||||
} |
||||
|
||||
func DecodeGob(data []byte, out *Item) error { |
||||
buf := bytes.NewBuffer(data) |
||||
return gob.NewDecoder(buf).Decode(&out) |
||||
} |
||||
|
||||
func Incr(val interface{}) (interface{}, error) { |
||||
switch val.(type) { |
||||
case int: |
||||
val = val.(int) + 1 |
||||
case int32: |
||||
val = val.(int32) + 1 |
||||
case int64: |
||||
val = val.(int64) + 1 |
||||
case uint: |
||||
val = val.(uint) + 1 |
||||
case uint32: |
||||
val = val.(uint32) + 1 |
||||
case uint64: |
||||
val = val.(uint64) + 1 |
||||
default: |
||||
return val, errors.New("item value is not int-type") |
||||
} |
||||
return val, nil |
||||
} |
||||
|
||||
func Decr(val interface{}) (interface{}, error) { |
||||
switch val.(type) { |
||||
case int: |
||||
val = val.(int) - 1 |
||||
case int32: |
||||
val = val.(int32) - 1 |
||||
case int64: |
||||
val = val.(int64) - 1 |
||||
case uint: |
||||
if val.(uint) > 0 { |
||||
val = val.(uint) - 1 |
||||
} else { |
||||
return val, errors.New("item value is less than 0") |
||||
} |
||||
case uint32: |
||||
if val.(uint32) > 0 { |
||||
val = val.(uint32) - 1 |
||||
} else { |
||||
return val, errors.New("item value is less than 0") |
||||
} |
||||
case uint64: |
||||
if val.(uint64) > 0 { |
||||
val = val.(uint64) - 1 |
||||
} else { |
||||
return val, errors.New("item value is less than 0") |
||||
} |
||||
default: |
||||
return val, errors.New("item value is not int-type") |
||||
} |
||||
return val, nil |
||||
} |
@ -0,0 +1,191 @@ |
||||
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: |
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of |
||||
this License; and |
||||
You must cause any modified files to carry prominent notices stating that You |
||||
changed the files; and |
||||
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 |
||||
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. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work |
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate |
||||
notice, with the fields enclosed by brackets "[]" replaced with your own |
||||
identifying information. (Don't include the brackets!) The text should be |
||||
enclosed in the appropriate comment syntax for the file format. We also |
||||
recommend that a file or class name and description of purpose be included on |
||||
the same "printed page" as the copyright notice for easier identification within |
||||
third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
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. |
@ -0,0 +1,16 @@ |
||||
# captcha [![Build Status](https://travis-ci.org/go-macaron/captcha.svg?branch=master)](https://travis-ci.org/go-macaron/captcha) |
||||
|
||||
Middleware captcha provides captcha service for [Macaron](https://github.com/go-macaron/macaron). |
||||
|
||||
### Installation |
||||
|
||||
go get github.com/go-macaron/captcha |
||||
|
||||
## Getting Help |
||||
|
||||
- [API Reference](https://gowalker.org/github.com/go-macaron/captcha) |
||||
- [Documentation](http://go-macaron.com/docs/middlewares/captcha) |
||||
|
||||
## License |
||||
|
||||
This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. |
@ -0,0 +1,241 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 captcha a middleware that provides captcha service for Macaron.
|
||||
package captcha |
||||
|
||||
import ( |
||||
"fmt" |
||||
"html/template" |
||||
"path" |
||||
"strings" |
||||
|
||||
"github.com/Unknwon/com" |
||||
"github.com/go-macaron/cache" |
||||
"gopkg.in/macaron.v1" |
||||
) |
||||
|
||||
const _VERSION = "0.1.0" |
||||
|
||||
func Version() string { |
||||
return _VERSION |
||||
} |
||||
|
||||
var ( |
||||
defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} |
||||
) |
||||
|
||||
// Captcha represents a captcha service.
|
||||
type Captcha struct { |
||||
store cache.Cache |
||||
SubURL string |
||||
URLPrefix string |
||||
FieldIdName string |
||||
FieldCaptchaName string |
||||
StdWidth int |
||||
StdHeight int |
||||
ChallengeNums int |
||||
Expiration int64 |
||||
CachePrefix string |
||||
} |
||||
|
||||
// generate key string
|
||||
func (c *Captcha) key(id string) string { |
||||
return c.CachePrefix + id |
||||
} |
||||
|
||||
// generate rand chars with default chars
|
||||
func (c *Captcha) genRandChars() string { |
||||
return string(com.RandomCreateBytes(c.ChallengeNums, defaultChars...)) |
||||
} |
||||
|
||||
// tempalte func for output html
|
||||
func (c *Captcha) CreateHtml() template.HTML { |
||||
value, err := c.CreateCaptcha() |
||||
if err != nil { |
||||
panic(fmt.Errorf("fail to create captcha: %v", err)) |
||||
} |
||||
return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s"> |
||||
<a class="captcha" href="javascript:"> |
||||
<img onclick="this.src=('%s%s%s.png?reload='+(new Date()).getTime())" class="captcha-img" src="%s%s%s.png"> |
||||
</a>`, c.FieldIdName, value, c.SubURL, c.URLPrefix, value, c.SubURL, c.URLPrefix, value)) |
||||
} |
||||
|
||||
// create a new captcha id
|
||||
func (c *Captcha) CreateCaptcha() (string, error) { |
||||
id := string(com.RandomCreateBytes(15)) |
||||
if err := c.store.Put(c.key(id), c.genRandChars(), c.Expiration); err != nil { |
||||
return "", err |
||||
} |
||||
return id, nil |
||||
} |
||||
|
||||
// verify from a request
|
||||
func (c *Captcha) VerifyReq(req macaron.Request) bool { |
||||
req.ParseForm() |
||||
return c.Verify(req.Form.Get(c.FieldIdName), req.Form.Get(c.FieldCaptchaName)) |
||||
} |
||||
|
||||
// direct verify id and challenge string
|
||||
func (c *Captcha) Verify(id string, challenge string) bool { |
||||
if len(challenge) == 0 || len(id) == 0 { |
||||
return false |
||||
} |
||||
|
||||
var chars string |
||||
|
||||
key := c.key(id) |
||||
|
||||
if v, ok := c.store.Get(key).(string); ok { |
||||
chars = v |
||||
} else { |
||||
return false |
||||
} |
||||
|
||||
defer c.store.Delete(key) |
||||
|
||||
if len(chars) != len(challenge) { |
||||
return false |
||||
} |
||||
|
||||
// verify challenge
|
||||
for i, c := range []byte(chars) { |
||||
if c != challenge[i]-48 { |
||||
return false |
||||
} |
||||
} |
||||
|
||||
return true |
||||
} |
||||
|
||||
type Options struct { |
||||
// Suburl path. Default is empty.
|
||||
SubURL string |
||||
// URL prefix of getting captcha pictures. Default is "/captcha/".
|
||||
URLPrefix string |
||||
// Hidden input element ID. Default is "captcha_id".
|
||||
FieldIdName string |
||||
// User input value element name in request form. Default is "captcha".
|
||||
FieldCaptchaName string |
||||
// Challenge number. Default is 6.
|
||||
ChallengeNums int |
||||
// Captcha image width. Default is 240.
|
||||
Width int |
||||
// Captcha image height. Default is 80.
|
||||
Height int |
||||
// Captcha expiration time in seconds. Default is 600.
|
||||
Expiration int64 |
||||
// Cache key prefix captcha characters. Default is "captcha_".
|
||||
CachePrefix string |
||||
} |
||||
|
||||
func prepareOptions(options []Options) Options { |
||||
var opt Options |
||||
if len(options) > 0 { |
||||
opt = options[0] |
||||
} |
||||
|
||||
opt.SubURL = strings.TrimSuffix(opt.SubURL, "/") |
||||
|
||||
// Defaults.
|
||||
if len(opt.URLPrefix) == 0 { |
||||
opt.URLPrefix = "/captcha/" |
||||
} else if opt.URLPrefix[len(opt.URLPrefix)-1] != '/' { |
||||
opt.URLPrefix += "/" |
||||
} |
||||
if len(opt.FieldIdName) == 0 { |
||||
opt.FieldIdName = "captcha_id" |
||||
} |
||||
if len(opt.FieldCaptchaName) == 0 { |
||||
opt.FieldCaptchaName = "captcha" |
||||
} |
||||
if opt.ChallengeNums == 0 { |
||||
opt.ChallengeNums = 6 |
||||
} |
||||
if opt.Width == 0 { |
||||
opt.Width = stdWidth |
||||
} |
||||
if opt.Height == 0 { |
||||
opt.Height = stdHeight |
||||
} |
||||
if opt.Expiration == 0 { |
||||
opt.Expiration = 600 |
||||
} |
||||
if len(opt.CachePrefix) == 0 { |
||||
opt.CachePrefix = "captcha_" |
||||
} |
||||
|
||||
return opt |
||||
} |
||||
|
||||
// NewCaptcha initializes and returns a captcha with given options.
|
||||
func NewCaptcha(opt Options) *Captcha { |
||||
return &Captcha{ |
||||
SubURL: opt.SubURL, |
||||
URLPrefix: opt.URLPrefix, |
||||
FieldIdName: opt.FieldIdName, |
||||
FieldCaptchaName: opt.FieldCaptchaName, |
||||
StdWidth: opt.Width, |
||||
StdHeight: opt.Height, |
||||
ChallengeNums: opt.ChallengeNums, |
||||
Expiration: opt.Expiration, |
||||
CachePrefix: opt.CachePrefix, |
||||
} |
||||
} |
||||
|
||||
// Captchaer is a middleware that maps a captcha.Captcha service into the Macaron handler chain.
|
||||
// An single variadic captcha.Options struct can be optionally provided to configure.
|
||||
// This should be register after cache.Cacher.
|
||||
func Captchaer(options ...Options) macaron.Handler { |
||||
return func(ctx *macaron.Context, cache cache.Cache) { |
||||
cpt := NewCaptcha(prepareOptions(options)) |
||||
cpt.store = cache |
||||
|
||||
if strings.HasPrefix(ctx.Req.URL.Path, cpt.URLPrefix) { |
||||
var chars string |
||||
id := path.Base(ctx.Req.URL.Path) |
||||
if i := strings.Index(id, "."); i > -1 { |
||||
id = id[:i] |
||||
} |
||||
key := cpt.key(id) |
||||
|
||||
// Reload captcha.
|
||||
if len(ctx.Query("reload")) > 0 { |
||||
chars = cpt.genRandChars() |
||||
if err := cpt.store.Put(key, chars, cpt.Expiration); err != nil { |
||||
ctx.Status(500) |
||||
ctx.Write([]byte("captcha reload error")) |
||||
panic(fmt.Errorf("reload captcha: %v", err)) |
||||
} |
||||
} else { |
||||
if v, ok := cpt.store.Get(key).(string); ok { |
||||
chars = v |
||||
} else { |
||||
ctx.Status(404) |
||||
ctx.Write([]byte("captcha not found")) |
||||
return |
||||
} |
||||
} |
||||
|
||||
if _, err := NewImage([]byte(chars), cpt.StdWidth, cpt.StdHeight).WriteTo(ctx.Resp); err != nil { |
||||
panic(fmt.Errorf("write captcha: %v", err)) |
||||
} |
||||
return |
||||
} |
||||
|
||||
ctx.Data["Captcha"] = cpt |
||||
ctx.Map(cpt) |
||||
} |
||||
} |
@ -0,0 +1,498 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
//
|
||||
// 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 captcha |
||||
|
||||
import ( |
||||
"bytes" |
||||
"image" |
||||
"image/color" |
||||
"image/png" |
||||
"io" |
||||
"math" |
||||
) |
||||
|
||||
const ( |
||||
fontWidth = 11 |
||||
fontHeight = 18 |
||||
blackChar = 1 |
||||
|
||||
// Standard width and height of a captcha image.
|
||||
stdWidth = 240 |
||||
stdHeight = 80 |
||||
|
||||
// Maximum absolute skew factor of a single digit.
|
||||
maxSkew = 0.7 |
||||
// Number of background circles.
|
||||
circleCount = 20 |
||||
) |
||||
|
||||
var font = [][]byte{ |
||||
{ // 0
|
||||
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, |
||||
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, |
||||
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, |
||||
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, |
||||
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, |
||||
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, |
||||
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, |
||||
}, |
||||
{ // 1
|
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, |
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
}, |
||||
{ // 2
|
||||
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, |
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, |
||||
1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, |
||||
0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, |
||||
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, |
||||
0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
}, |
||||
{ // 3
|
||||
0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, |
||||
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, |
||||
0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, |
||||
1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, |
||||
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, |
||||
}, |
||||
{ // 4
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, |
||||
0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, |
||||
0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, |
||||
0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, |
||||
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, |
||||
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, |
||||
0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, |
||||
0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, |
||||
0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, |
||||
1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, |
||||
1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, |
||||
}, |
||||
{ // 5
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, |
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, |
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, |
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, |
||||
0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, |
||||
}, |
||||
{ // 6
|
||||
0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, |
||||
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, |
||||
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, |
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, |
||||
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, |
||||
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, |
||||
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, |
||||
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, |
||||
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, |
||||
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, |
||||
}, |
||||
{ // 7
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
||||
1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, |
||||
}, |
||||
{ // 8
|
||||
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, |
||||
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, |
||||
0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, |
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, |
||||
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, |
||||
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, |
||||
0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, |
||||
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, |
||||
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, |
||||
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, |
||||
}, |
||||
{ // 9
|
||||
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, |
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, |
||||
0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, |
||||
0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, |
||||
0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, |
||||
0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, |
||||
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, |
||||
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, |
||||
0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, |
||||
}, |
||||
} |
||||
|
||||
type Image struct { |
||||
*image.Paletted |
||||
numWidth int |
||||
numHeight int |
||||
dotSize int |
||||
} |
||||
|
||||
var prng = &siprng{} |
||||
|
||||
// randIntn returns a pseudorandom non-negative int in range [0, n).
|
||||
func randIntn(n int) int { |
||||
return prng.Intn(n) |
||||
} |
||||
|
||||
// randInt returns a pseudorandom int in range [from, to].
|
||||
func randInt(from, to int) int { |
||||
return prng.Intn(to+1-from) + from |
||||
} |
||||
|
||||
// randFloat returns a pseudorandom float64 in range [from, to].
|
||||
func randFloat(from, to float64) float64 { |
||||
return (to-from)*prng.Float64() + from |
||||
} |
||||
|
||||
func randomPalette() color.Palette { |
||||
p := make([]color.Color, circleCount+1) |
||||
// Transparent color.
|
||||
p[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00} |
||||
// Primary color.
|
||||
prim := color.RGBA{ |
||||
uint8(randIntn(129)), |
||||
uint8(randIntn(129)), |
||||
uint8(randIntn(129)), |
||||
0xFF, |
||||
} |
||||
p[1] = prim |
||||
// Circle colors.
|
||||
for i := 2; i <= circleCount; i++ { |
||||
p[i] = randomBrightness(prim, 255) |
||||
} |
||||
return p |
||||
} |
||||
|
||||
// NewImage returns a new captcha image of the given width and height with the
|
||||
// given digits, where each digit must be in range 0-9.
|
||||
func NewImage(digits []byte, width, height int) *Image { |
||||
m := new(Image) |
||||
m.Paletted = image.NewPaletted(image.Rect(0, 0, width, height), randomPalette()) |
||||
m.calculateSizes(width, height, len(digits)) |
||||
// Randomly position captcha inside the image.
|
||||
maxx := width - (m.numWidth+m.dotSize)*len(digits) - m.dotSize |
||||
maxy := height - m.numHeight - m.dotSize*2 |
||||
var border int |
||||
if width > height { |
||||
border = height / 5 |
||||
} else { |
||||
border = width / 5 |
||||
} |
||||
x := randInt(border, maxx-border) |
||||
y := randInt(border, maxy-border) |
||||
// Draw digits.
|
||||
for _, n := range digits { |
||||
m.drawDigit(font[n], x, y) |
||||
x += m.numWidth + m.dotSize |
||||
} |
||||
// Draw strike-through line.
|
||||
m.strikeThrough() |
||||
// Apply wave distortion.
|
||||
m.distort(randFloat(5, 10), randFloat(100, 200)) |
||||
// Fill image with random circles.
|
||||
m.fillWithCircles(circleCount, m.dotSize) |
||||
return m |
||||
} |
||||
|
||||
// encodedPNG encodes an image to PNG and returns
|
||||
// the result as a byte slice.
|
||||
func (m *Image) encodedPNG() []byte { |
||||
var buf bytes.Buffer |
||||
if err := png.Encode(&buf, m.Paletted); err != nil { |
||||
panic(err.Error()) |
||||
} |
||||
return buf.Bytes() |
||||
} |
||||
|
||||
// WriteTo writes captcha image in PNG format into the given writer.
|
||||
func (m *Image) WriteTo(w io.Writer) (int64, error) { |
||||
n, err := w.Write(m.encodedPNG()) |
||||
return int64(n), err |
||||
} |
||||
|
||||
func (m *Image) calculateSizes(width, height, ncount int) { |
||||
// Goal: fit all digits inside the image.
|
||||
var border int |
||||
if width > height { |
||||
border = height / 4 |
||||
} else { |
||||
border = width / 4 |
||||
} |
||||
// Convert everything to floats for calculations.
|
||||
w := float64(width - border*2) |
||||
h := float64(height - border*2) |
||||
// fw takes into account 1-dot spacing between digits.
|
||||
fw := float64(fontWidth + 1) |
||||
fh := float64(fontHeight) |
||||
nc := float64(ncount) |
||||
// Calculate the width of a single digit taking into account only the
|
||||
// width of the image.
|
||||
nw := w / nc |
||||
// Calculate the height of a digit from this width.
|
||||
nh := nw * fh / fw |
||||
// Digit too high?
|
||||
if nh > h { |
||||
// Fit digits based on height.
|
||||
nh = h |
||||
nw = fw / fh * nh |
||||
} |
||||
// Calculate dot size.
|
||||
m.dotSize = int(nh / fh) |
||||
// Save everything, making the actual width smaller by 1 dot to account
|
||||
// for spacing between digits.
|
||||
m.numWidth = int(nw) - m.dotSize |
||||
m.numHeight = int(nh) |
||||
} |
||||
|
||||
func (m *Image) drawHorizLine(fromX, toX, y int, colorIdx uint8) { |
||||
for x := fromX; x <= toX; x++ { |
||||
m.SetColorIndex(x, y, colorIdx) |
||||
} |
||||
} |
||||
|
||||
func (m *Image) drawCircle(x, y, radius int, colorIdx uint8) { |
||||
f := 1 - radius |
||||
dfx := 1 |
||||
dfy := -2 * radius |
||||
xo := 0 |
||||
yo := radius |
||||
|
||||
m.SetColorIndex(x, y+radius, colorIdx) |
||||
m.SetColorIndex(x, y-radius, colorIdx) |
||||
m.drawHorizLine(x-radius, x+radius, y, colorIdx) |
||||
|
||||
for xo < yo { |
||||
if f >= 0 { |
||||
yo-- |
||||
dfy += 2 |
||||
f += dfy |
||||
} |
||||
xo++ |
||||
dfx += 2 |
||||
f += dfx |
||||
m.drawHorizLine(x-xo, x+xo, y+yo, colorIdx) |
||||
m.drawHorizLine(x-xo, x+xo, y-yo, colorIdx) |
||||
m.drawHorizLine(x-yo, x+yo, y+xo, colorIdx) |
||||
m.drawHorizLine(x-yo, x+yo, y-xo, colorIdx) |
||||
} |
||||
} |
||||
|
||||
func (m *Image) fillWithCircles(n, maxradius int) { |
||||
maxx := m.Bounds().Max.X |
||||
maxy := m.Bounds().Max.Y |
||||
for i := 0; i < n; i++ { |
||||
colorIdx := uint8(randInt(1, circleCount-1)) |
||||
r := randInt(1, maxradius) |
||||
m.drawCircle(randInt(r, maxx-r), randInt(r, maxy-r), r, colorIdx) |
||||
} |
||||
} |
||||
|
||||
func (m *Image) strikeThrough() { |
||||
maxx := m.Bounds().Max.X |
||||
maxy := m.Bounds().Max.Y |
||||
y := randInt(maxy/3, maxy-maxy/3) |
||||
amplitude := randFloat(5, 20) |
||||
period := randFloat(80, 180) |
||||
dx := 2.0 * math.Pi / period |
||||
for x := 0; x < maxx; x++ { |
||||
xo := amplitude * math.Cos(float64(y)*dx) |
||||
yo := amplitude * math.Sin(float64(x)*dx) |
||||
for yn := 0; yn < m.dotSize; yn++ { |
||||
r := randInt(0, m.dotSize) |
||||
m.drawCircle(x+int(xo), y+int(yo)+(yn*m.dotSize), r/2, 1) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (m *Image) drawDigit(digit []byte, x, y int) { |
||||
skf := randFloat(-maxSkew, maxSkew) |
||||
xs := float64(x) |
||||
r := m.dotSize / 2 |
||||
y += randInt(-r, r) |
||||
for yo := 0; yo < fontHeight; yo++ { |
||||
for xo := 0; xo < fontWidth; xo++ { |
||||
if digit[yo*fontWidth+xo] != blackChar { |
||||
continue |
||||
} |
||||
m.drawCircle(x+xo*m.dotSize, y+yo*m.dotSize, r, 1) |
||||
} |
||||
xs += skf |
||||
x = int(xs) |
||||
} |
||||
} |
||||
|
||||
func (m *Image) distort(amplude float64, period float64) { |
||||
w := m.Bounds().Max.X |
||||
h := m.Bounds().Max.Y |
||||
|
||||
oldm := m.Paletted |
||||
newm := image.NewPaletted(image.Rect(0, 0, w, h), oldm.Palette) |
||||
|
||||
dx := 2.0 * math.Pi / period |
||||
for x := 0; x < w; x++ { |
||||
for y := 0; y < h; y++ { |
||||
xo := amplude * math.Sin(float64(y)*dx) |
||||
yo := amplude * math.Cos(float64(x)*dx) |
||||
newm.SetColorIndex(x, y, oldm.ColorIndexAt(x+int(xo), y+int(yo))) |
||||
} |
||||
} |
||||
m.Paletted = newm |
||||
} |
||||
|
||||
func randomBrightness(c color.RGBA, max uint8) color.RGBA { |
||||
minc := min3(c.R, c.G, c.B) |
||||
maxc := max3(c.R, c.G, c.B) |
||||
if maxc > max { |
||||
return c |
||||
} |
||||
n := randIntn(int(max-maxc)) - int(minc) |
||||
return color.RGBA{ |
||||
uint8(int(c.R) + n), |
||||
uint8(int(c.G) + n), |
||||
uint8(int(c.B) + n), |
||||
uint8(c.A), |
||||
} |
||||
} |
||||
|
||||
func min3(x, y, z uint8) (m uint8) { |
||||
m = x |
||||
if y < m { |
||||
m = y |
||||
} |
||||
if z < m { |
||||
m = z |
||||
} |
||||
return |
||||
} |
||||
|
||||
func max3(x, y, z uint8) (m uint8) { |
||||
m = x |
||||
if y > m { |
||||
m = y |
||||
} |
||||
if z > m { |
||||
m = z |
||||
} |
||||
return |
||||
} |
@ -0,0 +1,277 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
//
|
||||
// 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 captcha |
||||
|
||||
import ( |
||||
"crypto/rand" |
||||
"encoding/binary" |
||||
"io" |
||||
"sync" |
||||
) |
||||
|
||||
// siprng is PRNG based on SipHash-2-4.
|
||||
type siprng struct { |
||||
mu sync.Mutex |
||||
k0, k1, ctr uint64 |
||||
} |
||||
|
||||
// siphash implements SipHash-2-4, accepting a uint64 as a message.
|
||||
func siphash(k0, k1, m uint64) uint64 { |
||||
// Initialization.
|
||||
v0 := k0 ^ 0x736f6d6570736575 |
||||
v1 := k1 ^ 0x646f72616e646f6d |
||||
v2 := k0 ^ 0x6c7967656e657261 |
||||
v3 := k1 ^ 0x7465646279746573 |
||||
t := uint64(8) << 56 |
||||
|
||||
// Compression.
|
||||
v3 ^= m |
||||
|
||||
// Round 1.
|
||||
v0 += v1 |
||||
v1 = v1<<13 | v1>>(64-13) |
||||
v1 ^= v0 |
||||
v0 = v0<<32 | v0>>(64-32) |
||||
|
||||
v2 += v3 |
||||
v3 = v3<<16 | v3>>(64-16) |
||||
v3 ^= v2 |
||||
|
||||
v0 += v3 |
||||
v3 = v3<<21 | v3>>(64-21) |
||||
v3 ^= v0 |
||||
|
||||
v2 += v1 |
||||
v1 = v1<<17 | v1>>(64-17) |
||||
v1 ^= v2 |
||||
v2 = v2<<32 | v2>>(64-32) |
||||
|
||||
// Round 2.
|
||||
v0 += v1 |
||||
v1 = v1<<13 | v1>>(64-13) |
||||
v1 ^= v0 |
||||
v0 = v0<<32 | v0>>(64-32) |
||||
|
||||
v2 += v3 |
||||
v3 = v3<<16 | v3>>(64-16) |
||||
v3 ^= v2 |
||||
|
||||
v0 += v3 |
||||
v3 = v3<<21 | v3>>(64-21) |
||||
v3 ^= v0 |
||||
|
||||
v2 += v1 |
||||
v1 = v1<<17 | v1>>(64-17) |
||||
v1 ^= v2 |
||||
v2 = v2<<32 | v2>>(64-32) |
||||
|
||||
v0 ^= m |
||||
|
||||
// Compress last block.
|
||||
v3 ^= t |
||||
|
||||
// Round 1.
|
||||
v0 += v1 |
||||
v1 = v1<<13 | v1>>(64-13) |
||||
v1 ^= v0 |
||||
v0 = v0<<32 | v0>>(64-32) |
||||
|
||||
v2 += v3 |
||||
v3 = v3<<16 | v3>>(64-16) |
||||
v3 ^= v2 |
||||
|
||||
v0 += v3 |
||||
v3 = v3<<21 | v3>>(64-21) |
||||
v3 ^= v0 |
||||
|
||||
v2 += v1 |
||||
v1 = v1<<17 | v1>>(64-17) |
||||
v1 ^= v2 |
||||
v2 = v2<<32 | v2>>(64-32) |
||||
|
||||
// Round 2.
|
||||
v0 += v1 |
||||
v1 = v1<<13 | v1>>(64-13) |
||||
v1 ^= v0 |
||||
v0 = v0<<32 | v0>>(64-32) |
||||
|
||||
v2 += v3 |
||||
v3 = v3<<16 | v3>>(64-16) |
||||
v3 ^= v2 |
||||
|
||||
v0 += v3 |
||||
v3 = v3<<21 | v3>>(64-21) |
||||
v3 ^= v0 |
||||
|
||||
v2 += v1 |
||||
v1 = v1<<17 | v1>>(64-17) |
||||
v1 ^= v2 |
||||
v2 = v2<<32 | v2>>(64-32) |
||||
|
||||
v0 ^= t |
||||
|
||||
// Finalization.
|
||||
v2 ^= 0xff |
||||
|
||||
// Round 1.
|
||||
v0 += v1 |
||||
v1 = v1<<13 | v1>>(64-13) |
||||
v1 ^= v0 |
||||
v0 = v0<<32 | v0>>(64-32) |
||||
|
||||
v2 += v3 |
||||
v3 = v3<<16 | v3>>(64-16) |
||||
v3 ^= v2 |
||||
|
||||
v0 += v3 |
||||
v3 = v3<<21 | v3>>(64-21) |
||||
v3 ^= v0 |
||||
|
||||
v2 += v1 |
||||
v1 = v1<<17 | v1>>(64-17) |
||||
v1 ^= v2 |
||||
v2 = v2<<32 | v2>>(64-32) |
||||
|
||||
// Round 2.
|
||||
v0 += v1 |
||||
v1 = v1<<13 | v1>>(64-13) |
||||
v1 ^= v0 |
||||
v0 = v0<<32 | v0>>(64-32) |
||||
|
||||
v2 += v3 |
||||
v3 = v3<<16 | v3>>(64-16) |
||||
v3 ^= v2 |
||||
|
||||
v0 += v3 |
||||
v3 = v3<<21 | v3>>(64-21) |
||||
v3 ^= v0 |
||||
|
||||
v2 += v1 |
||||
v1 = v1<<17 | v1>>(64-17) |
||||
v1 ^= v2 |
||||
v2 = v2<<32 | v2>>(64-32) |
||||
|
||||
// Round 3.
|
||||
v0 += v1 |
||||
v1 = v1<<13 | v1>>(64-13) |
||||
v1 ^= v0 |
||||
v0 = v0<<32 | v0>>(64-32) |
||||
|
||||
v2 += v3 |
||||
v3 = v3<<16 | v3>>(64-16) |
||||
v3 ^= v2 |
||||
|
||||
v0 += v3 |
||||
v3 = v3<<21 | v3>>(64-21) |
||||
v3 ^= v0 |
||||
|
||||
v2 += v1 |
||||
v1 = v1<<17 | v1>>(64-17) |
||||
v1 ^= v2 |
||||
v2 = v2<<32 | v2>>(64-32) |
||||
|
||||
// Round 4.
|
||||
v0 += v1 |
||||
v1 = v1<<13 | v1>>(64-13) |
||||
v1 ^= v0 |
||||
v0 = v0<<32 | v0>>(64-32) |
||||
|
||||
v2 += v3 |
||||
v3 = v3<<16 | v3>>(64-16) |
||||
v3 ^= v2 |
||||
|
||||
v0 += v3 |
||||
v3 = v3<<21 | v3>>(64-21) |
||||
v3 ^= v0 |
||||
|
||||
v2 += v1 |
||||
v1 = v1<<17 | v1>>(64-17) |
||||
v1 ^= v2 |
||||
v2 = v2<<32 | v2>>(64-32) |
||||
|
||||
return v0 ^ v1 ^ v2 ^ v3 |
||||
} |
||||
|
||||
// rekey sets a new PRNG key, which is read from crypto/rand.
|
||||
func (p *siprng) rekey() { |
||||
var k [16]byte |
||||
if _, err := io.ReadFull(rand.Reader, k[:]); err != nil { |
||||
panic(err.Error()) |
||||
} |
||||
p.k0 = binary.LittleEndian.Uint64(k[0:8]) |
||||
p.k1 = binary.LittleEndian.Uint64(k[8:16]) |
||||
p.ctr = 1 |
||||
} |
||||
|
||||
// Uint64 returns a new pseudorandom uint64.
|
||||
// It rekeys PRNG on the first call and every 64 MB of generated data.
|
||||
func (p *siprng) Uint64() uint64 { |
||||
p.mu.Lock() |
||||
if p.ctr == 0 || p.ctr > 8*1024*1024 { |
||||
p.rekey() |
||||
} |
||||
v := siphash(p.k0, p.k1, p.ctr) |
||||
p.ctr++ |
||||
p.mu.Unlock() |
||||
return v |
||||
} |
||||
|
||||
func (p *siprng) Int63() int64 { |
||||
return int64(p.Uint64() & 0x7fffffffffffffff) |
||||
} |
||||
|
||||
func (p *siprng) Uint32() uint32 { |
||||
return uint32(p.Uint64()) |
||||
} |
||||
|
||||
func (p *siprng) Int31() int32 { |
||||
return int32(p.Uint32() & 0x7fffffff) |
||||
} |
||||
|
||||
func (p *siprng) Intn(n int) int { |
||||
if n <= 0 { |
||||
panic("invalid argument to Intn") |
||||
} |
||||
if n <= 1<<31-1 { |
||||
return int(p.Int31n(int32(n))) |
||||
} |
||||
return int(p.Int63n(int64(n))) |
||||
} |
||||
|
||||
func (p *siprng) Int63n(n int64) int64 { |
||||
if n <= 0 { |
||||
panic("invalid argument to Int63n") |
||||
} |
||||
max := int64((1 << 63) - 1 - (1<<63)%uint64(n)) |
||||
v := p.Int63() |
||||
for v > max { |
||||
v = p.Int63() |
||||
} |
||||
return v % n |
||||
} |
||||
|
||||
func (p *siprng) Int31n(n int32) int32 { |
||||
if n <= 0 { |
||||
panic("invalid argument to Int31n") |
||||
} |
||||
max := int32((1 << 31) - 1 - (1<<31)%uint32(n)) |
||||
v := p.Int31() |
||||
for v > max { |
||||
v = p.Int31() |
||||
} |
||||
return v % n |
||||
} |
||||
|
||||
func (p *siprng) Float64() float64 { return float64(p.Int63()) / (1 << 63) } |
@ -0,0 +1,191 @@ |
||||
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: |
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of |
||||
this License; and |
||||
You must cause any modified files to carry prominent notices stating that You |
||||
changed the files; and |
||||
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 |
||||
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. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work |
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate |
||||
notice, with the fields enclosed by brackets "[]" replaced with your own |
||||
identifying information. (Don't include the brackets!) The text should be |
||||
enclosed in the appropriate comment syntax for the file format. We also |
||||
recommend that a file or class name and description of purpose be included on |
||||
the same "printed page" as the copyright notice for easier identification within |
||||
third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
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. |
@ -0,0 +1,18 @@ |
||||
# csrf [![Build Status](https://travis-ci.org/go-macaron/csrf.svg?branch=master)](https://travis-ci.org/go-macaron/csrf) [![](http://gocover.io/_badge/github.com/go-macaron/csrf)](http://gocover.io/github.com/go-macaron/csrf) |
||||
|
||||
Middleware csrf generates and validates CSRF tokens for [Macaron](https://github.com/go-macaron/macaron). |
||||
|
||||
[API Reference](https://gowalker.org/github.com/go-macaron/csrf) |
||||
|
||||
### Installation |
||||
|
||||
go get github.com/go-macaron/csrf |
||||
|
||||
## Getting Help |
||||
|
||||
- [API Reference](https://gowalker.org/github.com/go-macaron/csrf) |
||||
- [Documentation](http://go-macaron.com/docs/middlewares/csrf) |
||||
|
||||
## License |
||||
|
||||
This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. |
@ -0,0 +1,251 @@ |
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 csrf is a middleware that generates and validates CSRF tokens for Macaron.
|
||||
package csrf |
||||
|
||||
import ( |
||||
"net/http" |
||||
"time" |
||||
|
||||
"github.com/Unknwon/com" |
||||
"github.com/go-macaron/session" |
||||
"gopkg.in/macaron.v1" |
||||
) |
||||
|
||||
const _VERSION = "0.1.0" |
||||
|
||||
func Version() string { |
||||
return _VERSION |
||||
} |
||||
|
||||
// CSRF represents a CSRF service and is used to get the current token and validate a suspect token.
|
||||
type CSRF interface { |
||||
// Return HTTP header to search for token.
|
||||
GetHeaderName() string |
||||
// Return form value to search for token.
|
||||
GetFormName() string |
||||
// Return cookie name to search for token.
|
||||
GetCookieName() string |
||||
// Return cookie path
|
||||
GetCookiePath() string |
||||
// Return the token.
|
||||
GetToken() string |
||||
// Validate by token.
|
||||
ValidToken(t string) bool |
||||
// Error replies to the request with a custom function when ValidToken fails.
|
||||
Error(w http.ResponseWriter) |
||||
} |
||||
|
||||
type csrf struct { |
||||
// Header name value for setting and getting csrf token.
|
||||
Header string |
||||
// Form name value for setting and getting csrf token.
|
||||
Form string |
||||
// Cookie name value for setting and getting csrf token.
|
||||
Cookie string |
||||
//Cookie path
|
||||
CookiePath string |
||||
// Token generated to pass via header, cookie, or hidden form value.
|
||||
Token string |
||||
// This value must be unique per user.
|
||||
ID string |
||||
// Secret used along with the unique id above to generate the Token.
|
||||
Secret string |
||||
// ErrorFunc is the custom function that replies to the request when ValidToken fails.
|
||||
ErrorFunc func(w http.ResponseWriter) |
||||
} |
||||
|
||||
// GetHeaderName returns the name of the HTTP header for csrf token.
|
||||
func (c *csrf) GetHeaderName() string { |
||||
return c.Header |
||||
} |
||||
|
||||
// GetFormName returns the name of the form value for csrf token.
|
||||
func (c *csrf) GetFormName() string { |
||||
return c.Form |
||||
} |
||||
|
||||
// GetCookieName returns the name of the cookie for csrf token.
|
||||
func (c *csrf) GetCookieName() string { |
||||
return c.Cookie |
||||
} |
||||
|
||||
// GetCookiePath returns the path of the cookie for csrf token.
|
||||
func (c *csrf) GetCookiePath() string { |
||||
return c.CookiePath |
||||
} |
||||
|
||||
// GetToken returns the current token. This is typically used
|
||||
// to populate a hidden form in an HTML template.
|
||||
func (c *csrf) GetToken() string { |
||||
return c.Token |
||||
} |
||||
|
||||
// ValidToken validates the passed token against the existing Secret and ID.
|
||||
func (c *csrf) ValidToken(t string) bool { |
||||
return ValidToken(t, c.Secret, c.ID, "POST") |
||||
} |
||||
|
||||
// Error replies to the request when ValidToken fails.
|
||||
func (c *csrf) Error(w http.ResponseWriter) { |
||||
c.ErrorFunc(w) |
||||
} |
||||
|
||||
// Options maintains options to manage behavior of Generate.
|
||||
type Options struct { |
||||
// The global secret value used to generate Tokens.
|
||||
Secret string |
||||
// HTTP header used to set and get token.
|
||||
Header string |
||||
// Form value used to set and get token.
|
||||
Form string |
||||
// Cookie value used to set and get token.
|
||||
Cookie string |
||||
// Cookie path.
|
||||
CookiePath string |
||||
// Key used for getting the unique ID per user.
|
||||
SessionKey string |
||||
// oldSeesionKey saves old value corresponding to SessionKey.
|
||||
oldSeesionKey string |
||||
// If true, send token via X-CSRFToken header.
|
||||
SetHeader bool |
||||
// If true, send token via _csrf cookie.
|
||||
SetCookie bool |
||||
// Set the Secure flag to true on the cookie.
|
||||
Secure bool |
||||
// Disallow Origin appear in request header.
|
||||
Origin bool |
||||
// The function called when Validate fails.
|
||||
ErrorFunc func(w http.ResponseWriter) |
||||
} |
||||
|
||||
func prepareOptions(options []Options) Options { |
||||
var opt Options |
||||
if len(options) > 0 { |
||||
opt = options[0] |
||||
} |
||||
|
||||
// Defaults.
|
||||
if len(opt.Secret) == 0 { |
||||
opt.Secret = string(com.RandomCreateBytes(10)) |
||||
} |
||||
if len(opt.Header) == 0 { |
||||
opt.Header = "X-CSRFToken" |
||||
} |
||||
if len(opt.Form) == 0 { |
||||
opt.Form = "_csrf" |
||||
} |
||||
if len(opt.Cookie) == 0 { |
||||
opt.Cookie = "_csrf" |
||||
} |
||||
if len(opt.CookiePath) == 0 { |
||||
opt.CookiePath = "/" |
||||
} |
||||
if len(opt.SessionKey) == 0 { |
||||
opt.SessionKey = "uid" |
||||
} |
||||
opt.oldSeesionKey = "_old_" + opt.SessionKey |
||||
if opt.ErrorFunc == nil { |
||||
opt.ErrorFunc = func(w http.ResponseWriter) { |
||||
http.Error(w, "Invalid csrf token.", http.StatusBadRequest) |
||||
} |
||||
} |
||||
|
||||
return opt |
||||
} |
||||
|
||||
// Generate maps CSRF to each request. If this request is a Get request, it will generate a new token.
|
||||
// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
|
||||
func Generate(options ...Options) macaron.Handler { |
||||
opt := prepareOptions(options) |
||||
return func(ctx *macaron.Context, sess session.Store) { |
||||
x := &csrf{ |
||||
Secret: opt.Secret, |
||||
Header: opt.Header, |
||||
Form: opt.Form, |
||||
Cookie: opt.Cookie, |
||||
CookiePath: opt.CookiePath, |
||||
ErrorFunc: opt.ErrorFunc, |
||||
} |
||||
ctx.MapTo(x, (*CSRF)(nil)) |
||||
|
||||
if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 { |
||||
return |
||||
} |
||||
|
||||
x.ID = "0" |
||||
uid := sess.Get(opt.SessionKey) |
||||
if uid != nil { |
||||
x.ID = com.ToStr(uid) |
||||
} |
||||
|
||||
needsNew := false |
||||
oldUid := sess.Get(opt.oldSeesionKey) |
||||
if oldUid == nil || oldUid.(string) != x.ID { |
||||
needsNew = true |
||||
sess.Set(opt.oldSeesionKey, x.ID) |
||||
} else { |
||||
// If cookie present, map existing token, else generate a new one.
|
||||
if val := ctx.GetCookie(opt.Cookie); len(val) > 0 { |
||||
// FIXME: test coverage.
|
||||
x.Token = val |
||||
} else { |
||||
needsNew = true |
||||
} |
||||
} |
||||
|
||||
if needsNew { |
||||
// FIXME: actionId.
|
||||
x.Token = GenerateToken(x.Secret, x.ID, "POST") |
||||
if opt.SetCookie { |
||||
ctx.SetCookie(opt.Cookie, x.Token, 0, opt.CookiePath, "", false, true, time.Now().AddDate(0, 0, 1)) |
||||
} |
||||
} |
||||
|
||||
if opt.SetHeader { |
||||
ctx.Resp.Header().Add(opt.Header, x.Token) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Csrfer maps CSRF to each request. If this request is a Get request, it will generate a new token.
|
||||
// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
|
||||
func Csrfer(options ...Options) macaron.Handler { |
||||
return Generate(options...) |
||||
} |
||||
|
||||
// Validate should be used as a per route middleware. It attempts to get a token from a "X-CSRFToken"
|
||||
// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated
|
||||
// using ValidToken. If this validation fails, custom Error is sent in the reply.
|
||||
// If neither a header or form value is found, http.StatusBadRequest is sent.
|
||||
func Validate(ctx *macaron.Context, x CSRF) { |
||||
if token := ctx.Req.Header.Get(x.GetHeaderName()); len(token) > 0 { |
||||
if !x.ValidToken(token) { |
||||
ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath()) |
||||
x.Error(ctx.Resp) |
||||
} |
||||
return |
||||
} |
||||
if token := ctx.Req.FormValue(x.GetFormName()); len(token) > 0 { |
||||
if !x.ValidToken(token) { |
||||
ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath()) |
||||
x.Error(ctx.Resp) |
||||
} |
||||
return |
||||
} |
||||
|
||||
http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest) |
||||
} |
@ -0,0 +1,97 @@ |
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 csrf |
||||
|
||||
import ( |
||||
"bytes" |
||||
"crypto/hmac" |
||||
"crypto/sha1" |
||||
"crypto/subtle" |
||||
"encoding/base64" |
||||
"fmt" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// The duration that XSRF tokens are valid.
|
||||
// It is exported so clients may set cookie timeouts that match generated tokens.
|
||||
const TIMEOUT = 24 * time.Hour |
||||
|
||||
// clean sanitizes a string for inclusion in a token by replacing all ":"s.
|
||||
func clean(s string) string { |
||||
return strings.Replace(s, ":", "_", -1) |
||||
} |
||||
|
||||
// GenerateToken returns a URL-safe secure XSRF token that expires in 24 hours.
|
||||
//
|
||||
// key is a secret key for your application.
|
||||
// userID is a unique identifier for the user.
|
||||
// actionID is the action the user is taking (e.g. POSTing to a particular path).
|
||||
func GenerateToken(key, userID, actionID string) string { |
||||
return generateTokenAtTime(key, userID, actionID, time.Now()) |
||||
} |
||||
|
||||
// generateTokenAtTime is like Generate, but returns a token that expires 24 hours from now.
|
||||
func generateTokenAtTime(key, userID, actionID string, now time.Time) string { |
||||
h := hmac.New(sha1.New, []byte(key)) |
||||
fmt.Fprintf(h, "%s:%s:%d", clean(userID), clean(actionID), now.UnixNano()) |
||||
tok := fmt.Sprintf("%s:%d", h.Sum(nil), now.UnixNano()) |
||||
return base64.URLEncoding.EncodeToString([]byte(tok)) |
||||
} |
||||
|
||||
// Valid returns true if token is a valid, unexpired token returned by Generate.
|
||||
func ValidToken(token, key, userID, actionID string) bool { |
||||
return validTokenAtTime(token, key, userID, actionID, time.Now()) |
||||
} |
||||
|
||||
// validTokenAtTime is like Valid, but it uses now to check if the token is expired.
|
||||
func validTokenAtTime(token, key, userID, actionID string, now time.Time) bool { |
||||
// Decode the token.
|
||||
data, err := base64.URLEncoding.DecodeString(token) |
||||
if err != nil { |
||||
return false |
||||
} |
||||
|
||||
// Extract the issue time of the token.
|
||||
sep := bytes.LastIndex(data, []byte{':'}) |
||||
if sep < 0 { |
||||
return false |
||||
} |
||||
nanos, err := strconv.ParseInt(string(data[sep+1:]), 10, 64) |
||||
if err != nil { |
||||
return false |
||||
} |
||||
issueTime := time.Unix(0, nanos) |
||||
|
||||
// Check that the token is not expired.
|
||||
if now.Sub(issueTime) >= TIMEOUT { |
||||
return false |
||||
} |
||||
|
||||
// Check that the token is not from the future.
|
||||
// Allow 1 minute grace period in case the token is being verified on a
|
||||
// machine whose clock is behind the machine that issued the token.
|
||||
if issueTime.After(now.Add(1 * time.Minute)) { |
||||
return false |
||||
} |
||||
|
||||
expected := generateTokenAtTime(key, userID, actionID, issueTime) |
||||
|
||||
// Check that the token matches the expected value.
|
||||
// Use constant time comparison to avoid timing attacks.
|
||||
return subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1 |
||||
} |
@ -0,0 +1,191 @@ |
||||
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: |
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of |
||||
this License; and |
||||
You must cause any modified files to carry prominent notices stating that You |
||||
changed the files; and |
||||
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 |
||||
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. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work |
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate |
||||
notice, with the fields enclosed by brackets "[]" replaced with your own |
||||
identifying information. (Don't include the brackets!) The text should be |
||||
enclosed in the appropriate comment syntax for the file format. We also |
||||
recommend that a file or class name and description of purpose be included on |
||||
the same "printed page" as the copyright notice for easier identification within |
||||
third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
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. |
@ -0,0 +1,20 @@ |
||||
# gzip [![Build Status](https://travis-ci.org/go-macaron/gzip.svg?branch=master)](https://travis-ci.org/go-macaron/gzip) [![](http://gocover.io/_badge/github.com/go-macaron/gzip)](http://gocover.io/github.com/go-macaron/gzip) |
||||
|
||||
Middleware gzip provides compress to responses for [Macaron](https://github.com/go-macaron/macaron). |
||||
|
||||
### Installation |
||||
|
||||
go get github.com/go-macaron/gzip |
||||
|
||||
## Getting Help |
||||
|
||||
- [API Reference](https://gowalker.org/github.com/go-macaron/gzip) |
||||
- [Documentation](http://go-macaron.com/docs/middlewares/gzip) |
||||
|
||||
## Credits |
||||
|
||||
This package is a modified version of [martini-contrib/gzip](https://github.com/martini-contrib/gzip). |
||||
|
||||
## License |
||||
|
||||
This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. |
@ -0,0 +1,121 @@ |
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2015 The Macaron Authors
|
||||
//
|
||||
// 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 gzip |
||||
|
||||
import ( |
||||
"bufio" |
||||
"fmt" |
||||
"net" |
||||
"net/http" |
||||
"strings" |
||||
|
||||
"github.com/klauspost/compress/gzip" |
||||
"gopkg.in/macaron.v1" |
||||
) |
||||
|
||||
const ( |
||||
_HEADER_ACCEPT_ENCODING = "Accept-Encoding" |
||||
_HEADER_CONTENT_ENCODING = "Content-Encoding" |
||||
_HEADER_CONTENT_LENGTH = "Content-Length" |
||||
_HEADER_CONTENT_TYPE = "Content-Type" |
||||
_HEADER_VARY = "Vary" |
||||
) |
||||
|
||||
// Options represents a struct for specifying configuration options for the GZip middleware.
|
||||
type Options struct { |
||||
// Compression level. Can be DefaultCompression(-1), ConstantCompression(-2)
|
||||
// or any integer value between BestSpeed(1) and BestCompression(9) inclusive.
|
||||
CompressionLevel int |
||||
} |
||||
|
||||
func isCompressionLevelValid(level int) bool { |
||||
return level == gzip.DefaultCompression || |
||||
level == gzip.ConstantCompression || |
||||
(level >= gzip.BestSpeed && level <= gzip.BestCompression) |
||||
} |
||||
|
||||
func prepareOptions(options []Options) Options { |
||||
var opt Options |
||||
if len(options) > 0 { |
||||
opt = options[0] |
||||
} |
||||
|
||||
if !isCompressionLevelValid(opt.CompressionLevel) { |
||||
// For web content, level 4 seems to be a sweet spot.
|
||||
opt.CompressionLevel = 4 |
||||
} |
||||
return opt |
||||
} |
||||
|
||||
// Gziper returns a Handler that adds gzip compression to all requests.
|
||||
// Make sure to include the Gzip middleware above other middleware
|
||||
// that alter the response body (like the render middleware).
|
||||
func Gziper(options ...Options) macaron.Handler { |
||||
opt := prepareOptions(options) |
||||
|
||||
return func(ctx *macaron.Context) { |
||||
if !strings.Contains(ctx.Req.Header.Get(_HEADER_ACCEPT_ENCODING), "gzip") { |
||||
return |
||||
} |
||||
|
||||
headers := ctx.Resp.Header() |
||||
headers.Set(_HEADER_CONTENT_ENCODING, "gzip") |
||||
headers.Set(_HEADER_VARY, _HEADER_ACCEPT_ENCODING) |
||||
|
||||
// We've made sure compression level is valid in prepareGzipOptions,
|
||||
// no need to check same error again.
|
||||
gz, err := gzip.NewWriterLevel(ctx.Resp, opt.CompressionLevel) |
||||
if err != nil { |
||||
panic(err.Error()) |
||||
} |
||||
defer gz.Close() |
||||
|
||||
gzw := gzipResponseWriter{gz, ctx.Resp} |
||||
ctx.Resp = gzw |
||||
ctx.MapTo(gzw, (*http.ResponseWriter)(nil)) |
||||
|
||||
// Check if render middleware has been registered,
|
||||
// if yes, we need to modify ResponseWriter for it as well.
|
||||
if _, ok := ctx.Render.(*macaron.DummyRender); !ok { |
||||
ctx.Render.SetResponseWriter(gzw) |
||||
} |
||||
|
||||
ctx.Next() |
||||
|
||||
// delete content length after we know we have been written to
|
||||
gzw.Header().Del("Content-Length") |
||||
} |
||||
} |
||||
|
||||
type gzipResponseWriter struct { |
||||
w *gzip.Writer |
||||
macaron.ResponseWriter |
||||
} |
||||
|
||||
func (grw gzipResponseWriter) Write(p []byte) (int, error) { |
||||
if len(grw.Header().Get(_HEADER_CONTENT_TYPE)) == 0 { |
||||
grw.Header().Set(_HEADER_CONTENT_TYPE, http.DetectContentType(p)) |
||||
} |
||||
return grw.w.Write(p) |
||||
} |
||||
|
||||
func (grw gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { |
||||
hijacker, ok := grw.ResponseWriter.(http.Hijacker) |
||||
if !ok { |
||||
return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface") |
||||
} |
||||
return hijacker.Hijack() |
||||
} |
@ -0,0 +1,191 @@ |
||||
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: |
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of |
||||
this License; and |
||||
You must cause any modified files to carry prominent notices stating that You |
||||
changed the files; and |
||||
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 |
||||
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. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work |
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate |
||||
notice, with the fields enclosed by brackets "[]" replaced with your own |
||||
identifying information. (Don't include the brackets!) The text should be |
||||
enclosed in the appropriate comment syntax for the file format. We also |
||||
recommend that a file or class name and description of purpose be included on |
||||
the same "printed page" as the copyright notice for easier identification within |
||||
third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
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. |
@ -0,0 +1,16 @@ |
||||
# i18n [![Build Status](https://travis-ci.org/go-macaron/i18n.svg?branch=master)](https://travis-ci.org/go-macaron/i18n) [![](http://gocover.io/_badge/github.com/go-macaron/i18n)](http://gocover.io/github.com/go-macaron/i18n) |
||||
|
||||
Middleware i18n provides app Internationalization and Localization for [Macaron](https://github.com/go-macaron/macaron). |
||||
|
||||
### Installation |
||||
|
||||
go get github.com/go-macaron/i18n |
||||
|
||||
## Getting Help |
||||
|
||||
- [API Reference](https://gowalker.org/github.com/go-macaron/i18n) |
||||
- [Documentation](http://go-macaron.com/docs/middlewares/i18n) |
||||
|
||||
## License |
||||
|
||||
This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. |
@ -0,0 +1,225 @@ |
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 i18n is a middleware that provides app Internationalization and Localization of Macaron.
|
||||
package i18n |
||||
|
||||
import ( |
||||
"fmt" |
||||
"path" |
||||
"strings" |
||||
|
||||
"github.com/Unknwon/com" |
||||
"github.com/Unknwon/i18n" |
||||
"golang.org/x/text/language" |
||||
"gopkg.in/macaron.v1" |
||||
) |
||||
|
||||
const _VERSION = "0.3.0" |
||||
|
||||
func Version() string { |
||||
return _VERSION |
||||
} |
||||
|
||||
// initLocales initializes language type list and Accept-Language header matcher.
|
||||
func initLocales(opt Options) language.Matcher { |
||||
tags := make([]language.Tag, len(opt.Langs)) |
||||
for i, lang := range opt.Langs { |
||||
tags[i] = language.Raw.Make(lang) |
||||
fname := fmt.Sprintf(opt.Format, lang) |
||||
// Append custom locale file.
|
||||
custom := []interface{}{} |
||||
customPath := path.Join(opt.CustomDirectory, fname) |
||||
if com.IsFile(customPath) { |
||||
custom = append(custom, customPath) |
||||
} |
||||
|
||||
var locale interface{} |
||||
if data, ok := opt.Files[fname]; ok { |
||||
locale = data |
||||
} else { |
||||
locale = path.Join(opt.Directory, fname) |
||||
} |
||||
|
||||
err := i18n.SetMessageWithDesc(lang, opt.Names[i], locale, custom...) |
||||
if err != nil && err != i18n.ErrLangAlreadyExist { |
||||
panic(fmt.Errorf("fail to set message file(%s): %v", lang, err)) |
||||
} |
||||
} |
||||
return language.NewMatcher(tags) |
||||
} |
||||
|
||||
// A Locale describles the information of localization.
|
||||
type Locale struct { |
||||
i18n.Locale |
||||
} |
||||
|
||||
// Language returns language current locale represents.
|
||||
func (l Locale) Language() string { |
||||
return l.Lang |
||||
} |
||||
|
||||
// Options represents a struct for specifying configuration options for the i18n middleware.
|
||||
type Options struct { |
||||
// Suburl of path. Default is empty.
|
||||
SubURL string |
||||
// Directory to load locale files. Default is "conf/locale"
|
||||
Directory string |
||||
// File stores actual data of locale files. Used for in-memory purpose.
|
||||
Files map[string][]byte |
||||
// Custom directory to overload locale files. Default is "custom/conf/locale"
|
||||
CustomDirectory string |
||||
// Langauges that will be supported, order is meaningful.
|
||||
Langs []string |
||||
// Human friendly names corresponding to Langs list.
|
||||
Names []string |
||||
// Default language locale, leave empty to remain unset.
|
||||
DefaultLang string |
||||
// Locale file naming style. Default is "locale_%s.ini".
|
||||
Format string |
||||
// Name of language parameter name in URL. Default is "lang".
|
||||
Parameter string |
||||
// Redirect when user uses get parameter to specify language.
|
||||
Redirect bool |
||||
// Name that maps into template variable. Default is "i18n".
|
||||
TmplName string |
||||
// Configuration section name. Default is "i18n".
|
||||
Section string |
||||
} |
||||
|
||||
func prepareOptions(options []Options) Options { |
||||
var opt Options |
||||
if len(options) > 0 { |
||||
opt = options[0] |
||||
} |
||||
|
||||
if len(opt.Section) == 0 { |
||||
opt.Section = "i18n" |
||||
} |
||||
sec := macaron.Config().Section(opt.Section) |
||||
|
||||
opt.SubURL = strings.TrimSuffix(opt.SubURL, "/") |
||||
|
||||
if len(opt.Langs) == 0 { |
||||
opt.Langs = sec.Key("LANGS").Strings(",") |
||||
} |
||||
if len(opt.Names) == 0 { |
||||
opt.Names = sec.Key("NAMES").Strings(",") |
||||
} |
||||
if len(opt.Langs) == 0 { |
||||
panic("no language is specified") |
||||
} else if len(opt.Langs) != len(opt.Names) { |
||||
panic("length of langs is not same as length of names") |
||||
} |
||||
i18n.SetDefaultLang(opt.DefaultLang) |
||||
|
||||
if len(opt.Directory) == 0 { |
||||
opt.Directory = sec.Key("DIRECTORY").MustString("conf/locale") |
||||
} |
||||
if len(opt.CustomDirectory) == 0 { |
||||
opt.CustomDirectory = sec.Key("CUSTOM_DIRECTORY").MustString("custom/conf/locale") |
||||
} |
||||
if len(opt.Format) == 0 { |
||||
opt.Format = sec.Key("FORMAT").MustString("locale_%s.ini") |
||||
} |
||||
if len(opt.Parameter) == 0 { |
||||
opt.Parameter = sec.Key("PARAMETER").MustString("lang") |
||||
} |
||||
if !opt.Redirect { |
||||
opt.Redirect = sec.Key("REDIRECT").MustBool() |
||||
} |
||||
if len(opt.TmplName) == 0 { |
||||
opt.TmplName = sec.Key("TMPL_NAME").MustString("i18n") |
||||
} |
||||
|
||||
return opt |
||||
} |
||||
|
||||
type LangType struct { |
||||
Lang, Name string |
||||
} |
||||
|
||||
// I18n is a middleware provides localization layer for your application.
|
||||
// Paramenter langs must be in the form of "en-US", "zh-CN", etc.
|
||||
// Otherwise it may not recognize browser input.
|
||||
func I18n(options ...Options) macaron.Handler { |
||||
opt := prepareOptions(options) |
||||
m := initLocales(opt) |
||||
return func(ctx *macaron.Context) { |
||||
isNeedRedir := false |
||||
hasCookie := false |
||||
|
||||
// 1. Check URL arguments.
|
||||
lang := ctx.Query(opt.Parameter) |
||||
|
||||
// 2. Get language information from cookies.
|
||||
if len(lang) == 0 { |
||||
lang = ctx.GetCookie("lang") |
||||
hasCookie = true |
||||
} else { |
||||
isNeedRedir = true |
||||
} |
||||
|
||||
// Check again in case someone modify by purpose.
|
||||
if !i18n.IsExist(lang) { |
||||
lang = "" |
||||
isNeedRedir = false |
||||
hasCookie = false |
||||
} |
||||
|
||||
// 3. Get language information from 'Accept-Language'.
|
||||
// The first element in the list is chosen to be the default language automatically.
|
||||
if len(lang) == 0 { |
||||
tags, _, _ := language.ParseAcceptLanguage(ctx.Req.Header.Get("Accept-Language")) |
||||
tag, _, _ := m.Match(tags...) |
||||
lang = tag.String() |
||||
isNeedRedir = false |
||||
} |
||||
|
||||
curLang := LangType{ |
||||
Lang: lang, |
||||
} |
||||
|
||||
// Save language information in cookies.
|
||||
if !hasCookie { |
||||
ctx.SetCookie("lang", curLang.Lang, 1<<31-1, "/"+strings.TrimPrefix(opt.SubURL, "/")) |
||||
} |
||||
|
||||
restLangs := make([]LangType, 0, i18n.Count()-1) |
||||
langs := i18n.ListLangs() |
||||
names := i18n.ListLangDescs() |
||||
for i, v := range langs { |
||||
if lang != v { |
||||
restLangs = append(restLangs, LangType{v, names[i]}) |
||||
} else { |
||||
curLang.Name = names[i] |
||||
} |
||||
} |
||||
|
||||
// Set language properties.
|
||||
locale := Locale{i18n.Locale{lang}} |
||||
ctx.Map(locale) |
||||
ctx.Locale = locale |
||||
ctx.Data[opt.TmplName] = locale |
||||
ctx.Data["Tr"] = i18n.Tr |
||||
ctx.Data["Lang"] = locale.Lang |
||||
ctx.Data["LangName"] = curLang.Name |
||||
ctx.Data["AllLangs"] = append([]LangType{curLang}, restLangs...) |
||||
ctx.Data["RestLangs"] = restLangs |
||||
|
||||
if opt.Redirect && isNeedRedir { |
||||
ctx.Redirect(opt.SubURL + ctx.Req.RequestURI[:strings.Index(ctx.Req.RequestURI, "?")]) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,191 @@ |
||||
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: |
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of |
||||
this License; and |
||||
You must cause any modified files to carry prominent notices stating that You |
||||
changed the files; and |
||||
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 |
||||
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. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work |
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate |
||||
notice, with the fields enclosed by brackets "[]" replaced with your own |
||||
identifying information. (Don't include the brackets!) The text should be |
||||
enclosed in the appropriate comment syntax for the file format. We also |
||||
recommend that a file or class name and description of purpose be included on |
||||
the same "printed page" as the copyright notice for easier identification within |
||||
third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
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. |
@ -0,0 +1,11 @@ |
||||
# inject [![Build Status](https://travis-ci.org/go-macaron/inject.svg?branch=master)](https://travis-ci.org/go-macaron/inject) [![](http://gocover.io/_badge/github.com/go-macaron/inject)](http://gocover.io/github.com/go-macaron/inject) |
||||
|
||||
Package inject provides utilities for mapping and injecting dependencies in various ways. |
||||
|
||||
**This a modified version of [codegangsta/inject](https://github.com/codegangsta/inject) for special purpose of Macaron** |
||||
|
||||
**Please use the original version if you need dependency injection feature** |
||||
|
||||
## License |
||||
|
||||
This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. |
@ -0,0 +1,262 @@ |
||||
// Copyright 2013 Jeremy Saenz
|
||||
// Copyright 2015 The Macaron Authors
|
||||
//
|
||||
// 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 inject provides utilities for mapping and injecting dependencies in various ways.
|
||||
package inject |
||||
|
||||
import ( |
||||
"fmt" |
||||
"reflect" |
||||
) |
||||
|
||||
// Injector represents an interface for mapping and injecting dependencies into structs
|
||||
// and function arguments.
|
||||
type Injector interface { |
||||
Applicator |
||||
Invoker |
||||
TypeMapper |
||||
// SetParent sets the parent of the injector. If the injector cannot find a
|
||||
// dependency in its Type map it will check its parent before returning an
|
||||
// error.
|
||||
SetParent(Injector) |
||||
} |
||||
|
||||
// Applicator represents an interface for mapping dependencies to a struct.
|
||||
type Applicator interface { |
||||
// Maps dependencies in the Type map to each field in the struct
|
||||
// that is tagged with 'inject'. Returns an error if the injection
|
||||
// fails.
|
||||
Apply(interface{}) error |
||||
} |
||||
|
||||
// Invoker represents an interface for calling functions via reflection.
|
||||
type Invoker interface { |
||||
// Invoke attempts to call the interface{} provided as a function,
|
||||
// providing dependencies for function arguments based on Type. Returns
|
||||
// a slice of reflect.Value representing the returned values of the function.
|
||||
// Returns an error if the injection fails.
|
||||
Invoke(interface{}) ([]reflect.Value, error) |
||||
} |
||||
|
||||
// FastInvoker represents an interface in order to avoid the calling function via reflection.
|
||||
//
|
||||
// example:
|
||||
// type handlerFuncHandler func(http.ResponseWriter, *http.Request) error
|
||||
// func (f handlerFuncHandler)Invoke([]interface{}) ([]reflect.Value, error){
|
||||
// ret := f(p[0].(http.ResponseWriter), p[1].(*http.Request))
|
||||
// return []reflect.Value{reflect.ValueOf(ret)}, nil
|
||||
// }
|
||||
//
|
||||
// type funcHandler func(int, string)
|
||||
// func (f funcHandler)Invoke([]interface{}) ([]reflect.Value, error){
|
||||
// f(p[0].(int), p[1].(string))
|
||||
// return nil, nil
|
||||
// }
|
||||
type FastInvoker interface { |
||||
// Invoke attempts to call the ordinary functions. If f is a function
|
||||
// with the appropriate signature, f.Invoke([]interface{}) is a Call that calls f.
|
||||
// Returns a slice of reflect.Value representing the returned values of the function.
|
||||
// Returns an error if the injection fails.
|
||||
Invoke([]interface{}) ([]reflect.Value, error) |
||||
} |
||||
|
||||
// IsFastInvoker check interface is FastInvoker
|
||||
func IsFastInvoker(h interface{}) bool { |
||||
_, ok := h.(FastInvoker) |
||||
return ok |
||||
} |
||||
|
||||
// TypeMapper represents an interface for mapping interface{} values based on type.
|
||||
type TypeMapper interface { |
||||
// Maps the interface{} value based on its immediate type from reflect.TypeOf.
|
||||
Map(interface{}) TypeMapper |
||||
// Maps the interface{} value based on the pointer of an Interface provided.
|
||||
// This is really only useful for mapping a value as an interface, as interfaces
|
||||
// cannot at this time be referenced directly without a pointer.
|
||||
MapTo(interface{}, interface{}) TypeMapper |
||||
// Provides a possibility to directly insert a mapping based on type and value.
|
||||
// This makes it possible to directly map type arguments not possible to instantiate
|
||||
// with reflect like unidirectional channels.
|
||||
Set(reflect.Type, reflect.Value) TypeMapper |
||||
// Returns the Value that is mapped to the current type. Returns a zeroed Value if
|
||||
// the Type has not been mapped.
|
||||
GetVal(reflect.Type) reflect.Value |
||||
} |
||||
|
||||
type injector struct { |
||||
values map[reflect.Type]reflect.Value |
||||
parent Injector |
||||
} |
||||
|
||||
// InterfaceOf dereferences a pointer to an Interface type.
|
||||
// It panics if value is not an pointer to an interface.
|
||||
func InterfaceOf(value interface{}) reflect.Type { |
||||
t := reflect.TypeOf(value) |
||||
|
||||
for t.Kind() == reflect.Ptr { |
||||
t = t.Elem() |
||||
} |
||||
|
||||
if t.Kind() != reflect.Interface { |
||||
panic("Called inject.InterfaceOf with a value that is not a pointer to an interface. (*MyInterface)(nil)") |
||||
} |
||||
|
||||
return t |
||||
} |
||||
|
||||
// New returns a new Injector.
|
||||
func New() Injector { |
||||
return &injector{ |
||||
values: make(map[reflect.Type]reflect.Value), |
||||
} |
||||
} |
||||
|
||||
// Invoke attempts to call the interface{} provided as a function,
|
||||
// providing dependencies for function arguments based on Type.
|
||||
// Returns a slice of reflect.Value representing the returned values of the function.
|
||||
// Returns an error if the injection fails.
|
||||
// It panics if f is not a function
|
||||
func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) { |
||||
t := reflect.TypeOf(f) |
||||
switch v := f.(type) { |
||||
case FastInvoker: |
||||
return inj.fastInvoke(v, t, t.NumIn()) |
||||
default: |
||||
return inj.callInvoke(f, t, t.NumIn()) |
||||
} |
||||
} |
||||
|
||||
func (inj *injector) fastInvoke(f FastInvoker, t reflect.Type, numIn int) ([]reflect.Value, error) { |
||||
var in []interface{} |
||||
if numIn > 0 { |
||||
in = make([]interface{}, numIn) // Panic if t is not kind of Func
|
||||
var argType reflect.Type |
||||
var val reflect.Value |
||||
for i := 0; i < numIn; i++ { |
||||
argType = t.In(i) |
||||
val = inj.GetVal(argType) |
||||
if !val.IsValid() { |
||||
return nil, fmt.Errorf("Value not found for type %v", argType) |
||||
} |
||||
|
||||
in[i] = val.Interface() |
||||
} |
||||
} |
||||
return f.Invoke(in) |
||||
} |
||||
|
||||
// callInvoke reflect.Value.Call
|
||||
func (inj *injector) callInvoke(f interface{}, t reflect.Type, numIn int) ([]reflect.Value, error) { |
||||
var in []reflect.Value |
||||
if numIn > 0 { |
||||
in = make([]reflect.Value, numIn) |
||||
var argType reflect.Type |
||||
var val reflect.Value |
||||
for i := 0; i < numIn; i++ { |
||||
argType = t.In(i) |
||||
val = inj.GetVal(argType) |
||||
if !val.IsValid() { |
||||
return nil, fmt.Errorf("Value not found for type %v", argType) |
||||
} |
||||
|
||||
in[i] = val |
||||
} |
||||
} |
||||
return reflect.ValueOf(f).Call(in), nil |
||||
} |
||||
|
||||
// Maps dependencies in the Type map to each field in the struct
|
||||
// that is tagged with 'inject'.
|
||||
// Returns an error if the injection fails.
|
||||
func (inj *injector) Apply(val interface{}) error { |
||||
v := reflect.ValueOf(val) |
||||
|
||||
for v.Kind() == reflect.Ptr { |
||||
v = v.Elem() |
||||
} |
||||
|
||||
if v.Kind() != reflect.Struct { |
||||
return nil // Should not panic here ?
|
||||
} |
||||
|
||||
t := v.Type() |
||||
|
||||
for i := 0; i < v.NumField(); i++ { |
||||
f := v.Field(i) |
||||
structField := t.Field(i) |
||||
if f.CanSet() && (structField.Tag == "inject" || structField.Tag.Get("inject") != "") { |
||||
ft := f.Type() |
||||
v := inj.GetVal(ft) |
||||
if !v.IsValid() { |
||||
return fmt.Errorf("Value not found for type %v", ft) |
||||
} |
||||
|
||||
f.Set(v) |
||||
} |
||||
|
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// Maps the concrete value of val to its dynamic type using reflect.TypeOf,
|
||||
// It returns the TypeMapper registered in.
|
||||
func (i *injector) Map(val interface{}) TypeMapper { |
||||
i.values[reflect.TypeOf(val)] = reflect.ValueOf(val) |
||||
return i |
||||
} |
||||
|
||||
func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper { |
||||
i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val) |
||||
return i |
||||
} |
||||
|
||||
// Maps the given reflect.Type to the given reflect.Value and returns
|
||||
// the Typemapper the mapping has been registered in.
|
||||
func (i *injector) Set(typ reflect.Type, val reflect.Value) TypeMapper { |
||||
i.values[typ] = val |
||||
return i |
||||
} |
||||
|
||||
func (i *injector) GetVal(t reflect.Type) reflect.Value { |
||||
val := i.values[t] |
||||
|
||||
if val.IsValid() { |
||||
return val |
||||
} |
||||
|
||||
// no concrete types found, try to find implementors
|
||||
// if t is an interface
|
||||
if t.Kind() == reflect.Interface { |
||||
for k, v := range i.values { |
||||
if k.Implements(t) { |
||||
val = v |
||||
break |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Still no type found, try to look it up on the parent
|
||||
if !val.IsValid() && i.parent != nil { |
||||
val = i.parent.GetVal(t) |
||||
} |
||||
|
||||
return val |
||||
|
||||
} |
||||
|
||||
func (i *injector) SetParent(parent Injector) { |
||||
i.parent = parent |
||||
} |
@ -0,0 +1,191 @@ |
||||
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: |
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of |
||||
this License; and |
||||
You must cause any modified files to carry prominent notices stating that You |
||||
changed the files; and |
||||
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 |
||||
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. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work |
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate |
||||
notice, with the fields enclosed by brackets "[]" replaced with your own |
||||
identifying information. (Don't include the brackets!) The text should be |
||||
enclosed in the appropriate comment syntax for the file format. We also |
||||
recommend that a file or class name and description of purpose be included on |
||||
the same "printed page" as the copyright notice for easier identification within |
||||
third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
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. |
@ -0,0 +1,20 @@ |
||||
# session [![Build Status](https://travis-ci.org/go-macaron/session.svg?branch=master)](https://travis-ci.org/go-macaron/session) [![](http://gocover.io/_badge/github.com/go-macaron/session)](http://gocover.io/github.com/go-macaron/session) |
||||
|
||||
Middleware session provides session management for [Macaron](https://github.com/go-macaron/macaron). It can use many session providers, including memory, file, Redis, Memcache, PostgreSQL, MySQL, Couchbase, Ledis and Nodb. |
||||
|
||||
### Installation |
||||
|
||||
go get github.com/go-macaron/session |
||||
|
||||
## Getting Help |
||||
|
||||
- [API Reference](https://gowalker.org/github.com/go-macaron/session) |
||||
- [Documentation](http://go-macaron.com/docs/middlewares/session) |
||||
|
||||
## Credits |
||||
|
||||
This package is a modified version of [beego/session](https://github.com/astaxie/beego/tree/master/session). |
||||
|
||||
## License |
||||
|
||||
This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. |
@ -0,0 +1,261 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 session |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io/ioutil" |
||||
"log" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/Unknwon/com" |
||||
) |
||||
|
||||
// FileStore represents a file session store implementation.
|
||||
type FileStore struct { |
||||
p *FileProvider |
||||
sid string |
||||
lock sync.RWMutex |
||||
data map[interface{}]interface{} |
||||
} |
||||
|
||||
// NewFileStore creates and returns a file session store.
|
||||
func NewFileStore(p *FileProvider, sid string, kv map[interface{}]interface{}) *FileStore { |
||||
return &FileStore{ |
||||
p: p, |
||||
sid: sid, |
||||
data: kv, |
||||
} |
||||
} |
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *FileStore) Set(key, val interface{}) error { |
||||
s.lock.Lock() |
||||
defer s.lock.Unlock() |
||||
|
||||
s.data[key] = val |
||||
return nil |
||||
} |
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *FileStore) Get(key interface{}) interface{} { |
||||
s.lock.RLock() |
||||
defer s.lock.RUnlock() |
||||
|
||||
return s.data[key] |
||||
} |
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *FileStore) Delete(key interface{}) error { |
||||
s.lock.Lock() |
||||
defer s.lock.Unlock() |
||||
|
||||
delete(s.data, key) |
||||
return nil |
||||
} |
||||
|
||||
// ID returns current session ID.
|
||||
func (s *FileStore) ID() string { |
||||
return s.sid |
||||
} |
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *FileStore) Release() error { |
||||
s.p.lock.Lock() |
||||
defer s.p.lock.Unlock() |
||||
|
||||
data, err := EncodeGob(s.data) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
return ioutil.WriteFile(s.p.filepath(s.sid), data, os.ModePerm) |
||||
} |
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *FileStore) Flush() error { |
||||
s.lock.Lock() |
||||
defer s.lock.Unlock() |
||||
|
||||
s.data = make(map[interface{}]interface{}) |
||||
return nil |
||||
} |
||||
|
||||
// FileProvider represents a file session provider implementation.
|
||||
type FileProvider struct { |
||||
lock sync.RWMutex |
||||
maxlifetime int64 |
||||
rootPath string |
||||
} |
||||
|
||||
// Init initializes file session provider with given root path.
|
||||
func (p *FileProvider) Init(maxlifetime int64, rootPath string) error { |
||||
p.lock.Lock() |
||||
p.maxlifetime = maxlifetime |
||||
p.rootPath = rootPath |
||||
p.lock.Unlock() |
||||
return nil |
||||
} |
||||
|
||||
func (p *FileProvider) filepath(sid string) string { |
||||
return path.Join(p.rootPath, string(sid[0]), string(sid[1]), sid) |
||||
} |
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *FileProvider) Read(sid string) (_ RawStore, err error) { |
||||
filename := p.filepath(sid) |
||||
if err = os.MkdirAll(path.Dir(filename), os.ModePerm); err != nil { |
||||
return nil, err |
||||
} |
||||
p.lock.RLock() |
||||
defer p.lock.RUnlock() |
||||
|
||||
var f *os.File |
||||
if com.IsFile(filename) { |
||||
f, err = os.OpenFile(filename, os.O_RDWR, os.ModePerm) |
||||
} else { |
||||
f, err = os.Create(filename) |
||||
} |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer f.Close() |
||||
|
||||
if err = os.Chtimes(filename, time.Now(), time.Now()); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
var kv map[interface{}]interface{} |
||||
data, err := ioutil.ReadAll(f) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if len(data) == 0 { |
||||
kv = make(map[interface{}]interface{}) |
||||
} else { |
||||
kv, err = DecodeGob(data) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
return NewFileStore(p, sid, kv), nil |
||||
} |
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *FileProvider) Exist(sid string) bool { |
||||
p.lock.RLock() |
||||
defer p.lock.RUnlock() |
||||
return com.IsFile(p.filepath(sid)) |
||||
} |
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *FileProvider) Destory(sid string) error { |
||||
p.lock.Lock() |
||||
defer p.lock.Unlock() |
||||
return os.Remove(p.filepath(sid)) |
||||
} |
||||
|
||||
func (p *FileProvider) regenerate(oldsid, sid string) (err error) { |
||||
p.lock.Lock() |
||||
defer p.lock.Unlock() |
||||
|
||||
filename := p.filepath(sid) |
||||
if com.IsExist(filename) { |
||||
return fmt.Errorf("new sid '%s' already exists", sid) |
||||
} |
||||
|
||||
oldname := p.filepath(oldsid) |
||||
if !com.IsFile(oldname) { |
||||
data, err := EncodeGob(make(map[interface{}]interface{})) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err = os.MkdirAll(path.Dir(oldname), os.ModePerm); err != nil { |
||||
return err |
||||
} |
||||
if err = ioutil.WriteFile(oldname, data, os.ModePerm); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
if err = os.MkdirAll(path.Dir(filename), os.ModePerm); err != nil { |
||||
return err |
||||
} |
||||
if err = os.Rename(oldname, filename); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *FileProvider) Regenerate(oldsid, sid string) (_ RawStore, err error) { |
||||
if err := p.regenerate(oldsid, sid); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return p.Read(sid) |
||||
} |
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *FileProvider) Count() int { |
||||
count := 0 |
||||
if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error { |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if !fi.IsDir() { |
||||
count++ |
||||
} |
||||
return nil |
||||
}); err != nil { |
||||
log.Printf("error counting session files: %v", err) |
||||
return 0 |
||||
} |
||||
return count |
||||
} |
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (p *FileProvider) GC() { |
||||
p.lock.RLock() |
||||
defer p.lock.RUnlock() |
||||
|
||||
if !com.IsExist(p.rootPath) { |
||||
return |
||||
} |
||||
|
||||
if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error { |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if !fi.IsDir() && |
||||
(fi.ModTime().Unix()+p.maxlifetime) < time.Now().Unix() { |
||||
return os.Remove(path) |
||||
} |
||||
return nil |
||||
}); err != nil { |
||||
log.Printf("error garbage collecting session files: %v", err) |
||||
} |
||||
} |
||||
|
||||
func init() { |
||||
Register("file", &FileProvider{}) |
||||
} |
@ -0,0 +1,217 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 session |
||||
|
||||
import ( |
||||
"container/list" |
||||
"fmt" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
// MemStore represents a in-memory session store implementation.
|
||||
type MemStore struct { |
||||
sid string |
||||
lock sync.RWMutex |
||||
data map[interface{}]interface{} |
||||
lastAccess time.Time |
||||
} |
||||
|
||||
// NewMemStore creates and returns a memory session store.
|
||||
func NewMemStore(sid string) *MemStore { |
||||
return &MemStore{ |
||||
sid: sid, |
||||
data: make(map[interface{}]interface{}), |
||||
lastAccess: time.Now(), |
||||
} |
||||
} |
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *MemStore) Set(key, val interface{}) error { |
||||
s.lock.Lock() |
||||
defer s.lock.Unlock() |
||||
|
||||
s.data[key] = val |
||||
return nil |
||||
} |
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *MemStore) Get(key interface{}) interface{} { |
||||
s.lock.RLock() |
||||
defer s.lock.RUnlock() |
||||
|
||||
return s.data[key] |
||||
} |
||||
|
||||
// Delete deletes a key from session.
|
||||
func (s *MemStore) Delete(key interface{}) error { |
||||
s.lock.Lock() |
||||
defer s.lock.Unlock() |
||||
|
||||
delete(s.data, key) |
||||
return nil |
||||
} |
||||
|
||||
// ID returns current session ID.
|
||||
func (s *MemStore) ID() string { |
||||
return s.sid |
||||
} |
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (_ *MemStore) Release() error { |
||||
return nil |
||||
} |
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *MemStore) Flush() error { |
||||
s.lock.Lock() |
||||
defer s.lock.Unlock() |
||||
|
||||
s.data = make(map[interface{}]interface{}) |
||||
return nil |
||||
} |
||||
|
||||
// MemProvider represents a in-memory session provider implementation.
|
||||
type MemProvider struct { |
||||
lock sync.RWMutex |
||||
maxLifetime int64 |
||||
data map[string]*list.Element |
||||
// A priority list whose lastAccess newer gets higer priority.
|
||||
list *list.List |
||||
} |
||||
|
||||
// Init initializes memory session provider.
|
||||
func (p *MemProvider) Init(maxLifetime int64, _ string) error { |
||||
p.lock.Lock() |
||||
p.maxLifetime = maxLifetime |
||||
p.lock.Unlock() |
||||
return nil |
||||
} |
||||
|
||||
// update expands time of session store by given ID.
|
||||
func (p *MemProvider) update(sid string) error { |
||||
p.lock.Lock() |
||||
defer p.lock.Unlock() |
||||
|
||||
if e, ok := p.data[sid]; ok { |
||||
e.Value.(*MemStore).lastAccess = time.Now() |
||||
p.list.MoveToFront(e) |
||||
return nil |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *MemProvider) Read(sid string) (_ RawStore, err error) { |
||||
p.lock.RLock() |
||||
e, ok := p.data[sid] |
||||
p.lock.RUnlock() |
||||
|
||||
if ok { |
||||
if err = p.update(sid); err != nil { |
||||
return nil, err |
||||
} |
||||
return e.Value.(*MemStore), nil |
||||
} |
||||
|
||||
// Create a new session.
|
||||
p.lock.Lock() |
||||
defer p.lock.Unlock() |
||||
|
||||
s := NewMemStore(sid) |
||||
p.data[sid] = p.list.PushBack(s) |
||||
return s, nil |
||||
} |
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *MemProvider) Exist(sid string) bool { |
||||
p.lock.RLock() |
||||
defer p.lock.RUnlock() |
||||
|
||||
_, ok := p.data[sid] |
||||
return ok |
||||
} |
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *MemProvider) Destory(sid string) error { |
||||
p.lock.Lock() |
||||
defer p.lock.Unlock() |
||||
|
||||
e, ok := p.data[sid] |
||||
if !ok { |
||||
return nil |
||||
} |
||||
|
||||
p.list.Remove(e) |
||||
delete(p.data, sid) |
||||
return nil |
||||
} |
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *MemProvider) Regenerate(oldsid, sid string) (RawStore, error) { |
||||
if p.Exist(sid) { |
||||
return nil, fmt.Errorf("new sid '%s' already exists", sid) |
||||
} |
||||
|
||||
s, err := p.Read(oldsid) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if err = p.Destory(oldsid); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
s.(*MemStore).sid = sid |
||||
|
||||
p.lock.Lock() |
||||
defer p.lock.Unlock() |
||||
p.data[sid] = p.list.PushBack(s) |
||||
return s, nil |
||||
} |
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *MemProvider) Count() int { |
||||
return p.list.Len() |
||||
} |
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (p *MemProvider) GC() { |
||||
p.lock.RLock() |
||||
for { |
||||
// No session in the list.
|
||||
e := p.list.Back() |
||||
if e == nil { |
||||
break |
||||
} |
||||
|
||||
if (e.Value.(*MemStore).lastAccess.Unix() + p.maxLifetime) < time.Now().Unix() { |
||||
p.lock.RUnlock() |
||||
p.lock.Lock() |
||||
p.list.Remove(e) |
||||
delete(p.data, e.Value.(*MemStore).sid) |
||||
p.lock.Unlock() |
||||
p.lock.RLock() |
||||
} else { |
||||
break |
||||
} |
||||
} |
||||
p.lock.RUnlock() |
||||
} |
||||
|
||||
func init() { |
||||
Register("memory", &MemProvider{list: list.New(), data: make(map[string]*list.Element)}) |
||||
} |
@ -0,0 +1,235 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 session |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strings" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/Unknwon/com" |
||||
"gopkg.in/ini.v1" |
||||
"gopkg.in/redis.v2" |
||||
|
||||
"github.com/go-macaron/session" |
||||
) |
||||
|
||||
// RedisStore represents a redis session store implementation.
|
||||
type RedisStore struct { |
||||
c *redis.Client |
||||
prefix, sid string |
||||
duration time.Duration |
||||
lock sync.RWMutex |
||||
data map[interface{}]interface{} |
||||
} |
||||
|
||||
// NewRedisStore creates and returns a redis session store.
|
||||
func NewRedisStore(c *redis.Client, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore { |
||||
return &RedisStore{ |
||||
c: c, |
||||
prefix: prefix, |
||||
sid: sid, |
||||
duration: dur, |
||||
data: kv, |
||||
} |
||||
} |
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *RedisStore) Set(key, val interface{}) error { |
||||
s.lock.Lock() |
||||
defer s.lock.Unlock() |
||||
|
||||
s.data[key] = val |
||||
return nil |
||||
} |
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *RedisStore) Get(key interface{}) interface{} { |
||||
s.lock.RLock() |
||||
defer s.lock.RUnlock() |
||||
|
||||
return s.data[key] |
||||
} |
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *RedisStore) Delete(key interface{}) error { |
||||
s.lock.Lock() |
||||
defer s.lock.Unlock() |
||||
|
||||
delete(s.data, key) |
||||
return nil |
||||
} |
||||
|
||||
// ID returns current session ID.
|
||||
func (s *RedisStore) ID() string { |
||||
return s.sid |
||||
} |
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *RedisStore) Release() error { |
||||
data, err := session.EncodeGob(s.data) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
return s.c.SetEx(s.prefix+s.sid, s.duration, string(data)).Err() |
||||
} |
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *RedisStore) Flush() error { |
||||
s.lock.Lock() |
||||
defer s.lock.Unlock() |
||||
|
||||
s.data = make(map[interface{}]interface{}) |
||||
return nil |
||||
} |
||||
|
||||
// RedisProvider represents a redis session provider implementation.
|
||||
type RedisProvider struct { |
||||
c *redis.Client |
||||
duration time.Duration |
||||
prefix string |
||||
} |
||||
|
||||
// Init initializes redis session provider.
|
||||
// configs: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180,prefix=session;
|
||||
func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) { |
||||
p.duration, err = time.ParseDuration(fmt.Sprintf("%ds", maxlifetime)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
cfg, err := ini.Load([]byte(strings.Replace(configs, ",", "\n", -1))) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
opt := &redis.Options{ |
||||
Network: "tcp", |
||||
} |
||||
for k, v := range cfg.Section("").KeysHash() { |
||||
switch k { |
||||
case "network": |
||||
opt.Network = v |
||||
case "addr": |
||||
opt.Addr = v |
||||
case "password": |
||||
opt.Password = v |
||||
case "db": |
||||
opt.DB = com.StrTo(v).MustInt64() |
||||
case "pool_size": |
||||
opt.PoolSize = com.StrTo(v).MustInt() |
||||
case "idle_timeout": |
||||
opt.IdleTimeout, err = time.ParseDuration(v + "s") |
||||
if err != nil { |
||||
return fmt.Errorf("error parsing idle timeout: %v", err) |
||||
} |
||||
case "prefix": |
||||
p.prefix = v |
||||
default: |
||||
return fmt.Errorf("session/redis: unsupported option '%s'", k) |
||||
} |
||||
} |
||||
|
||||
p.c = redis.NewClient(opt) |
||||
return p.c.Ping().Err() |
||||
} |
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *RedisProvider) Read(sid string) (session.RawStore, error) { |
||||
psid := p.prefix + sid |
||||
if !p.Exist(sid) { |
||||
if err := p.c.Set(psid, "").Err(); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
var kv map[interface{}]interface{} |
||||
kvs, err := p.c.Get(psid).Result() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if len(kvs) == 0 { |
||||
kv = make(map[interface{}]interface{}) |
||||
} else { |
||||
kv, err = session.DecodeGob([]byte(kvs)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil |
||||
} |
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *RedisProvider) Exist(sid string) bool { |
||||
has, err := p.c.Exists(p.prefix + sid).Result() |
||||
return err == nil && has |
||||
} |
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *RedisProvider) Destory(sid string) error { |
||||
return p.c.Del(p.prefix + sid).Err() |
||||
} |
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) { |
||||
poldsid := p.prefix + oldsid |
||||
psid := p.prefix + sid |
||||
|
||||
if p.Exist(sid) { |
||||
return nil, fmt.Errorf("new sid '%s' already exists", sid) |
||||
} else if !p.Exist(oldsid) { |
||||
// Make a fake old session.
|
||||
if err = p.c.SetEx(poldsid, p.duration, "").Err(); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
if err = p.c.Rename(poldsid, psid).Err(); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
var kv map[interface{}]interface{} |
||||
kvs, err := p.c.Get(psid).Result() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if len(kvs) == 0 { |
||||
kv = make(map[interface{}]interface{}) |
||||
} else { |
||||
kv, err = session.DecodeGob([]byte(kvs)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil |
||||
} |
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *RedisProvider) Count() int { |
||||
return int(p.c.DbSize().Val()) |
||||
} |
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (_ *RedisProvider) GC() {} |
||||
|
||||
func init() { |
||||
session.Register("redis", &RedisProvider{}) |
||||
} |
@ -0,0 +1 @@ |
||||
ignore |
@ -0,0 +1,399 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 session a middleware that provides the session management of Macaron.
|
||||
package session |
||||
|
||||
import ( |
||||
"encoding/hex" |
||||
"fmt" |
||||
"net/http" |
||||
"net/url" |
||||
"time" |
||||
|
||||
"gopkg.in/macaron.v1" |
||||
) |
||||
|
||||
const _VERSION = "0.3.0" |
||||
|
||||
func Version() string { |
||||
return _VERSION |
||||
} |
||||
|
||||
// RawStore is the interface that operates the session data.
|
||||
type RawStore interface { |
||||
// Set sets value to given key in session.
|
||||
Set(interface{}, interface{}) error |
||||
// Get gets value by given key in session.
|
||||
Get(interface{}) interface{} |
||||
// Delete deletes a key from session.
|
||||
Delete(interface{}) error |
||||
// ID returns current session ID.
|
||||
ID() string |
||||
// Release releases session resource and save data to provider.
|
||||
Release() error |
||||
// Flush deletes all session data.
|
||||
Flush() error |
||||
} |
||||
|
||||
// Store is the interface that contains all data for one session process with specific ID.
|
||||
type Store interface { |
||||
RawStore |
||||
// Read returns raw session store by session ID.
|
||||
Read(string) (RawStore, error) |
||||
// Destory deletes a session.
|
||||
Destory(*macaron.Context) error |
||||
// RegenerateId regenerates a session store from old session ID to new one.
|
||||
RegenerateId(*macaron.Context) (RawStore, error) |
||||
// Count counts and returns number of sessions.
|
||||
Count() int |
||||
// GC calls GC to clean expired sessions.
|
||||
GC() |
||||
} |
||||
|
||||
type store struct { |
||||
RawStore |
||||
*Manager |
||||
} |
||||
|
||||
var _ Store = &store{} |
||||
|
||||
// Options represents a struct for specifying configuration options for the session middleware.
|
||||
type Options struct { |
||||
// Name of provider. Default is "memory".
|
||||
Provider string |
||||
// Provider configuration, it's corresponding to provider.
|
||||
ProviderConfig string |
||||
// Cookie name to save session ID. Default is "MacaronSession".
|
||||
CookieName string |
||||
// Cookie path to store. Default is "/".
|
||||
CookiePath string |
||||
// GC interval time in seconds. Default is 3600.
|
||||
Gclifetime int64 |
||||
// Max life time in seconds. Default is whatever GC interval time is.
|
||||
Maxlifetime int64 |
||||
// Use HTTPS only. Default is false.
|
||||
Secure bool |
||||
// Cookie life time. Default is 0.
|
||||
CookieLifeTime int |
||||
// Cookie domain name. Default is empty.
|
||||
Domain string |
||||
// Session ID length. Default is 16.
|
||||
IDLength int |
||||
// Configuration section name. Default is "session".
|
||||
Section string |
||||
} |
||||
|
||||
func prepareOptions(options []Options) Options { |
||||
var opt Options |
||||
if len(options) > 0 { |
||||
opt = options[0] |
||||
} |
||||
if len(opt.Section) == 0 { |
||||
opt.Section = "session" |
||||
} |
||||
sec := macaron.Config().Section(opt.Section) |
||||
|
||||
if len(opt.Provider) == 0 { |
||||
opt.Provider = sec.Key("PROVIDER").MustString("memory") |
||||
} |
||||
if len(opt.ProviderConfig) == 0 { |
||||
opt.ProviderConfig = sec.Key("PROVIDER_CONFIG").MustString("data/sessions") |
||||
} |
||||
if len(opt.CookieName) == 0 { |
||||
opt.CookieName = sec.Key("COOKIE_NAME").MustString("MacaronSession") |
||||
} |
||||
if len(opt.CookiePath) == 0 { |
||||
opt.CookiePath = sec.Key("COOKIE_PATH").MustString("/") |
||||
} |
||||
if opt.Gclifetime == 0 { |
||||
opt.Gclifetime = sec.Key("GC_INTERVAL_TIME").MustInt64(3600) |
||||
} |
||||
if opt.Maxlifetime == 0 { |
||||
opt.Maxlifetime = sec.Key("MAX_LIFE_TIME").MustInt64(opt.Gclifetime) |
||||
} |
||||
if !opt.Secure { |
||||
opt.Secure = sec.Key("SECURE").MustBool() |
||||
} |
||||
if opt.CookieLifeTime == 0 { |
||||
opt.CookieLifeTime = sec.Key("COOKIE_LIFE_TIME").MustInt() |
||||
} |
||||
if len(opt.Domain) == 0 { |
||||
opt.Domain = sec.Key("DOMAIN").String() |
||||
} |
||||
if opt.IDLength == 0 { |
||||
opt.IDLength = sec.Key("ID_LENGTH").MustInt(16) |
||||
} |
||||
|
||||
return opt |
||||
} |
||||
|
||||
// Sessioner is a middleware that maps a session.SessionStore service into the Macaron handler chain.
|
||||
// An single variadic session.Options struct can be optionally provided to configure.
|
||||
func Sessioner(options ...Options) macaron.Handler { |
||||
opt := prepareOptions(options) |
||||
manager, err := NewManager(opt.Provider, opt) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
go manager.startGC() |
||||
|
||||
return func(ctx *macaron.Context) { |
||||
sess, err := manager.Start(ctx) |
||||
if err != nil { |
||||
panic("session(start): " + err.Error()) |
||||
} |
||||
|
||||
// Get flash.
|
||||
vals, _ := url.ParseQuery(ctx.GetCookie("macaron_flash")) |
||||
if len(vals) > 0 { |
||||
f := &Flash{Values: vals} |
||||
f.ErrorMsg = f.Get("error") |
||||
f.SuccessMsg = f.Get("success") |
||||
f.InfoMsg = f.Get("info") |
||||
f.WarningMsg = f.Get("warning") |
||||
ctx.Data["Flash"] = f |
||||
ctx.SetCookie("macaron_flash", "", -1, opt.CookiePath) |
||||
} |
||||
|
||||
f := &Flash{ctx, url.Values{}, "", "", "", ""} |
||||
ctx.Resp.Before(func(macaron.ResponseWriter) { |
||||
if flash := f.Encode(); len(flash) > 0 { |
||||
ctx.SetCookie("macaron_flash", flash, 0, opt.CookiePath) |
||||
} |
||||
}) |
||||
|
||||
ctx.Map(f) |
||||
s := store{ |
||||
RawStore: sess, |
||||
Manager: manager, |
||||
} |
||||
|
||||
ctx.MapTo(s, (*Store)(nil)) |
||||
|
||||
ctx.Next() |
||||
|
||||
if err = sess.Release(); err != nil { |
||||
panic("session(release): " + err.Error()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Provider is the interface that provides session manipulations.
|
||||
type Provider interface { |
||||
// Init initializes session provider.
|
||||
Init(gclifetime int64, config string) error |
||||
// Read returns raw session store by session ID.
|
||||
Read(sid string) (RawStore, error) |
||||
// Exist returns true if session with given ID exists.
|
||||
Exist(sid string) bool |
||||
// Destory deletes a session by session ID.
|
||||
Destory(sid string) error |
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
Regenerate(oldsid, sid string) (RawStore, error) |
||||
// Count counts and returns number of sessions.
|
||||
Count() int |
||||
// GC calls GC to clean expired sessions.
|
||||
GC() |
||||
} |
||||
|
||||
var providers = make(map[string]Provider) |
||||
|
||||
// Register registers a provider.
|
||||
func Register(name string, provider Provider) { |
||||
if provider == nil { |
||||
panic("session: cannot register provider with nil value") |
||||
} |
||||
if _, dup := providers[name]; dup { |
||||
panic(fmt.Errorf("session: cannot register provider '%s' twice", name)) |
||||
} |
||||
providers[name] = provider |
||||
} |
||||
|
||||
// _____
|
||||
// / \ _____ ____ _____ ____ ___________
|
||||
// / \ / \\__ \ / \\__ \ / ___\_/ __ \_ __ \
|
||||
// / Y \/ __ \| | \/ __ \_/ /_/ > ___/| | \/
|
||||
// \____|__ (____ /___| (____ /\___ / \___ >__|
|
||||
// \/ \/ \/ \//_____/ \/
|
||||
|
||||
// Manager represents a struct that contains session provider and its configuration.
|
||||
type Manager struct { |
||||
provider Provider |
||||
opt Options |
||||
} |
||||
|
||||
// NewManager creates and returns a new session manager by given provider name and configuration.
|
||||
// It panics when given provider isn't registered.
|
||||
func NewManager(name string, opt Options) (*Manager, error) { |
||||
p, ok := providers[name] |
||||
if !ok { |
||||
return nil, fmt.Errorf("session: unknown provider '%s'(forgotten import?)", name) |
||||
} |
||||
return &Manager{p, opt}, p.Init(opt.Maxlifetime, opt.ProviderConfig) |
||||
} |
||||
|
||||
// sessionId generates a new session ID with rand string, unix nano time, remote addr by hash function.
|
||||
func (m *Manager) sessionId() string { |
||||
return hex.EncodeToString(generateRandomKey(m.opt.IDLength / 2)) |
||||
} |
||||
|
||||
// Start starts a session by generating new one
|
||||
// or retrieve existence one by reading session ID from HTTP request if it's valid.
|
||||
func (m *Manager) Start(ctx *macaron.Context) (RawStore, error) { |
||||
sid := ctx.GetCookie(m.opt.CookieName) |
||||
if len(sid) > 0 && m.provider.Exist(sid) { |
||||
return m.provider.Read(sid) |
||||
} |
||||
|
||||
sid = m.sessionId() |
||||
sess, err := m.provider.Read(sid) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
cookie := &http.Cookie{ |
||||
Name: m.opt.CookieName, |
||||
Value: sid, |
||||
Path: m.opt.CookiePath, |
||||
HttpOnly: true, |
||||
Secure: m.opt.Secure, |
||||
Domain: m.opt.Domain, |
||||
} |
||||
if m.opt.CookieLifeTime >= 0 { |
||||
cookie.MaxAge = m.opt.CookieLifeTime |
||||
} |
||||
http.SetCookie(ctx.Resp, cookie) |
||||
ctx.Req.AddCookie(cookie) |
||||
return sess, nil |
||||
} |
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (m *Manager) Read(sid string) (RawStore, error) { |
||||
return m.provider.Read(sid) |
||||
} |
||||
|
||||
// Destory deletes a session by given ID.
|
||||
func (m *Manager) Destory(ctx *macaron.Context) error { |
||||
sid := ctx.GetCookie(m.opt.CookieName) |
||||
if len(sid) == 0 { |
||||
return nil |
||||
} |
||||
|
||||
if err := m.provider.Destory(sid); err != nil { |
||||
return err |
||||
} |
||||
cookie := &http.Cookie{ |
||||
Name: m.opt.CookieName, |
||||
Path: m.opt.CookiePath, |
||||
HttpOnly: true, |
||||
Expires: time.Now(), |
||||
MaxAge: -1, |
||||
} |
||||
http.SetCookie(ctx.Resp, cookie) |
||||
return nil |
||||
} |
||||
|
||||
// RegenerateId regenerates a session store from old session ID to new one.
|
||||
func (m *Manager) RegenerateId(ctx *macaron.Context) (sess RawStore, err error) { |
||||
sid := m.sessionId() |
||||
oldsid := ctx.GetCookie(m.opt.CookieName) |
||||
sess, err = m.provider.Regenerate(oldsid, sid) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
ck := &http.Cookie{ |
||||
Name: m.opt.CookieName, |
||||
Value: sid, |
||||
Path: m.opt.CookiePath, |
||||
HttpOnly: true, |
||||
Secure: m.opt.Secure, |
||||
Domain: m.opt.Domain, |
||||
} |
||||
if m.opt.CookieLifeTime >= 0 { |
||||
ck.MaxAge = m.opt.CookieLifeTime |
||||
} |
||||
http.SetCookie(ctx.Resp, ck) |
||||
ctx.Req.AddCookie(ck) |
||||
return sess, nil |
||||
} |
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (m *Manager) Count() int { |
||||
return m.provider.Count() |
||||
} |
||||
|
||||
// GC starts GC job in a certain period.
|
||||
func (m *Manager) GC() { |
||||
m.provider.GC() |
||||
} |
||||
|
||||
// startGC starts GC job in a certain period.
|
||||
func (m *Manager) startGC() { |
||||
m.GC() |
||||
time.AfterFunc(time.Duration(m.opt.Gclifetime)*time.Second, func() { m.startGC() }) |
||||
} |
||||
|
||||
// SetSecure indicates whether to set cookie with HTTPS or not.
|
||||
func (m *Manager) SetSecure(secure bool) { |
||||
m.opt.Secure = secure |
||||
} |
||||
|
||||
// ___________.____ _____ _________ ___ ___
|
||||
// \_ _____/| | / _ \ / _____// | \
|
||||
// | __) | | / /_\ \ \_____ \/ ~ \
|
||||
// | \ | |___/ | \/ \ Y /
|
||||
// \___ / |_______ \____|__ /_______ /\___|_ /
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
type Flash struct { |
||||
ctx *macaron.Context |
||||
url.Values |
||||
ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string |
||||
} |
||||
|
||||
func (f *Flash) set(name, msg string, current ...bool) { |
||||
isShow := false |
||||
if (len(current) == 0 && macaron.FlashNow) || |
||||
(len(current) > 0 && current[0]) { |
||||
isShow = true |
||||
} |
||||
|
||||
if isShow { |
||||
f.ctx.Data["Flash"] = f |
||||
} else { |
||||
f.Set(name, msg) |
||||
} |
||||
} |
||||
|
||||
func (f *Flash) Error(msg string, current ...bool) { |
||||
f.ErrorMsg = msg |
||||
f.set("error", msg, current...) |
||||
} |
||||
|
||||
func (f *Flash) Warning(msg string, current ...bool) { |
||||
f.WarningMsg = msg |
||||
f.set("warning", msg, current...) |
||||
} |
||||
|
||||
func (f *Flash) Info(msg string, current ...bool) { |
||||
f.InfoMsg = msg |
||||
f.set("info", msg, current...) |
||||
} |
||||
|
||||
func (f *Flash) Success(msg string, current ...bool) { |
||||
f.SuccessMsg = msg |
||||
f.set("success", msg, current...) |
||||
} |
@ -0,0 +1,60 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 session |
||||
|
||||
import ( |
||||
"bytes" |
||||
"crypto/rand" |
||||
"encoding/gob" |
||||
"io" |
||||
|
||||
"github.com/Unknwon/com" |
||||
) |
||||
|
||||
func init() { |
||||
gob.Register([]interface{}{}) |
||||
gob.Register(map[int]interface{}{}) |
||||
gob.Register(map[string]interface{}{}) |
||||
gob.Register(map[interface{}]interface{}{}) |
||||
gob.Register(map[string]string{}) |
||||
gob.Register(map[int]string{}) |
||||
gob.Register(map[int]int{}) |
||||
gob.Register(map[int]int64{}) |
||||
} |
||||
|
||||
func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) { |
||||
for _, v := range obj { |
||||
gob.Register(v) |
||||
} |
||||
buf := bytes.NewBuffer(nil) |
||||
err := gob.NewEncoder(buf).Encode(obj) |
||||
return buf.Bytes(), err |
||||
} |
||||
|
||||
func DecodeGob(encoded []byte) (out map[interface{}]interface{}, err error) { |
||||
buf := bytes.NewBuffer(encoded) |
||||
err = gob.NewDecoder(buf).Decode(&out) |
||||
return out, err |
||||
} |
||||
|
||||
// generateRandomKey creates a random key with the given strength.
|
||||
func generateRandomKey(strength int) []byte { |
||||
k := make([]byte, strength) |
||||
if n, err := io.ReadFull(rand.Reader, k); n != strength || err != nil { |
||||
return com.RandomCreateBytes(strength) |
||||
} |
||||
return k |
||||
} |
@ -0,0 +1,191 @@ |
||||
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: |
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of |
||||
this License; and |
||||
You must cause any modified files to carry prominent notices stating that You |
||||
changed the files; and |
||||
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 |
||||
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. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work |
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate |
||||
notice, with the fields enclosed by brackets "[]" replaced with your own |
||||
identifying information. (Don't include the brackets!) The text should be |
||||
enclosed in the appropriate comment syntax for the file format. We also |
||||
recommend that a file or class name and description of purpose be included on |
||||
the same "printed page" as the copyright notice for easier identification within |
||||
third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
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. |
@ -0,0 +1,110 @@ |
||||
toolbox |
||||
======= |
||||
|
||||
Middleware toolbox provides health chcek, pprof, profile and statistic services for [Macaron](https://github.com/go-macaron/macaron). |
||||
|
||||
[API Reference](https://gowalker.org/github.com/go-macaron/toolbox) |
||||
|
||||
### Installation |
||||
|
||||
go get github.com/go-macaron/toolbox |
||||
|
||||
## Usage |
||||
|
||||
```go |
||||
// main.go |
||||
import ( |
||||
"gopkg.in/macaron.v1" |
||||
"github.com/go-macaron/toolbox" |
||||
) |
||||
|
||||
func main() { |
||||
m := macaron.Classic() |
||||
m.Use(toolbox.Toolboxer(m)) |
||||
m.Run() |
||||
} |
||||
``` |
||||
|
||||
Open your browser and visit `http://localhost:4000/debug` to see the effects. |
||||
|
||||
## Options |
||||
|
||||
`toolbox.Toolboxer` comes with a variety of configuration options: |
||||
|
||||
```go |
||||
type dummyChecker struct { |
||||
} |
||||
|
||||
func (dc *dummyChecker) Desc() string { |
||||
return "Dummy checker" |
||||
} |
||||
|
||||
func (dc *dummyChecker) Check() error { |
||||
return nil |
||||
} |
||||
|
||||
// ... |
||||
m.Use(toolbox.Toolboxer(m, toolbox.Options{ |
||||
URLPrefix: "/debug", // URL prefix for toolbox dashboard. |
||||
HealthCheckURL: "/healthcheck", // URL for health check request. |
||||
HealthCheckers: []HealthChecker{ |
||||
new(dummyChecker), |
||||
}, // Health checkers. |
||||
HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{ |
||||
&toolbox.HealthCheckFuncDesc{ |
||||
Desc: "Database connection", |
||||
Func: func() error { return "OK" }, |
||||
}, |
||||
}, // Health check functions. |
||||
PprofURLPrefix: "/debug/pprof/", // URL prefix of pprof. |
||||
ProfileURLPrefix: "/debug/profile/", // URL prefix of profile. |
||||
ProfilePath: "profile", // Path store profile files. |
||||
})) |
||||
// ... |
||||
``` |
||||
|
||||
## Route Statistic |
||||
|
||||
Toolbox also comes with a route call statistic functionality: |
||||
|
||||
```go |
||||
import ( |
||||
"os" |
||||
"time" |
||||
//... |
||||
"github.com/go-macaron/toolbox" |
||||
) |
||||
|
||||
func main() { |
||||
//... |
||||
m.Get("/", func(t toolbox.Toolbox) { |
||||
start := time.Now() |
||||
|
||||
// Other operations. |
||||
|
||||
t.AddStatistics("GET", "/", time.Since(start)) |
||||
}) |
||||
|
||||
m.Get("/dump", func(t toolbox.Toolbox) { |
||||
t.GetMap(os.Stdout) |
||||
}) |
||||
} |
||||
``` |
||||
|
||||
Output take from test: |
||||
|
||||
``` |
||||
+---------------------------------------------------+------------+------------------+------------------+------------------+------------------+------------------+ |
||||
| Request URL | Method | Times | Total Used(s) | Max Used(μs) | Min Used(μs) | Avg Used(μs) | |
||||
+---------------------------------------------------+------------+------------------+------------------+------------------+------------------+------------------+ |
||||
| /api/user | POST | 2 | 0.000122 | 120.000000 | 2.000000 | 61.000000 | |
||||
| /api/user | GET | 1 | 0.000013 | 13.000000 | 13.000000 | 13.000000 | |
||||
| /api/user | DELETE | 1 | 0.000001 | 1.400000 | 1.400000 | 1.400000 | |
||||
| /api/admin | POST | 1 | 0.000014 | 14.000000 | 14.000000 | 14.000000 | |
||||
| /api/user/unknwon | POST | 1 | 0.000012 | 12.000000 | 12.000000 | 12.000000 | |
||||
+---------------------------------------------------+------------+------------------+------------------+------------------+------------------+------------------+ |
||||
``` |
||||
|
||||
## License |
||||
|
||||
This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text. |
@ -0,0 +1,83 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 toolbox |
||||
|
||||
import ( |
||||
"bytes" |
||||
) |
||||
|
||||
// HealthChecker represents a health check instance.
|
||||
type HealthChecker interface { |
||||
Desc() string |
||||
Check() error |
||||
} |
||||
|
||||
// HealthCheckFunc represents a callable function for health check.
|
||||
type HealthCheckFunc func() error |
||||
|
||||
// HealthCheckFunc represents a callable function for health check with description.
|
||||
type HealthCheckFuncDesc struct { |
||||
Desc string |
||||
Func HealthCheckFunc |
||||
} |
||||
|
||||
type healthCheck struct { |
||||
desc string |
||||
HealthChecker |
||||
check HealthCheckFunc // Not nil if add job as a function.
|
||||
} |
||||
|
||||
// AddHealthCheck adds new health check job.
|
||||
func (t *toolbox) AddHealthCheck(hc HealthChecker) { |
||||
t.healthCheckJobs = append(t.healthCheckJobs, &healthCheck{ |
||||
HealthChecker: hc, |
||||
}) |
||||
} |
||||
|
||||
// AddHealthCheckFunc adds a function as a new health check job.
|
||||
func (t *toolbox) AddHealthCheckFunc(desc string, fn HealthCheckFunc) { |
||||
t.healthCheckJobs = append(t.healthCheckJobs, &healthCheck{ |
||||
desc: desc, |
||||
check: fn, |
||||
}) |
||||
} |
||||
|
||||
func (t *toolbox) handleHealthCheck() string { |
||||
if len(t.healthCheckJobs) == 0 { |
||||
return "no health check jobs" |
||||
} |
||||
|
||||
var buf bytes.Buffer |
||||
var err error |
||||
for _, job := range t.healthCheckJobs { |
||||
buf.WriteString("* ") |
||||
if job.check != nil { |
||||
buf.WriteString(job.desc) |
||||
err = job.check() |
||||
} else { |
||||
buf.WriteString(job.Desc()) |
||||
err = job.Check() |
||||
} |
||||
buf.WriteString(": ") |
||||
if err == nil { |
||||
buf.WriteString("OK") |
||||
} else { |
||||
buf.WriteString(err.Error()) |
||||
} |
||||
buf.WriteString("\n") |
||||
} |
||||
return buf.String() |
||||
} |
@ -0,0 +1,163 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 toolbox |
||||
|
||||
import ( |
||||
"bytes" |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"os" |
||||
"path" |
||||
"runtime" |
||||
"runtime/debug" |
||||
"runtime/pprof" |
||||
"time" |
||||
|
||||
"github.com/Unknwon/com" |
||||
"gopkg.in/macaron.v1" |
||||
) |
||||
|
||||
var ( |
||||
profilePath string |
||||
pid int |
||||
startTime = time.Now() |
||||
inCPUProfile bool |
||||
) |
||||
|
||||
// StartCPUProfile starts CPU profile monitor.
|
||||
func StartCPUProfile() error { |
||||
if inCPUProfile { |
||||
return errors.New("CPU profile has alreday been started!") |
||||
} |
||||
inCPUProfile = true |
||||
|
||||
os.MkdirAll(profilePath, os.ModePerm) |
||||
f, err := os.Create(path.Join(profilePath, "cpu-"+com.ToStr(pid)+".pprof")) |
||||
if err != nil { |
||||
panic("fail to record CPU profile: " + err.Error()) |
||||
} |
||||
pprof.StartCPUProfile(f) |
||||
return nil |
||||
} |
||||
|
||||
// StopCPUProfile stops CPU profile monitor.
|
||||
func StopCPUProfile() error { |
||||
if !inCPUProfile { |
||||
return errors.New("CPU profile hasn't been started!") |
||||
} |
||||
pprof.StopCPUProfile() |
||||
inCPUProfile = false |
||||
return nil |
||||
} |
||||
|
||||
func init() { |
||||
pid = os.Getpid() |
||||
} |
||||
|
||||
// DumpMemProf dumps memory profile in pprof.
|
||||
func DumpMemProf(w io.Writer) { |
||||
pprof.WriteHeapProfile(w) |
||||
} |
||||
|
||||
func dumpMemProf() { |
||||
os.MkdirAll(profilePath, os.ModePerm) |
||||
f, err := os.Create(path.Join(profilePath, "mem-"+com.ToStr(pid)+".memprof")) |
||||
if err != nil { |
||||
panic("fail to record memory profile: " + err.Error()) |
||||
} |
||||
runtime.GC() |
||||
DumpMemProf(f) |
||||
f.Close() |
||||
} |
||||
|
||||
func avg(items []time.Duration) time.Duration { |
||||
var sum time.Duration |
||||
for _, item := range items { |
||||
sum += item |
||||
} |
||||
return time.Duration(int64(sum) / int64(len(items))) |
||||
} |
||||
|
||||
func dumpGC(memStats *runtime.MemStats, gcstats *debug.GCStats, w io.Writer) { |
||||
|
||||
if gcstats.NumGC > 0 { |
||||
lastPause := gcstats.Pause[0] |
||||
elapsed := time.Now().Sub(startTime) |
||||
overhead := float64(gcstats.PauseTotal) / float64(elapsed) * 100 |
||||
allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds() |
||||
|
||||
fmt.Fprintf(w, "NumGC:%d Pause:%s Pause(Avg):%s Overhead:%3.2f%% Alloc:%s Sys:%s Alloc(Rate):%s/s Histogram:%s %s %s \n", |
||||
gcstats.NumGC, |
||||
com.ToStr(lastPause), |
||||
com.ToStr(avg(gcstats.Pause)), |
||||
overhead, |
||||
com.HumaneFileSize(memStats.Alloc), |
||||
com.HumaneFileSize(memStats.Sys), |
||||
com.HumaneFileSize(uint64(allocatedRate)), |
||||
com.ToStr(gcstats.PauseQuantiles[94]), |
||||
com.ToStr(gcstats.PauseQuantiles[98]), |
||||
com.ToStr(gcstats.PauseQuantiles[99])) |
||||
} else { |
||||
// while GC has disabled
|
||||
elapsed := time.Now().Sub(startTime) |
||||
allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds() |
||||
|
||||
fmt.Fprintf(w, "Alloc:%s Sys:%s Alloc(Rate):%s/s\n", |
||||
com.HumaneFileSize(memStats.Alloc), |
||||
com.HumaneFileSize(memStats.Sys), |
||||
com.HumaneFileSize(uint64(allocatedRate))) |
||||
} |
||||
} |
||||
|
||||
// DumpGCSummary dumps GC information to io.Writer
|
||||
func DumpGCSummary(w io.Writer) { |
||||
memStats := &runtime.MemStats{} |
||||
runtime.ReadMemStats(memStats) |
||||
gcstats := &debug.GCStats{PauseQuantiles: make([]time.Duration, 100)} |
||||
debug.ReadGCStats(gcstats) |
||||
|
||||
dumpGC(memStats, gcstats, w) |
||||
} |
||||
|
||||
func handleProfile(ctx *macaron.Context) string { |
||||
switch ctx.Query("op") { |
||||
case "startcpu": |
||||
if err := StartCPUProfile(); err != nil { |
||||
return err.Error() |
||||
} |
||||
case "stopcpu": |
||||
if err := StopCPUProfile(); err != nil { |
||||
return err.Error() |
||||
} |
||||
case "mem": |
||||
dumpMemProf() |
||||
case "gc": |
||||
var buf bytes.Buffer |
||||
DumpGCSummary(&buf) |
||||
return string(buf.Bytes()) |
||||
default: |
||||
return fmt.Sprintf(`<p>Available operations:</p> |
||||
<ol> |
||||
<li><a href="%[1]s?op=startcpu">Start CPU profile</a></li> |
||||
<li><a href="%[1]s?op=stopcpu">Stop CPU profile</a></li> |
||||
<li><a href="%[1]s?op=mem">Dump memory profile</a></li> |
||||
<li><a href="%[1]s?op=gc">Dump GC summary</a></li> |
||||
</ol>`, opt.ProfileURLPrefix) |
||||
} |
||||
ctx.Redirect(opt.ProfileURLPrefix) |
||||
return "" |
||||
} |
@ -0,0 +1,138 @@ |
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 toolbox |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"strings" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
// Statistics struct
|
||||
type Statistics struct { |
||||
RequestUrl string |
||||
RequestNum int64 |
||||
MinTime time.Duration |
||||
MaxTime time.Duration |
||||
TotalTime time.Duration |
||||
} |
||||
|
||||
// UrlMap contains several statistics struct to log different data
|
||||
type UrlMap struct { |
||||
lock sync.RWMutex |
||||
LengthLimit int // limit the urlmap's length if it's equal to 0 there's no limit
|
||||
urlmap map[string]map[string]*Statistics |
||||
} |
||||
|
||||
// add statistics task.
|
||||
// it needs request method, request url and statistics time duration
|
||||
func (m *UrlMap) AddStatistics(requestMethod, requestUrl string, requesttime time.Duration) { |
||||
m.lock.Lock() |
||||
defer m.lock.Unlock() |
||||
|
||||
if method, ok := m.urlmap[requestUrl]; ok { |
||||
if s, ok := method[requestMethod]; ok { |
||||
s.RequestNum += 1 |
||||
if s.MaxTime < requesttime { |
||||
s.MaxTime = requesttime |
||||
} |
||||
if s.MinTime > requesttime { |
||||
s.MinTime = requesttime |
||||
} |
||||
s.TotalTime += requesttime |
||||
} else { |
||||
nb := &Statistics{ |
||||
RequestUrl: requestUrl, |
||||
RequestNum: 1, |
||||
MinTime: requesttime, |
||||
MaxTime: requesttime, |
||||
TotalTime: requesttime, |
||||
} |
||||
m.urlmap[requestUrl][requestMethod] = nb |
||||
} |
||||
|
||||
} else { |
||||
if m.LengthLimit > 0 && m.LengthLimit <= len(m.urlmap) { |
||||
return |
||||
} |
||||
methodmap := make(map[string]*Statistics) |
||||
nb := &Statistics{ |
||||
RequestUrl: requestUrl, |
||||
RequestNum: 1, |
||||
MinTime: requesttime, |
||||
MaxTime: requesttime, |
||||
TotalTime: requesttime, |
||||
} |
||||
methodmap[requestMethod] = nb |
||||
m.urlmap[requestUrl] = methodmap |
||||
} |
||||
} |
||||
|
||||
// put url statistics result in io.Writer
|
||||
func (m *UrlMap) GetMap(w io.Writer) { |
||||
m.lock.RLock() |
||||
defer m.lock.RUnlock() |
||||
|
||||
sep := fmt.Sprintf("+%s+%s+%s+%s+%s+%s+%s+\n", strings.Repeat("-", 51), strings.Repeat("-", 12), |
||||
strings.Repeat("-", 18), strings.Repeat("-", 18), strings.Repeat("-", 18), strings.Repeat("-", 18), strings.Repeat("-", 18)) |
||||
fmt.Fprintf(w, sep) |
||||
fmt.Fprintf(w, "| % -50s| % -10s | % -16s | % -16s | % -16s | % -16s | % -16s |\n", "Request URL", "Method", "Times", "Total Used(s)", "Max Used(μs)", "Min Used(μs)", "Avg Used(μs)") |
||||
fmt.Fprintf(w, sep) |
||||
|
||||
for k, v := range m.urlmap { |
||||
for kk, vv := range v { |
||||
fmt.Fprintf(w, "| % -50s| % -10s | % 16d | % 16f | % 16.6f | % 16.6f | % 16.6f |\n", k, |
||||
kk, vv.RequestNum, vv.TotalTime.Seconds(), float64(vv.MaxTime.Nanoseconds())/1000, |
||||
float64(vv.MinTime.Nanoseconds())/1000, float64(time.Duration(int64(vv.TotalTime)/vv.RequestNum).Nanoseconds())/1000, |
||||
) |
||||
} |
||||
} |
||||
fmt.Fprintf(w, sep) |
||||
} |
||||
|
||||
type URLMapInfo struct { |
||||
URL string `json:"url"` |
||||
Method string `json:"method"` |
||||
Times int64 `json:"times"` |
||||
TotalUsed float64 `json:"total_used"` |
||||
MaxUsed float64 `json:"max_used"` |
||||
MinUsed float64 `json:"min_used"` |
||||
AvgUsed float64 `json:"avg_used"` |
||||
} |
||||
|
||||
func (m *UrlMap) JSON(w io.Writer) { |
||||
infos := make([]*URLMapInfo, 0, len(m.urlmap)) |
||||
for k, v := range m.urlmap { |
||||
for kk, vv := range v { |
||||
infos = append(infos, &URLMapInfo{ |
||||
URL: k, |
||||
Method: kk, |
||||
Times: vv.RequestNum, |
||||
TotalUsed: vv.TotalTime.Seconds(), |
||||
MaxUsed: float64(vv.MaxTime.Nanoseconds()) / 1000, |
||||
MinUsed: float64(vv.MinTime.Nanoseconds()) / 1000, |
||||
AvgUsed: float64(time.Duration(int64(vv.TotalTime)/vv.RequestNum).Nanoseconds()) / 1000, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
if err := json.NewEncoder(w).Encode(infos); err != nil { |
||||
panic("URLMap.JSON: " + err.Error()) |
||||
} |
||||
} |
@ -0,0 +1,154 @@ |
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 toolbox is a middleware that provides health check, pprof, profile and statistic services for Macaron.
|
||||
package toolbox |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"net/http/pprof" |
||||
"path" |
||||
"time" |
||||
|
||||
"gopkg.in/macaron.v1" |
||||
) |
||||
|
||||
const _VERSION = "0.1.2" |
||||
|
||||
func Version() string { |
||||
return _VERSION |
||||
} |
||||
|
||||
// Toolbox represents a tool box service for Macaron instance.
|
||||
type Toolbox interface { |
||||
AddHealthCheck(HealthChecker) |
||||
AddHealthCheckFunc(string, HealthCheckFunc) |
||||
AddStatistics(string, string, time.Duration) |
||||
GetMap(io.Writer) |
||||
JSON(io.Writer) |
||||
} |
||||
|
||||
type toolbox struct { |
||||
*UrlMap |
||||
healthCheckJobs []*healthCheck |
||||
} |
||||
|
||||
// Options represents a struct for specifying configuration options for the Toolbox middleware.
|
||||
type Options struct { |
||||
// URL prefix for toolbox dashboard. Default is "/debug".
|
||||
URLPrefix string |
||||
// URL for health check request. Default is "/healthcheck".
|
||||
HealthCheckURL string |
||||
// Health checkers.
|
||||
HealthCheckers []HealthChecker |
||||
// Health check functions.
|
||||
HealthCheckFuncs []*HealthCheckFuncDesc |
||||
// URL for URL map json. Default is "/urlmap.json".
|
||||
URLMapPrefix string |
||||
// URL prefix of pprof. Default is "/debug/pprof/".
|
||||
PprofURLPrefix string |
||||
// URL prefix of profile. Default is "/debug/profile/".
|
||||
ProfileURLPrefix string |
||||
// Path store profile files. Default is "profile".
|
||||
ProfilePath string |
||||
} |
||||
|
||||
var opt Options |
||||
|
||||
func prepareOptions(options []Options) { |
||||
if len(options) > 0 { |
||||
opt = options[0] |
||||
} |
||||
|
||||
// Defaults.
|
||||
if len(opt.URLPrefix) == 0 { |
||||
opt.URLPrefix = "/debug" |
||||
} |
||||
if len(opt.HealthCheckURL) == 0 { |
||||
opt.HealthCheckURL = "/healthcheck" |
||||
} |
||||
if len(opt.URLMapPrefix) == 0 { |
||||
opt.URLMapPrefix = "/urlmap.json" |
||||
} |
||||
if len(opt.PprofURLPrefix) == 0 { |
||||
opt.PprofURLPrefix = "/debug/pprof/" |
||||
} else if opt.PprofURLPrefix[len(opt.PprofURLPrefix)-1] != '/' { |
||||
opt.PprofURLPrefix += "/" |
||||
} |
||||
if len(opt.ProfileURLPrefix) == 0 { |
||||
opt.ProfileURLPrefix = "/debug/profile/" |
||||
} else if opt.ProfileURLPrefix[len(opt.ProfileURLPrefix)-1] != '/' { |
||||
opt.ProfileURLPrefix += "/" |
||||
} |
||||
if len(opt.ProfilePath) == 0 { |
||||
opt.ProfilePath = path.Join(macaron.Root, "profile") |
||||
} |
||||
} |
||||
|
||||
func dashboard(ctx *macaron.Context) string { |
||||
return fmt.Sprintf(`<p>Toolbox Index:</p> |
||||
<ol> |
||||
<li><a href="%s">Pprof Information</a></li> |
||||
<li><a href="%s">Profile Operations</a></li> |
||||
</ol>`, opt.PprofURLPrefix, opt.ProfileURLPrefix) |
||||
} |
||||
|
||||
var _ Toolbox = &toolbox{} |
||||
|
||||
// Toolboxer is a middleware provides health check, pprof, profile and statistic services for your application.
|
||||
func Toolboxer(m *macaron.Macaron, options ...Options) macaron.Handler { |
||||
prepareOptions(options) |
||||
t := &toolbox{ |
||||
healthCheckJobs: make([]*healthCheck, 0, len(opt.HealthCheckers)+len(opt.HealthCheckFuncs)), |
||||
} |
||||
|
||||
// Dashboard.
|
||||
m.Get(opt.URLPrefix, dashboard) |
||||
|
||||
// Health check.
|
||||
for _, hc := range opt.HealthCheckers { |
||||
t.AddHealthCheck(hc) |
||||
} |
||||
for _, fd := range opt.HealthCheckFuncs { |
||||
t.AddHealthCheckFunc(fd.Desc, fd.Func) |
||||
} |
||||
m.Get(opt.HealthCheckURL, t.handleHealthCheck) |
||||
|
||||
// URL map.
|
||||
m.Get(opt.URLMapPrefix, func(rw http.ResponseWriter) { |
||||
t.JSON(rw) |
||||
}) |
||||
|
||||
// Pprof.
|
||||
m.Any(path.Join(opt.PprofURLPrefix, "cmdline"), pprof.Cmdline) |
||||
m.Any(path.Join(opt.PprofURLPrefix, "profile"), pprof.Profile) |
||||
m.Any(path.Join(opt.PprofURLPrefix, "symbol"), pprof.Symbol) |
||||
m.Any(opt.PprofURLPrefix, pprof.Index) |
||||
m.Any(path.Join(opt.PprofURLPrefix, "*"), pprof.Index) |
||||
|
||||
// Profile.
|
||||
profilePath = opt.ProfilePath |
||||
m.Get(opt.ProfileURLPrefix, handleProfile) |
||||
|
||||
// Routes statistic.
|
||||
t.UrlMap = &UrlMap{ |
||||
urlmap: make(map[string]map[string]*Statistics), |
||||
} |
||||
|
||||
return func(ctx *macaron.Context) { |
||||
ctx.MapTo(t, (*Toolbox)(nil)) |
||||
} |
||||
} |
@ -0,0 +1,55 @@ |
||||
# This is the official list of Go-MySQL-Driver authors for copyright purposes. |
||||
|
||||
# If you are submitting a patch, please add your name or the name of the |
||||
# organization which holds the copyright to this list in alphabetical order. |
||||
|
||||
# Names should be added to this file as |
||||
# Name <email address> |
||||
# The email address is not required for organizations. |
||||
# Please keep the list sorted. |
||||
|
||||
|
||||
# Individual Persons |
||||
|
||||
Aaron Hopkins <go-sql-driver at die.net> |
||||
Arne Hormann <arnehormann at gmail.com> |
||||
Carlos Nieto <jose.carlos at menteslibres.net> |
||||
Chris Moos <chris at tech9computers.com> |
||||
Daniel Nichter <nil at codenode.com> |
||||
Daniël van Eeden <git at myname.nl> |
||||
DisposaBoy <disposaboy at dby.me> |
||||
Frederick Mayle <frederickmayle at gmail.com> |
||||
Gustavo Kristic <gkristic at gmail.com> |
||||
Hanno Braun <mail at hannobraun.com> |
||||
Henri Yandell <flamefew at gmail.com> |
||||
Hirotaka Yamamoto <ymmt2005 at gmail.com> |
||||
INADA Naoki <songofacandy at gmail.com> |
||||
James Harr <james.harr at gmail.com> |
||||
Jian Zhen <zhenjl at gmail.com> |
||||
Joshua Prunier <joshua.prunier at gmail.com> |
||||
Julien Lefevre <julien.lefevr at gmail.com> |
||||
Julien Schmidt <go-sql-driver at julienschmidt.com> |
||||
Kamil Dziedzic <kamil at klecza.pl> |
||||
Kevin Malachowski <kevin at chowski.com> |
||||
Lennart Rudolph <lrudolph at hmc.edu> |
||||
Leonardo YongUk Kim <dalinaum at gmail.com> |
||||
Luca Looz <luca.looz92 at gmail.com> |
||||
Lucas Liu <extrafliu at gmail.com> |
||||
Luke Scott <luke at webconnex.com> |
||||
Michael Woolnough <michael.woolnough at gmail.com> |
||||
Nicola Peduzzi <thenikso at gmail.com> |
||||
Paul Bonser <misterpib at gmail.com> |
||||
Runrioter Wung <runrioter at gmail.com> |
||||
Soroush Pour <me at soroushjp.com> |
||||
Stan Putrya <root.vagner at gmail.com> |
||||
Stanley Gunawan <gunawan.stanley at gmail.com> |
||||
Xiangyu Hu <xiangyu.hu at outlook.com> |
||||
Xiaobing Jiang <s7v7nislands at gmail.com> |
||||
Xiuming Chen <cc at cxm.cc> |
||||
Zhenye Xie <xiezhenye at gmail.com> |
||||
|
||||
# Organizations |
||||
|
||||
Barracuda Networks, Inc. |
||||
Google Inc. |
||||
Stripe Inc. |
@ -0,0 +1,114 @@ |
||||
## HEAD |
||||
|
||||
Changes: |
||||
|
||||
- Go 1.1 is no longer supported |
||||
- Use decimals fields in MySQL to format time types (#249) |
||||
- Buffer optimizations (#269) |
||||
- TLS ServerName defaults to the host (#283) |
||||
- Refactoring (#400, #410, #437) |
||||
- Adjusted documentation for second generation CloudSQL (#485) |
||||
|
||||
New Features: |
||||
|
||||
- Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249) |
||||
- Support for returning table alias on Columns() (#289, #359, #382) |
||||
- Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318, #490) |
||||
- Support for uint64 parameters with high bit set (#332, #345) |
||||
- Cleartext authentication plugin support (#327) |
||||
- Exported ParseDSN function and the Config struct (#403, #419, #429) |
||||
- Read / Write timeouts (#401) |
||||
- Support for JSON field type (#414) |
||||
- Support for multi-statements and multi-results (#411, #431) |
||||
- DSN parameter to set the driver-side max_allowed_packet value manually (#489) |
||||
|
||||
Bugfixes: |
||||
|
||||
- Fixed handling of queries without columns and rows (#255) |
||||
- Fixed a panic when SetKeepAlive() failed (#298) |
||||
- Handle ERR packets while reading rows (#321) |
||||
- Fixed reading NULL length-encoded integers in MySQL 5.6+ (#349) |
||||
- Fixed absolute paths support in LOAD LOCAL DATA INFILE (#356) |
||||
- Actually zero out bytes in handshake response (#378) |
||||
- Fixed race condition in registering LOAD DATA INFILE handler (#383) |
||||
- Fixed tests with MySQL 5.7.9+ (#380) |
||||
- QueryUnescape TLS config names (#397) |
||||
- Fixed "broken pipe" error by writing to closed socket (#390) |
||||
- Fixed LOAD LOCAL DATA INFILE buffering (#424) |
||||
- Fixed parsing of floats into float64 when placeholders are used (#434) |
||||
- Fixed DSN tests with Go 1.7+ (#459) |
||||
- Handle ERR packets while waiting for EOF (#473) |
||||
|
||||
|
||||
## Version 1.2 (2014-06-03) |
||||
|
||||
Changes: |
||||
|
||||
- We switched back to a "rolling release". `go get` installs the current master branch again |
||||
- Version v1 of the driver will not be maintained anymore. Go 1.0 is no longer supported by this driver |
||||
- Exported errors to allow easy checking from application code |
||||
- Enabled TCP Keepalives on TCP connections |
||||
- Optimized INFILE handling (better buffer size calculation, lazy init, ...) |
||||
- The DSN parser also checks for a missing separating slash |
||||
- Faster binary date / datetime to string formatting |
||||
- Also exported the MySQLWarning type |
||||
- mysqlConn.Close returns the first error encountered instead of ignoring all errors |
||||
- writePacket() automatically writes the packet size to the header |
||||
- readPacket() uses an iterative approach instead of the recursive approach to merge splitted packets |
||||
|
||||
New Features: |
||||
|
||||
- `RegisterDial` allows the usage of a custom dial function to establish the network connection |
||||
- Setting the connection collation is possible with the `collation` DSN parameter. This parameter should be preferred over the `charset` parameter |
||||
- Logging of critical errors is configurable with `SetLogger` |
||||
- Google CloudSQL support |
||||
|
||||
Bugfixes: |
||||
|
||||
- Allow more than 32 parameters in prepared statements |
||||
- Various old_password fixes |
||||
- Fixed TestConcurrent test to pass Go's race detection |
||||
- Fixed appendLengthEncodedInteger for large numbers |
||||
- Renamed readLengthEnodedString to readLengthEncodedString and skipLengthEnodedString to skipLengthEncodedString (fixed typo) |
||||
|
||||
|
||||
## Version 1.1 (2013-11-02) |
||||
|
||||
Changes: |
||||
|
||||
- Go-MySQL-Driver now requires Go 1.1 |
||||
- Connections now use the collation `utf8_general_ci` by default. Adding `&charset=UTF8` to the DSN should not be necessary anymore |
||||
- Made closing rows and connections error tolerant. This allows for example deferring rows.Close() without checking for errors |
||||
- `[]byte(nil)` is now treated as a NULL value. Before, it was treated like an empty string / `[]byte("")` |
||||
- DSN parameter values must now be url.QueryEscape'ed. This allows text values to contain special characters, such as '&'. |
||||
- Use the IO buffer also for writing. This results in zero allocations (by the driver) for most queries |
||||
- Optimized the buffer for reading |
||||
- stmt.Query now caches column metadata |
||||
- New Logo |
||||
- Changed the copyright header to include all contributors |
||||
- Improved the LOAD INFILE documentation |
||||
- The driver struct is now exported to make the driver directly accessible |
||||
- Refactored the driver tests |
||||
- Added more benchmarks and moved all to a separate file |
||||
- Other small refactoring |
||||
|
||||
New Features: |
||||
|
||||
- Added *old_passwords* support: Required in some cases, but must be enabled by adding `allowOldPasswords=true` to the DSN since it is insecure |
||||
- Added a `clientFoundRows` parameter: Return the number of matching rows instead of the number of rows changed on UPDATEs |
||||
- Added TLS/SSL support: Use a TLS/SSL encrypted connection to the server. Custom TLS configs can be registered and used |
||||
|
||||
Bugfixes: |
||||
|
||||
- Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification |
||||
- Convert to DB timezone when inserting `time.Time` |
||||
- Splitted packets (more than 16MB) are now merged correctly |
||||
- Fixed false positive `io.EOF` errors when the data was fully read |
||||
- Avoid panics on reuse of closed connections |
||||
- Fixed empty string producing false nil values |
||||
- Fixed sign byte for positive TIME fields |
||||
|
||||
|
||||
## Version 1.0 (2013-05-14) |
||||
|
||||
Initial Release |
@ -0,0 +1,23 @@ |
||||
# Contributing Guidelines |
||||
|
||||
## Reporting Issues |
||||
|
||||
Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed). |
||||
|
||||
## Contributing Code |
||||
|
||||
By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file. |
||||
Don't forget to add yourself to the AUTHORS file. |
||||
|
||||
### Code Review |
||||
|
||||
Everyone is invited to review and comment on pull requests. |
||||
If it looks fine to you, comment with "LGTM" (Looks good to me). |
||||
|
||||
If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes. |
||||
|
||||
Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM". |
||||
|
||||
## Development Ideas |
||||
|
||||
If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page. |
@ -0,0 +1,21 @@ |
||||
### Issue description |
||||
Tell us what should happen and what happens instead |
||||
|
||||
### Example code |
||||
```go |
||||
If possible, please enter some example code here to reproduce the issue. |
||||
``` |
||||
|
||||
### Error log |
||||
``` |
||||
If you have an error log, please paste it here. |
||||
``` |
||||
|
||||
### Configuration |
||||
*Driver version (or git SHA):* |
||||
|
||||
*Go version:* run `go version` in your console |
||||
|
||||
*Server version:* E.g. MySQL 5.6, MariaDB 10.0.20 |
||||
|
||||
*Server OS:* E.g. Debian 8.1 (Jessie), Windows 10 |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue