1
0
mirror of https://github.com/kataras/iris.git synced 2026-03-11 10:55:58 +00:00

Publish the new version ✈️ | Look description please!

# FAQ

### Looking for free support?

	http://support.iris-go.com
    https://kataras.rocket.chat/channel/iris

### Looking for previous versions?

    https://github.com/kataras/iris#version

### Should I upgrade my Iris?

Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready.
> Iris uses the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature, so you get truly reproducible builds, as this method guards against upstream renames and deletes.

**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
For further installation support, please click [here](http://support.iris-go.com/d/16-how-to-install-iris-web-framework).

### About our new home page
    http://iris-go.com

Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.com has been upgraded and it's really awesome!

[Santosh](https://github.com/santoshanand) is a freelancer, he has a great knowledge of nodejs and express js, Android, iOS, React Native, Vue.js etc, if you need a developer to find or create a solution for your problem or task, please contact with him.

The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please!

Read more at https://github.com/kataras/iris/blob/master/HISTORY.md


Former-commit-id: eec2d71bbe011d6b48d2526eb25919e36e5ad94e
This commit is contained in:
kataras
2017-06-03 23:22:52 +03:00
parent 03bcadadec
commit 5e4b63acb2
330 changed files with 35786 additions and 17316 deletions

100
core/errors/errors.go Normal file
View File

@@ -0,0 +1,100 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package errors
import (
"fmt"
"runtime"
)
var (
// Prefix the error prefix, applies to each error's message.
// Should not be changed.
Prefix = ""
// NewLine adds a new line to the end of each error's message
// defaults to true
NewLine = true
)
// Error holds the error message, this message never really changes
type Error struct {
message string
appended bool
}
// New creates and returns an Error with a pre-defined user output message
// all methods below that doesn't accept a pointer receiver because actually they are not changing the original message
func New(errMsg string) *Error {
if NewLine {
errMsg += "\n"
}
return &Error{message: Prefix + errMsg}
}
// String returns the error message
func (e Error) String() string {
return e.message
}
// Error returns the message of the actual error
// implements the error
func (e Error) Error() string {
return e.String()
}
// Format returns a formatted new error based on the arguments
// it does NOT change the original error's message
func (e Error) Format(a ...interface{}) Error {
e.message = fmt.Sprintf(e.message, a...)
return e
}
// Append adds a message to the predefined error message and returns a new error
// it does NOT change the original error's message
func (e Error) Append(format string, a ...interface{}) Error {
// eCp := *e
if NewLine {
format += "\n"
}
e.message += fmt.Sprintf(format, a...)
e.appended = true
return e
}
// AppendErr adds an error's message to the predefined error message and returns a new error
// it does NOT change the original error's message
func (e Error) AppendErr(err error) Error {
return e.Append(err.Error())
}
// IsAppended returns true if the Error instance is created using original's Error.Append/AppendErr func
func (e Error) IsAppended() bool {
return e.appended
}
// With does the same thing as Format but it receives an error type which if it's nil it returns a nil error
func (e Error) With(err error) error {
if err == nil {
return nil
}
return e.Format(err.Error())
}
// Panic output the message and after panics
func (e Error) Panic() {
_, fn, line, _ := runtime.Caller(1)
errMsg := e.message
errMsg += "\nCaller was: " + fmt.Sprintf("%s:%d", fn, line)
panic(errMsg)
}
// Panicf output the formatted message and after panics
func (e Error) Panicf(args ...interface{}) {
_, fn, line, _ := runtime.Caller(1)
errMsg := e.Format(args...).Error()
errMsg += "\nCaller was: " + fmt.Sprintf("%s:%d", fn, line)
panic(errMsg)
}

233
core/gui/LICENSE Normal file
View File

@@ -0,0 +1,233 @@
Copyright (c) 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Gerasimos Maropoulos nor the name of his
username, kataras, may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Third-Parties:
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 2014 Brave New Software Project, 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.

366
core/gui/icon/iconunix.go Normal file
View File

@@ -0,0 +1,366 @@
//+build linux darwin
// File generated by 2goarray v0.1.0 (http://github.com/cratonica/2goarray)
package icon
var Data []byte = []byte{
0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x20, 0x20, 0x00, 0x00, 0x01, 0x00,
0x20, 0x00, 0xa8, 0x10, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x28, 0x00,
0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x6d, 0x49, 0x00, 0x07, 0x73, 0x42,
0x00, 0x49, 0x72, 0x45, 0x00, 0x98, 0x72, 0x43, 0x00, 0xc1, 0x71, 0x44,
0x00, 0xda, 0x72, 0x44, 0x00, 0xf3, 0x41, 0x24, 0x00, 0xfe, 0x45, 0x27,
0x00, 0xfa, 0x49, 0x29, 0x00, 0xf0, 0x4e, 0x2c, 0x00, 0xd5, 0x55, 0x31,
0x00, 0x7e, 0x5b, 0x37, 0x00, 0x0e, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x70, 0x47, 0x00, 0x19, 0x73, 0x44,
0x00, 0x97, 0x72, 0x44, 0x00, 0xf2, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x73, 0x45, 0x00, 0xff, 0x74, 0x46,
0x01, 0xff, 0x43, 0x25, 0x00, 0xff, 0x41, 0x24, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x41, 0x24,
0x00, 0xfe, 0x4d, 0x2c, 0x00, 0xd6, 0x55, 0x35, 0x00, 0x30, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x80, 0x40, 0x00, 0x08, 0x72, 0x44,
0x00, 0x84, 0x72, 0x44, 0x00, 0xf5, 0x72, 0x44, 0x00, 0xff, 0x74, 0x46,
0x01, 0xff, 0x7e, 0x4f, 0x04, 0xff, 0x89, 0x59, 0x07, 0xff, 0x91, 0x60,
0x09, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49, 0x02, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x6f, 0x42, 0x00, 0xff, 0x62, 0x3a,
0x00, 0xff, 0x52, 0x2f, 0x00, 0xff, 0x42, 0x25, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x41, 0x24, 0x00, 0xff, 0x50, 0x2e, 0x00, 0xc3, 0x66, 0x33,
0x00, 0x0f, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x75, 0x40,
0x00, 0x18, 0x71, 0x44, 0x00, 0xc8, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x7b, 0x4c, 0x03, 0xff, 0x8f, 0x5f, 0x09, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49,
0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x6c, 0x40, 0x00, 0xff, 0x4d, 0x2c, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29, 0x00, 0xf3, 0x59, 0x32,
0x00, 0x2e, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0x71, 0x44, 0x00, 0x2d, 0x71, 0x45, 0x00, 0xe3, 0x72, 0x44,
0x00, 0xff, 0x74, 0x46, 0x01, 0xff, 0x86, 0x56, 0x06, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x90, 0x60,
0x09, 0xff, 0x78, 0x49, 0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x5e, 0x37, 0x00, 0xff, 0x42, 0x25,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x44, 0x26, 0x00, 0xfc, 0x57, 0x32,
0x00, 0x52, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x75, 0x40, 0x00, 0x18, 0x71, 0x45,
0x00, 0xe3, 0x72, 0x44, 0x00, 0xff, 0x78, 0x49, 0x02, 0xff, 0x8f, 0x5e,
0x09, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49, 0x02, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x6c, 0x40, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x44, 0x26, 0x00, 0xfc, 0x59, 0x32,
0x00, 0x2e, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x80, 0x40,
0x00, 0x08, 0x71, 0x44, 0x00, 0xc8, 0x72, 0x44, 0x00, 0xff, 0x73, 0x45,
0x00, 0xff, 0x8d, 0x5c, 0x08, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49,
0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x69, 0x3e, 0x00, 0xff, 0x40, 0x24,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29, 0x00, 0xf3, 0x60, 0x30,
0x00, 0x10, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x72, 0x44, 0x00, 0x84, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x83, 0x53, 0x05, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x90, 0x60,
0x09, 0xff, 0x78, 0x49, 0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x59, 0x34, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x4f, 0x2e, 0x00, 0xc4, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x70, 0x47,
0x00, 0x19, 0x72, 0x44, 0x00, 0xf5, 0x72, 0x44, 0x00, 0xff, 0x79, 0x4a,
0x02, 0xff, 0x92, 0x62, 0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49, 0x02, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x71, 0x43, 0x00, 0xff, 0x49, 0x2a, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x41, 0x24, 0x00, 0xff, 0x55, 0x35, 0x00, 0x30, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x73, 0x44, 0x00, 0x97, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x8d, 0x5c, 0x08, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49,
0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x69, 0x3e, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x4e, 0x2c, 0x00, 0xd5, 0xff, 0xff, 0xff, 0x00, 0x6d, 0x49,
0x00, 0x07, 0x72, 0x44, 0x00, 0xf2, 0x72, 0x44, 0x00, 0xff, 0x78, 0x49,
0x02, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x90, 0x60,
0x09, 0xff, 0x78, 0x49, 0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x48, 0x29, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x41, 0x25,
0x00, 0xfe, 0x5b, 0x37, 0x00, 0x0e, 0x73, 0x42, 0x00, 0x49, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x82, 0x52, 0x05, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49, 0x02, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x57, 0x33,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x56, 0x31,
0x00, 0x7d, 0x72, 0x45, 0x00, 0x98, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x88, 0x58, 0x07, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49,
0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x60, 0x39, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x4e, 0x2c, 0x00, 0xd5, 0x72, 0x43,
0x00, 0xc1, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x88, 0x58,
0x07, 0xff, 0x78, 0x49, 0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x61, 0x39, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x49, 0x29, 0x00, 0xf0, 0x71, 0x44, 0x00, 0xda, 0x72, 0x44,
0x00, 0xff, 0x76, 0x49, 0x00, 0xff, 0x7d, 0x52, 0x01, 0xff, 0x7f, 0x57,
0x00, 0xff, 0x84, 0x5e, 0x00, 0xff, 0x89, 0x65, 0x00, 0xff, 0x8e, 0x6c,
0x00, 0xff, 0x92, 0x73, 0x00, 0xff, 0x97, 0x7a, 0x00, 0xff, 0x9c, 0x81,
0x00, 0xff, 0xa1, 0x88, 0x00, 0xff, 0xa5, 0x8f, 0x00, 0xff, 0xaa, 0x96,
0x00, 0xff, 0x92, 0x72, 0x00, 0xff, 0x7a, 0x4f, 0x00, 0xff, 0x48, 0x2a,
0x00, 0xff, 0x67, 0x4c, 0x00, 0xff, 0x8e, 0x84, 0x00, 0xff, 0x87, 0x7b,
0x00, 0xff, 0x80, 0x73, 0x00, 0xff, 0x79, 0x6b, 0x00, 0xff, 0x73, 0x63,
0x00, 0xff, 0x6c, 0x5a, 0x00, 0xff, 0x66, 0x52, 0x00, 0xff, 0x5f, 0x4a,
0x00, 0xff, 0x58, 0x42, 0x00, 0xff, 0x51, 0x39, 0x00, 0xff, 0x4e, 0x34,
0x00, 0xff, 0x44, 0x29, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x45, 0x27,
0x00, 0xfa, 0x7c, 0x54, 0x00, 0xf4, 0x91, 0x71, 0x00, 0xff, 0xa0, 0x87,
0x00, 0xff, 0x9f, 0x86, 0x00, 0xff, 0x9d, 0x82, 0x00, 0xff, 0x9a, 0x7e,
0x00, 0xff, 0x97, 0x7a, 0x00, 0xff, 0x95, 0x77, 0x00, 0xff, 0x92, 0x72,
0x00, 0xff, 0x8f, 0x6f, 0x00, 0xff, 0x8d, 0x6b, 0x00, 0xff, 0x8a, 0x66,
0x00, 0xff, 0x87, 0x63, 0x00, 0xff, 0x81, 0x59, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x8a, 0x64, 0x2b, 0xff, 0x5f, 0x48, 0x2b, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x58, 0x3d, 0x00, 0xff, 0x5c, 0x47, 0x00, 0xff, 0x60, 0x4c,
0x00, 0xff, 0x64, 0x51, 0x00, 0xff, 0x68, 0x55, 0x00, 0xff, 0x6b, 0x59,
0x00, 0xff, 0x6f, 0x5f, 0x00, 0xff, 0x73, 0x63, 0x00, 0xff, 0x76, 0x67,
0x00, 0xff, 0x7b, 0x6c, 0x00, 0xff, 0x7e, 0x71, 0x00, 0xff, 0x7f, 0x72,
0x00, 0xff, 0x6b, 0x58, 0x00, 0xff, 0x4f, 0x35, 0x00, 0xfe, 0x72, 0x44,
0x00, 0xf3, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x8a, 0x66, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x8a, 0x66,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x7c, 0x52, 0x00, 0xff, 0x80, 0x59,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x4c, 0x2b,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x53, 0x3b, 0x00, 0xff, 0x4c, 0x33, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x60, 0x4c, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x60, 0x4c,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x41, 0x24, 0x00, 0xfe, 0x71, 0x44, 0x00, 0xda, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x73, 0x45,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x73, 0x45, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x73, 0x45, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x73, 0x46, 0x02, 0xff, 0xac, 0xe5,
0xe4, 0xff, 0xa1, 0xf2, 0xf8, 0xff, 0xa0, 0xf2, 0xf8, 0xff, 0xa0, 0xf2,
0xf8, 0xff, 0xa0, 0xf2, 0xf8, 0xff, 0xa9, 0xe4, 0xe5, 0xff, 0x40, 0x25,
0x02, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x40, 0x24,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x40, 0x25,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x40, 0x25, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x45, 0x27,
0x00, 0xfa, 0x71, 0x4c, 0x00, 0xd4, 0x82, 0x68, 0x00, 0xff, 0x96, 0x83,
0x00, 0xff, 0x99, 0x89, 0x00, 0xff, 0x9b, 0x8c, 0x00, 0xff, 0x9b, 0x8e,
0x00, 0xff, 0x9d, 0x91, 0x00, 0xff, 0x9f, 0x95, 0x00, 0xff, 0xa0, 0x98,
0x00, 0xff, 0xa2, 0x9a, 0x00, 0xff, 0xa4, 0x9e, 0x00, 0xff, 0xa8, 0xa3,
0x00, 0xff, 0xb3, 0xb2, 0x1c, 0xff, 0xe9, 0xfb, 0xfd, 0xff, 0xe9, 0xfc,
0xfe, 0xff, 0xe9, 0xfc, 0xfe, 0xff, 0xe9, 0xfc, 0xfe, 0xff, 0xe9, 0xfc,
0xfe, 0xff, 0xe7, 0xf9, 0xfa, 0xff, 0xbd, 0xb5, 0x14, 0xff, 0xb8, 0xad,
0x00, 0xff, 0xb7, 0xaa, 0x00, 0xff, 0xb3, 0xa5, 0x00, 0xff, 0xaf, 0xa2,
0x00, 0xff, 0xab, 0x9d, 0x00, 0xff, 0xa7, 0x98, 0x00, 0xff, 0xa2, 0x93,
0x00, 0xff, 0x9e, 0x8f, 0x00, 0xff, 0x9a, 0x8a, 0x00, 0xff, 0x93, 0x82,
0x00, 0xff, 0x7a, 0x62, 0x00, 0xff, 0x61, 0x41, 0x00, 0xf5, 0x51, 0x30,
0x00, 0xcc, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x42, 0x25,
0x00, 0xff, 0x4c, 0x2c, 0x00, 0xff, 0x54, 0x31, 0x00, 0xff, 0x5c, 0x36,
0x00, 0xff, 0x64, 0x3b, 0x00, 0xff, 0x6a, 0x3f, 0x00, 0xff, 0x6c, 0x40,
0x00, 0xff, 0xa8, 0x8c, 0x62, 0xff, 0xca, 0xb9, 0xa0, 0xff, 0xca, 0xb9,
0xa0, 0xff, 0xb7, 0xad, 0xa0, 0xff, 0xb7, 0xac, 0x9f, 0xff, 0x89, 0x74,
0x58, 0xff, 0x45, 0x27, 0x00, 0xff, 0x47, 0x28, 0x00, 0xff, 0x4d, 0x2c,
0x00, 0xff, 0x55, 0x31, 0x00, 0xff, 0x5d, 0x36, 0x00, 0xff, 0x65, 0x3c,
0x00, 0xff, 0x6f, 0x42, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x62, 0x3a, 0x00, 0xea, 0x5a, 0x33, 0x00, 0x6f, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x49, 0x29,
0x00, 0xff, 0x60, 0x39, 0x00, 0xff, 0x70, 0x43, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x74, 0x46, 0x00, 0xff, 0xa1, 0x91,
0x02, 0xff, 0xb4, 0xb4, 0x04, 0xff, 0x88, 0x67, 0x01, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x4b, 0x2b, 0x00, 0xff, 0x61, 0x4e,
0x01, 0xff, 0xa7, 0xac, 0x04, 0xff, 0x87, 0x80, 0x02, 0xff, 0x41, 0x26,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x41, 0x24,
0x00, 0xff, 0x51, 0x2e, 0x00, 0xff, 0x68, 0x3e, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x5f, 0x37,
0x00, 0x99, 0x60, 0x40, 0x00, 0x08, 0x43, 0x26, 0x00, 0xfe, 0x3f, 0x23,
0x00, 0xff, 0x52, 0x2f, 0x00, 0xff, 0x71, 0x43, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0xba, 0xc5, 0x05, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xb2, 0xc4,
0x08, 0xff, 0xb3, 0xc5, 0x08, 0xff, 0x82, 0x5e, 0x01, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x54, 0x37, 0x00, 0xff, 0xb3, 0xc5, 0x08, 0xff, 0xb2, 0xc4,
0x08, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xb6, 0xc2, 0x05, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x40, 0x24, 0x00, 0xff, 0x5f, 0x38, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x6d, 0x41, 0x00, 0xff, 0x55, 0x33, 0x00, 0x0f, 0xff, 0xff,
0xff, 0x00, 0x4f, 0x2d, 0x00, 0xd3, 0x41, 0x24, 0x00, 0xff, 0x70, 0x43,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x87, 0x67, 0x01, 0xff, 0xae, 0xbe,
0x08, 0xff, 0x9c, 0xa6, 0x06, 0xff, 0x9c, 0xa6, 0x06, 0xff, 0xa4, 0xb0,
0x07, 0xff, 0x94, 0x81, 0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x67, 0x4f,
0x01, 0xff, 0xa4, 0xb0, 0x07, 0xff, 0x9c, 0xa6, 0x06, 0xff, 0x9c, 0xa6,
0x06, 0xff, 0xaf, 0xbf, 0x08, 0xff, 0x5f, 0x4d, 0x01, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x41, 0x24, 0x00, 0xff, 0x70, 0x43, 0x00, 0xff, 0x64, 0x3b,
0x00, 0xee, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x57, 0x34,
0x00, 0x2c, 0x4f, 0x2e, 0x00, 0xfc, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0xa1, 0x8d, 0x01, 0xff, 0x80, 0x7d, 0x04, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x51, 0x3c, 0x01, 0xff, 0xad, 0xb5,
0x06, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x9d, 0xa0, 0x05, 0xff, 0x51, 0x3c,
0x01, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x80, 0x7d,
0x04, 0xff, 0x83, 0x7a, 0x01, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x62, 0x3a, 0x00, 0xff, 0x5e, 0x3b, 0x00, 0x41, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x58, 0x33,
0x00, 0xae, 0x70, 0x43, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x9e, 0x85,
0x00, 0xff, 0xb1, 0xb7, 0x03, 0xff, 0x62, 0x54, 0x02, 0xff, 0x83, 0x82,
0x05, 0xff, 0xa6, 0xb3, 0x07, 0xff, 0xb7, 0xc8, 0x07, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x4a, 0x2a, 0x00, 0xff, 0x4a, 0x2a,
0x00, 0xff, 0xb6, 0xc8, 0x07, 0xff, 0xa6, 0xb3, 0x07, 0xff, 0x83, 0x82,
0x05, 0xff, 0x62, 0x54, 0x02, 0xff, 0xb1, 0xb7, 0x03, 0xff, 0x7d, 0x6f,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x41, 0x24, 0x00, 0xff, 0x5e, 0x37,
0x00, 0xd7, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x71, 0x39, 0x00, 0x09, 0x6a, 0x3f,
0x00, 0xce, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x9e, 0x85, 0x00, 0xff, 0xf0, 0xfd,
0x00, 0xff, 0xbb, 0xcc, 0x07, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xb2, 0xc4,
0x08, 0xff, 0xb7, 0xc8, 0x07, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0xb6, 0xc8,
0x07, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xbb, 0xcc,
0x07, 0xff, 0xf0, 0xfd, 0x00, 0xff, 0x7d, 0x6f, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x4d, 0x2c, 0x00, 0xf4, 0x60, 0x30, 0x00, 0x10, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x75, 0x40, 0x00, 0x18, 0x71, 0x45,
0x00, 0xe3, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x9e, 0x85, 0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xdf, 0xee,
0x02, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xb7, 0xc8,
0x07, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0xb6, 0xc8, 0x07, 0xff, 0xb2, 0xc4,
0x08, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xdf, 0xee, 0x02, 0xff, 0xf2, 0xff,
0x00, 0xff, 0x7d, 0x6f, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x44, 0x26, 0x00, 0xfc, 0x59, 0x32,
0x00, 0x2e, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x71, 0x44, 0x00, 0x2d, 0x71, 0x45,
0x00, 0xe3, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x9e, 0x85,
0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xc6, 0xd7,
0x05, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xb7, 0xc8, 0x07, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0xb6, 0xc8, 0x07, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xc6, 0xd7,
0x05, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0x7d, 0x6f,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x44, 0x26,
0x00, 0xfc, 0x57, 0x32, 0x00, 0x52, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x75, 0x40, 0x00, 0x18, 0x71, 0x44,
0x00, 0xc8, 0x72, 0x44, 0x00, 0xff, 0x9e, 0x85, 0x00, 0xff, 0xf2, 0xff,
0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xeb, 0xf8, 0x01, 0xff, 0xb5, 0xc7,
0x08, 0xff, 0xb7, 0xc8, 0x07, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0xb6, 0xc8,
0x07, 0xff, 0xb5, 0xc7, 0x08, 0xff, 0xeb, 0xf8, 0x01, 0xff, 0xf2, 0xff,
0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0x7d, 0x6f, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x48, 0x29, 0x00, 0xf3, 0x59, 0x32, 0x00, 0x2e, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x80, 0x40, 0x00, 0x08, 0x72, 0x44,
0x00, 0x84, 0xa0, 0x87, 0x00, 0xf8, 0xf2, 0xff, 0x00, 0xff, 0xf2, 0xff,
0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xd4, 0xe4, 0x04, 0xff, 0xb7, 0xc8,
0x07, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0xb6, 0xc8, 0x07, 0xff, 0xd4, 0xe4,
0x04, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xf2, 0xff,
0x00, 0xff, 0x7e, 0x70, 0x00, 0xff, 0x4f, 0x2e, 0x00, 0xc4, 0x60, 0x30,
0x00, 0x10, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xac, 0x9a,
0x00, 0x2b, 0xc4, 0xbc, 0x00, 0xcc, 0xe6, 0xee, 0x00, 0xfe, 0xf2, 0xff,
0x00, 0xff, 0xf1, 0xfe, 0x00, 0xff, 0xbf, 0xcf, 0x06, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0xbe, 0xce, 0x06, 0xff, 0xf1, 0xfe, 0x00, 0xff, 0xf2, 0xff,
0x00, 0xff, 0xe1, 0xea, 0x00, 0xff, 0xa9, 0xa1, 0x00, 0xea, 0x87, 0x74,
0x00, 0x40, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xaa, 0x95, 0x00, 0x0c, 0xbe, 0xb0, 0x00, 0x7e, 0xc5, 0xbd,
0x00, 0xce, 0xcb, 0xcb, 0x01, 0xee, 0x71, 0x44, 0x00, 0xda, 0x72, 0x44,
0x00, 0xf3, 0x41, 0x24, 0x00, 0xfe, 0x45, 0x27, 0x00, 0xfa, 0xbb, 0xbd,
0x01, 0xfb, 0xab, 0xa5, 0x00, 0xeb, 0x9c, 0x8e, 0x00, 0xa3, 0x86, 0x6b,
0x00, 0x13, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xc0, 0x03, 0xff, 0xff, 0x00,
0x00, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xf8, 0x00, 0x00, 0x1f, 0xf0, 0x00,
0x00, 0x0f, 0xe0, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x03, 0xc0, 0x00,
0x00, 0x03, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x03, 0xc0, 0x00,
0x00, 0x03, 0xe0, 0x00, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x0f, 0xf8, 0x00,
0x00, 0x1f, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0xff, 0xff, 0xc0,
0x03, 0xff,
}

366
core/gui/icon/iconwin.go Normal file
View File

@@ -0,0 +1,366 @@
//+build windows
// File generated by 2goarray v0.1.0 (http://github.com/cratonica/2goarray)
package icon
var Data []byte = []byte{
0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x20, 0x20, 0x00, 0x00, 0x01, 0x00,
0x20, 0x00, 0xa8, 0x10, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x28, 0x00,
0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x6d, 0x49, 0x00, 0x07, 0x73, 0x42,
0x00, 0x49, 0x72, 0x45, 0x00, 0x98, 0x72, 0x43, 0x00, 0xc1, 0x71, 0x44,
0x00, 0xda, 0x72, 0x44, 0x00, 0xf3, 0x41, 0x24, 0x00, 0xfe, 0x45, 0x27,
0x00, 0xfa, 0x49, 0x29, 0x00, 0xf0, 0x4e, 0x2c, 0x00, 0xd5, 0x55, 0x31,
0x00, 0x7e, 0x5b, 0x37, 0x00, 0x0e, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x70, 0x47, 0x00, 0x19, 0x73, 0x44,
0x00, 0x97, 0x72, 0x44, 0x00, 0xf2, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x73, 0x45, 0x00, 0xff, 0x74, 0x46,
0x01, 0xff, 0x43, 0x25, 0x00, 0xff, 0x41, 0x24, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x41, 0x24,
0x00, 0xfe, 0x4d, 0x2c, 0x00, 0xd6, 0x55, 0x35, 0x00, 0x30, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x80, 0x40, 0x00, 0x08, 0x72, 0x44,
0x00, 0x84, 0x72, 0x44, 0x00, 0xf5, 0x72, 0x44, 0x00, 0xff, 0x74, 0x46,
0x01, 0xff, 0x7e, 0x4f, 0x04, 0xff, 0x89, 0x59, 0x07, 0xff, 0x91, 0x60,
0x09, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49, 0x02, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x6f, 0x42, 0x00, 0xff, 0x62, 0x3a,
0x00, 0xff, 0x52, 0x2f, 0x00, 0xff, 0x42, 0x25, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x41, 0x24, 0x00, 0xff, 0x50, 0x2e, 0x00, 0xc3, 0x66, 0x33,
0x00, 0x0f, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x75, 0x40,
0x00, 0x18, 0x71, 0x44, 0x00, 0xc8, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x7b, 0x4c, 0x03, 0xff, 0x8f, 0x5f, 0x09, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49,
0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x6c, 0x40, 0x00, 0xff, 0x4d, 0x2c, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29, 0x00, 0xf3, 0x59, 0x32,
0x00, 0x2e, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0x71, 0x44, 0x00, 0x2d, 0x71, 0x45, 0x00, 0xe3, 0x72, 0x44,
0x00, 0xff, 0x74, 0x46, 0x01, 0xff, 0x86, 0x56, 0x06, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x90, 0x60,
0x09, 0xff, 0x78, 0x49, 0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x5e, 0x37, 0x00, 0xff, 0x42, 0x25,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x44, 0x26, 0x00, 0xfc, 0x57, 0x32,
0x00, 0x52, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x75, 0x40, 0x00, 0x18, 0x71, 0x45,
0x00, 0xe3, 0x72, 0x44, 0x00, 0xff, 0x78, 0x49, 0x02, 0xff, 0x8f, 0x5e,
0x09, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49, 0x02, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x6c, 0x40, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x44, 0x26, 0x00, 0xfc, 0x59, 0x32,
0x00, 0x2e, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x80, 0x40,
0x00, 0x08, 0x71, 0x44, 0x00, 0xc8, 0x72, 0x44, 0x00, 0xff, 0x73, 0x45,
0x00, 0xff, 0x8d, 0x5c, 0x08, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49,
0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x69, 0x3e, 0x00, 0xff, 0x40, 0x24,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29, 0x00, 0xf3, 0x60, 0x30,
0x00, 0x10, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x72, 0x44, 0x00, 0x84, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x83, 0x53, 0x05, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x90, 0x60,
0x09, 0xff, 0x78, 0x49, 0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x59, 0x34, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x4f, 0x2e, 0x00, 0xc4, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x70, 0x47,
0x00, 0x19, 0x72, 0x44, 0x00, 0xf5, 0x72, 0x44, 0x00, 0xff, 0x79, 0x4a,
0x02, 0xff, 0x92, 0x62, 0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49, 0x02, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x71, 0x43, 0x00, 0xff, 0x49, 0x2a, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x41, 0x24, 0x00, 0xff, 0x55, 0x35, 0x00, 0x30, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x73, 0x44, 0x00, 0x97, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x8d, 0x5c, 0x08, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49,
0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x69, 0x3e, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x4e, 0x2c, 0x00, 0xd5, 0xff, 0xff, 0xff, 0x00, 0x6d, 0x49,
0x00, 0x07, 0x72, 0x44, 0x00, 0xf2, 0x72, 0x44, 0x00, 0xff, 0x78, 0x49,
0x02, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x93, 0x62, 0x0a, 0xff, 0x90, 0x60,
0x09, 0xff, 0x78, 0x49, 0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x48, 0x29, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x41, 0x25,
0x00, 0xfe, 0x5b, 0x37, 0x00, 0x0e, 0x73, 0x42, 0x00, 0x49, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x82, 0x52, 0x05, 0xff, 0x93, 0x62,
0x0a, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49, 0x02, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x57, 0x33,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x56, 0x31,
0x00, 0x7d, 0x72, 0x45, 0x00, 0x98, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x88, 0x58, 0x07, 0xff, 0x90, 0x60, 0x09, 0xff, 0x78, 0x49,
0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x6e, 0x41, 0x00, 0xff, 0x60, 0x39, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x4e, 0x2c, 0x00, 0xd5, 0x72, 0x43,
0x00, 0xc1, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x88, 0x58,
0x07, 0xff, 0x78, 0x49, 0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x48, 0x29,
0x00, 0xff, 0x61, 0x39, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x49, 0x29, 0x00, 0xf0, 0x71, 0x44, 0x00, 0xda, 0x72, 0x44,
0x00, 0xff, 0x76, 0x49, 0x00, 0xff, 0x7d, 0x52, 0x01, 0xff, 0x7f, 0x57,
0x00, 0xff, 0x84, 0x5e, 0x00, 0xff, 0x89, 0x65, 0x00, 0xff, 0x8e, 0x6c,
0x00, 0xff, 0x92, 0x73, 0x00, 0xff, 0x97, 0x7a, 0x00, 0xff, 0x9c, 0x81,
0x00, 0xff, 0xa1, 0x88, 0x00, 0xff, 0xa5, 0x8f, 0x00, 0xff, 0xaa, 0x96,
0x00, 0xff, 0x92, 0x72, 0x00, 0xff, 0x7a, 0x4f, 0x00, 0xff, 0x48, 0x2a,
0x00, 0xff, 0x67, 0x4c, 0x00, 0xff, 0x8e, 0x84, 0x00, 0xff, 0x87, 0x7b,
0x00, 0xff, 0x80, 0x73, 0x00, 0xff, 0x79, 0x6b, 0x00, 0xff, 0x73, 0x63,
0x00, 0xff, 0x6c, 0x5a, 0x00, 0xff, 0x66, 0x52, 0x00, 0xff, 0x5f, 0x4a,
0x00, 0xff, 0x58, 0x42, 0x00, 0xff, 0x51, 0x39, 0x00, 0xff, 0x4e, 0x34,
0x00, 0xff, 0x44, 0x29, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x45, 0x27,
0x00, 0xfa, 0x7c, 0x54, 0x00, 0xf4, 0x91, 0x71, 0x00, 0xff, 0xa0, 0x87,
0x00, 0xff, 0x9f, 0x86, 0x00, 0xff, 0x9d, 0x82, 0x00, 0xff, 0x9a, 0x7e,
0x00, 0xff, 0x97, 0x7a, 0x00, 0xff, 0x95, 0x77, 0x00, 0xff, 0x92, 0x72,
0x00, 0xff, 0x8f, 0x6f, 0x00, 0xff, 0x8d, 0x6b, 0x00, 0xff, 0x8a, 0x66,
0x00, 0xff, 0x87, 0x63, 0x00, 0xff, 0x81, 0x59, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x8a, 0x64, 0x2b, 0xff, 0x5f, 0x48, 0x2b, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x58, 0x3d, 0x00, 0xff, 0x5c, 0x47, 0x00, 0xff, 0x60, 0x4c,
0x00, 0xff, 0x64, 0x51, 0x00, 0xff, 0x68, 0x55, 0x00, 0xff, 0x6b, 0x59,
0x00, 0xff, 0x6f, 0x5f, 0x00, 0xff, 0x73, 0x63, 0x00, 0xff, 0x76, 0x67,
0x00, 0xff, 0x7b, 0x6c, 0x00, 0xff, 0x7e, 0x71, 0x00, 0xff, 0x7f, 0x72,
0x00, 0xff, 0x6b, 0x58, 0x00, 0xff, 0x4f, 0x35, 0x00, 0xfe, 0x72, 0x44,
0x00, 0xf3, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x8a, 0x66, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x8a, 0x66,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x7c, 0x52, 0x00, 0xff, 0x80, 0x59,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x4c, 0x2b,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x53, 0x3b, 0x00, 0xff, 0x4c, 0x33, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x60, 0x4c, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x60, 0x4c,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x41, 0x24, 0x00, 0xfe, 0x71, 0x44, 0x00, 0xda, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x73, 0x45,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x73, 0x45, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x73, 0x45, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x73, 0x46, 0x02, 0xff, 0xac, 0xe5,
0xe4, 0xff, 0xa1, 0xf2, 0xf8, 0xff, 0xa0, 0xf2, 0xf8, 0xff, 0xa0, 0xf2,
0xf8, 0xff, 0xa0, 0xf2, 0xf8, 0xff, 0xa9, 0xe4, 0xe5, 0xff, 0x40, 0x25,
0x02, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x40, 0x24,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x40, 0x25,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x40, 0x25, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x45, 0x27,
0x00, 0xfa, 0x71, 0x4c, 0x00, 0xd4, 0x82, 0x68, 0x00, 0xff, 0x96, 0x83,
0x00, 0xff, 0x99, 0x89, 0x00, 0xff, 0x9b, 0x8c, 0x00, 0xff, 0x9b, 0x8e,
0x00, 0xff, 0x9d, 0x91, 0x00, 0xff, 0x9f, 0x95, 0x00, 0xff, 0xa0, 0x98,
0x00, 0xff, 0xa2, 0x9a, 0x00, 0xff, 0xa4, 0x9e, 0x00, 0xff, 0xa8, 0xa3,
0x00, 0xff, 0xb3, 0xb2, 0x1c, 0xff, 0xe9, 0xfb, 0xfd, 0xff, 0xe9, 0xfc,
0xfe, 0xff, 0xe9, 0xfc, 0xfe, 0xff, 0xe9, 0xfc, 0xfe, 0xff, 0xe9, 0xfc,
0xfe, 0xff, 0xe7, 0xf9, 0xfa, 0xff, 0xbd, 0xb5, 0x14, 0xff, 0xb8, 0xad,
0x00, 0xff, 0xb7, 0xaa, 0x00, 0xff, 0xb3, 0xa5, 0x00, 0xff, 0xaf, 0xa2,
0x00, 0xff, 0xab, 0x9d, 0x00, 0xff, 0xa7, 0x98, 0x00, 0xff, 0xa2, 0x93,
0x00, 0xff, 0x9e, 0x8f, 0x00, 0xff, 0x9a, 0x8a, 0x00, 0xff, 0x93, 0x82,
0x00, 0xff, 0x7a, 0x62, 0x00, 0xff, 0x61, 0x41, 0x00, 0xf5, 0x51, 0x30,
0x00, 0xcc, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x42, 0x25,
0x00, 0xff, 0x4c, 0x2c, 0x00, 0xff, 0x54, 0x31, 0x00, 0xff, 0x5c, 0x36,
0x00, 0xff, 0x64, 0x3b, 0x00, 0xff, 0x6a, 0x3f, 0x00, 0xff, 0x6c, 0x40,
0x00, 0xff, 0xa8, 0x8c, 0x62, 0xff, 0xca, 0xb9, 0xa0, 0xff, 0xca, 0xb9,
0xa0, 0xff, 0xb7, 0xad, 0xa0, 0xff, 0xb7, 0xac, 0x9f, 0xff, 0x89, 0x74,
0x58, 0xff, 0x45, 0x27, 0x00, 0xff, 0x47, 0x28, 0x00, 0xff, 0x4d, 0x2c,
0x00, 0xff, 0x55, 0x31, 0x00, 0xff, 0x5d, 0x36, 0x00, 0xff, 0x65, 0x3c,
0x00, 0xff, 0x6f, 0x42, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x62, 0x3a, 0x00, 0xea, 0x5a, 0x33, 0x00, 0x6f, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x49, 0x29,
0x00, 0xff, 0x60, 0x39, 0x00, 0xff, 0x70, 0x43, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x74, 0x46, 0x00, 0xff, 0xa1, 0x91,
0x02, 0xff, 0xb4, 0xb4, 0x04, 0xff, 0x88, 0x67, 0x01, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x4b, 0x2b, 0x00, 0xff, 0x61, 0x4e,
0x01, 0xff, 0xa7, 0xac, 0x04, 0xff, 0x87, 0x80, 0x02, 0xff, 0x41, 0x26,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x41, 0x24,
0x00, 0xff, 0x51, 0x2e, 0x00, 0xff, 0x68, 0x3e, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x5f, 0x37,
0x00, 0x99, 0x60, 0x40, 0x00, 0x08, 0x43, 0x26, 0x00, 0xfe, 0x3f, 0x23,
0x00, 0xff, 0x52, 0x2f, 0x00, 0xff, 0x71, 0x43, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0xba, 0xc5, 0x05, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xb2, 0xc4,
0x08, 0xff, 0xb3, 0xc5, 0x08, 0xff, 0x82, 0x5e, 0x01, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x54, 0x37, 0x00, 0xff, 0xb3, 0xc5, 0x08, 0xff, 0xb2, 0xc4,
0x08, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xb6, 0xc2, 0x05, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x40, 0x24, 0x00, 0xff, 0x5f, 0x38, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x6d, 0x41, 0x00, 0xff, 0x55, 0x33, 0x00, 0x0f, 0xff, 0xff,
0xff, 0x00, 0x4f, 0x2d, 0x00, 0xd3, 0x41, 0x24, 0x00, 0xff, 0x70, 0x43,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x87, 0x67, 0x01, 0xff, 0xae, 0xbe,
0x08, 0xff, 0x9c, 0xa6, 0x06, 0xff, 0x9c, 0xa6, 0x06, 0xff, 0xa4, 0xb0,
0x07, 0xff, 0x94, 0x81, 0x02, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x67, 0x4f,
0x01, 0xff, 0xa4, 0xb0, 0x07, 0xff, 0x9c, 0xa6, 0x06, 0xff, 0x9c, 0xa6,
0x06, 0xff, 0xaf, 0xbf, 0x08, 0xff, 0x5f, 0x4d, 0x01, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x41, 0x24, 0x00, 0xff, 0x70, 0x43, 0x00, 0xff, 0x64, 0x3b,
0x00, 0xee, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x57, 0x34,
0x00, 0x2c, 0x4f, 0x2e, 0x00, 0xfc, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0xa1, 0x8d, 0x01, 0xff, 0x80, 0x7d, 0x04, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x51, 0x3c, 0x01, 0xff, 0xad, 0xb5,
0x06, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x9d, 0xa0, 0x05, 0xff, 0x51, 0x3c,
0x01, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x80, 0x7d,
0x04, 0xff, 0x83, 0x7a, 0x01, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x62, 0x3a, 0x00, 0xff, 0x5e, 0x3b, 0x00, 0x41, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x58, 0x33,
0x00, 0xae, 0x70, 0x43, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x9e, 0x85,
0x00, 0xff, 0xb1, 0xb7, 0x03, 0xff, 0x62, 0x54, 0x02, 0xff, 0x83, 0x82,
0x05, 0xff, 0xa6, 0xb3, 0x07, 0xff, 0xb7, 0xc8, 0x07, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x4a, 0x2a, 0x00, 0xff, 0x4a, 0x2a,
0x00, 0xff, 0xb6, 0xc8, 0x07, 0xff, 0xa6, 0xb3, 0x07, 0xff, 0x83, 0x82,
0x05, 0xff, 0x62, 0x54, 0x02, 0xff, 0xb1, 0xb7, 0x03, 0xff, 0x7d, 0x6f,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x41, 0x24, 0x00, 0xff, 0x5e, 0x37,
0x00, 0xd7, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x71, 0x39, 0x00, 0x09, 0x6a, 0x3f,
0x00, 0xce, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x9e, 0x85, 0x00, 0xff, 0xf0, 0xfd,
0x00, 0xff, 0xbb, 0xcc, 0x07, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xb2, 0xc4,
0x08, 0xff, 0xb7, 0xc8, 0x07, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0xb6, 0xc8,
0x07, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xbb, 0xcc,
0x07, 0xff, 0xf0, 0xfd, 0x00, 0xff, 0x7d, 0x6f, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x4d, 0x2c, 0x00, 0xf4, 0x60, 0x30, 0x00, 0x10, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x75, 0x40, 0x00, 0x18, 0x71, 0x45,
0x00, 0xe3, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x9e, 0x85, 0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xdf, 0xee,
0x02, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xb7, 0xc8,
0x07, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0xb6, 0xc8, 0x07, 0xff, 0xb2, 0xc4,
0x08, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xdf, 0xee, 0x02, 0xff, 0xf2, 0xff,
0x00, 0xff, 0x7d, 0x6f, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x44, 0x26, 0x00, 0xfc, 0x59, 0x32,
0x00, 0x2e, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x71, 0x44, 0x00, 0x2d, 0x71, 0x45,
0x00, 0xe3, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x9e, 0x85,
0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xc6, 0xd7,
0x05, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xb7, 0xc8, 0x07, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0xb6, 0xc8, 0x07, 0xff, 0xb2, 0xc4, 0x08, 0xff, 0xc6, 0xd7,
0x05, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0x7d, 0x6f,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x44, 0x26,
0x00, 0xfc, 0x57, 0x32, 0x00, 0x52, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x75, 0x40, 0x00, 0x18, 0x71, 0x44,
0x00, 0xc8, 0x72, 0x44, 0x00, 0xff, 0x9e, 0x85, 0x00, 0xff, 0xf2, 0xff,
0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xeb, 0xf8, 0x01, 0xff, 0xb5, 0xc7,
0x08, 0xff, 0xb7, 0xc8, 0x07, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0xb6, 0xc8,
0x07, 0xff, 0xb5, 0xc7, 0x08, 0xff, 0xeb, 0xf8, 0x01, 0xff, 0xf2, 0xff,
0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0x7d, 0x6f, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x48, 0x29, 0x00, 0xf3, 0x59, 0x32, 0x00, 0x2e, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x80, 0x40, 0x00, 0x08, 0x72, 0x44,
0x00, 0x84, 0xa0, 0x87, 0x00, 0xf8, 0xf2, 0xff, 0x00, 0xff, 0xf2, 0xff,
0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xd4, 0xe4, 0x04, 0xff, 0xb7, 0xc8,
0x07, 0xff, 0x72, 0x44, 0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0xb6, 0xc8, 0x07, 0xff, 0xd4, 0xe4,
0x04, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xf2, 0xff, 0x00, 0xff, 0xf2, 0xff,
0x00, 0xff, 0x7e, 0x70, 0x00, 0xff, 0x4f, 0x2e, 0x00, 0xc4, 0x60, 0x30,
0x00, 0x10, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xac, 0x9a,
0x00, 0x2b, 0xc4, 0xbc, 0x00, 0xcc, 0xe6, 0xee, 0x00, 0xfe, 0xf2, 0xff,
0x00, 0xff, 0xf1, 0xfe, 0x00, 0xff, 0xbf, 0xcf, 0x06, 0xff, 0x72, 0x44,
0x00, 0xff, 0x72, 0x44, 0x00, 0xff, 0x3f, 0x23, 0x00, 0xff, 0x3f, 0x23,
0x00, 0xff, 0xbe, 0xce, 0x06, 0xff, 0xf1, 0xfe, 0x00, 0xff, 0xf2, 0xff,
0x00, 0xff, 0xe1, 0xea, 0x00, 0xff, 0xa9, 0xa1, 0x00, 0xea, 0x87, 0x74,
0x00, 0x40, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xaa, 0x95, 0x00, 0x0c, 0xbe, 0xb0, 0x00, 0x7e, 0xc5, 0xbd,
0x00, 0xce, 0xcb, 0xcb, 0x01, 0xee, 0x71, 0x44, 0x00, 0xda, 0x72, 0x44,
0x00, 0xf3, 0x41, 0x24, 0x00, 0xfe, 0x45, 0x27, 0x00, 0xfa, 0xbb, 0xbd,
0x01, 0xfb, 0xab, 0xa5, 0x00, 0xeb, 0x9c, 0x8e, 0x00, 0xa3, 0x86, 0x6b,
0x00, 0x13, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xc0, 0x03, 0xff, 0xff, 0x00,
0x00, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xf8, 0x00, 0x00, 0x1f, 0xf0, 0x00,
0x00, 0x0f, 0xe0, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x03, 0xc0, 0x00,
0x00, 0x03, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x03, 0xc0, 0x00,
0x00, 0x03, 0xe0, 0x00, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x0f, 0xf8, 0x00,
0x00, 0x1f, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0xff, 0xff, 0xc0,
0x03, 0xff,
}

272
core/gui/tray.go Normal file
View File

@@ -0,0 +1,272 @@
// +build !linux
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gui
import (
"sync/atomic"
"github.com/getlantern/systray"
"github.com/kataras/iris/core/errors"
"github.com/kataras/iris/core/gui/icon"
)
var trayRunning int32 // != 0 means a system tray is running
type TrayItem struct {
*systray.MenuItem
title string
checked bool
disabled bool
clickEvents []TrayItemClickEvent
}
type TrayItemClickEvent func(*TrayItem)
func newTrayItem(title string) *TrayItem {
// MenuItem's fields are not exported,
// I could modify the source code to completes my needs
// but I will not because the sys tram is only one
// and its items are not shown until .Show()
// So I will use the AddMenuItem because it adds them with a specific order
// and I want to control the order, so this TrayItem
// will be a wrapper of the *systray.MenuItem and it will be shown as independed when tray host.Show called.
return &TrayItem{title: title}
}
func (i *TrayItem) Check() {
i.checked = true
if i.MenuItem != nil {
i.MenuItem.Check()
}
}
func (i *TrayItem) Uncheck() {
i.checked = false
if i.MenuItem != nil {
i.MenuItem.Uncheck()
}
}
func (i *TrayItem) Checked() bool {
return i.checked
}
func (i *TrayItem) Disable() {
i.disabled = true
if i.MenuItem != nil {
i.MenuItem.Disable()
}
}
func (i *TrayItem) Enable() {
i.disabled = false
if i.MenuItem != nil {
i.MenuItem.Enable()
}
}
func (i *TrayItem) Disabled() bool {
return i.disabled
}
func (i *TrayItem) SetTitle(title string) *TrayItem {
i.title = title
if i.MenuItem != nil {
i.MenuItem.SetTitle(title)
i.MenuItem.SetTooltip(title)
}
return i
}
func (i *TrayItem) SetToolTip(tooltip string) *TrayItem {
return i.SetTitle(tooltip)
}
// always checked by-default
func newTrayItemCheckBox(checkedTitle string, onCheck TrayItemClickEvent,
unCheckedTitle string, onUnCheck TrayItemClickEvent) *TrayItem {
item := newTrayItem(checkedTitle)
item.Check()
item.OnClick(func(i *TrayItem) {
if item.Checked() {
item.SetTitle(unCheckedTitle)
item.Uncheck()
onUnCheck(i)
} else {
item.SetTitle(checkedTitle)
item.Check()
onCheck(i)
}
})
return item
}
const (
sepLine = "────────────────"
)
func newTrayItemSeparator() *TrayItem {
item := newTrayItem(sepLine)
item.Disable()
return item
}
func (i *TrayItem) show() {
activeItem := systray.AddMenuItem(i.title, i.title)
if i.disabled {
activeItem.Disable()
}
if i.checked {
activeItem.Check()
}
i.MenuItem = activeItem
go func() {
for {
<-activeItem.ClickedCh
i.fireClick()
}
}()
}
func (i *TrayItem) OnClick(callback TrayItemClickEvent) *TrayItem {
i.clickEvents = append(i.clickEvents, callback)
return i
}
func (i *TrayItem) fireClick() {
for _, cb := range i.clickEvents {
cb(i)
}
}
type TrayHost struct {
items []*TrayItem // useless
version string
shutdownServerCb func()
startServerCb func()
hideCb func()
}
var Tray = defaultTrayHost()
func defaultTrayHost() *TrayHost {
t := new(TrayHost)
return t
}
func (t *TrayHost) putItem(item *TrayItem) {
t.items = append(t.items, item)
}
func (t *TrayHost) PutItem(title string) *TrayItem {
item := newTrayItem(title)
t.putItem(item)
return item
}
func (t *TrayHost) PutCheckBox(checkedTitle string, onCheck TrayItemClickEvent,
unCheckedTitle string, onUnCheck TrayItemClickEvent) *TrayItem {
item := newTrayItemCheckBox(checkedTitle, onCheck, unCheckedTitle, onUnCheck)
t.putItem(item)
return item
}
func (t *TrayHost) PutSeparator() *TrayItem {
item := newTrayItemSeparator()
t.putItem(item)
return item
}
func (t *TrayHost) SetVersion(v string) *TrayHost {
t.version = v
return t
}
func (t *TrayHost) OnServerStatusChange(start func(), shutdown func()) *TrayHost {
t.startServerCb = start
t.shutdownServerCb = shutdown
return t
}
func (t *TrayHost) OnHide(cb func()) *TrayHost {
t.hideCb = cb
return t
}
func (t *TrayHost) Hide() {
atomic.StoreInt32(&trayRunning, -1)
systray.Quit()
if t.hideCb != nil {
t.hideCb()
}
}
func (t *TrayHost) Show() error {
if running := atomic.LoadInt32(&trayRunning); running != 0 {
return errors.New("A system tray is already running, please close that first")
}
topItems := make([]*TrayItem, 0) // dynamic because we don't know if status btn will be shown
versionBtn := newTrayItem("Version " + t.version)
versionBtn.Disable()
topItems = append(topItems, versionBtn)
// if server status listeners have been registered, then show the online/offline button
// otherwise not.
if t.startServerCb != nil && t.shutdownServerCb != nil {
statusBtn := newTrayItemCheckBox("Online", t.startServerClicked, "Offline", t.shutdownServerClicked)
topItems = append(topItems, statusBtn)
}
topItems = append(topItems, newTrayItemSeparator())
t.items = append(topItems, t.items...)
t.PutItem("Hide").OnClick(t.hideClicked)
systray.Run(t.onTrayReady)
return nil
}
func (t *TrayHost) onTrayReady() {
systray.SetIcon(icon.Data)
systray.SetTitle("Iris web server")
systray.SetTooltip("Iris")
for _, item := range t.items {
item.show()
}
atomic.StoreInt32(&trayRunning, 1)
}
func (t *TrayHost) DisableItem(index int) {
if len(t.items)-1 > index {
t.items[index].Disable()
}
}
func (t *TrayHost) startServerClicked(item *TrayItem) {
if t.startServerCb != nil {
t.startServerCb()
}
}
func (t *TrayHost) shutdownServerClicked(item *TrayItem) {
if t.shutdownServerCb != nil {
t.shutdownServerCb()
}
}
func (t *TrayHost) hideClicked(item *TrayItem) {
t.Hide()
}

View File

@@ -0,0 +1,109 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package handlerconv
import (
"net/http"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
)
var errHandler = errors.New(`
Passed argument is not a func(context.Context) neither one of these types:
- http.Handler
- func(w http.ResponseWriter, r *http.Request)
- func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)
---------------------------------------------------------------------
It seems to be a %T points to: %v`)
// FromStd converts native http.Handler & http.HandlerFunc to context.Handler.
//
// Supported form types:
// .FromStd(h http.Handler)
// .FromStd(func(w http.ResponseWriter, r *http.Request))
// .FromStd(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc))
func FromStd(handler interface{}) context.Handler {
switch handler.(type) {
case context.Handler:
{
//
//it's already a iris handler
//
return handler.(context.Handler)
}
case http.Handler:
//
// handlerFunc.ServeHTTP(w,r)
//
{
h := handler.(http.Handler)
return func(ctx context.Context) {
h.ServeHTTP(ctx.ResponseWriter(), ctx.Request())
}
}
case func(http.ResponseWriter, *http.Request):
{
//
// handlerFunc(w,r)
//
return FromStd(http.HandlerFunc(handler.(func(http.ResponseWriter, *http.Request))))
}
case func(http.ResponseWriter, *http.Request, http.HandlerFunc):
{
//
// handlerFunc(w,r, http.HandlerFunc)
//
return FromStdWithNext(handler.(func(http.ResponseWriter, *http.Request, http.HandlerFunc)))
}
default:
{
//
// No valid handler passed
//
panic(errHandler.Format(handler, handler))
}
}
}
// FromStdWithNext receives a standar handler - middleware form - and returns a compatible context.Handler wrapper.
func FromStdWithNext(h func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)) context.Handler {
return func(ctx context.Context) {
// take the next handler in route's chain
nextIrisHandler := ctx.NextHandler()
if nextIrisHandler != nil {
executed := false // we need to watch this in order to StopExecution from all next handlers
// if this next handler is not executed by the third-party net/http next-style Handlers.
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextIrisHandler(ctx)
executed = true
})
h(ctx.ResponseWriter(), ctx.Request(), nextHandler)
// after third-party Handlers's job:
if executed {
// if next is executed then increment the position manually
// in order to the next handler not to be executed twice.
ctx.HandlerIndex(ctx.HandlerIndex(-1) + 1)
} else {
// otherwise StopExecution from all next handlers.
ctx.StopExecution()
}
return
}
// if not next handler found then this is not a 'valid' Handlers but
// some Handlers may don't care about next,
// so we just execute the handler with an empty net.
h(ctx.ResponseWriter(), ctx.Request(), http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
}
}

66
core/host/proxy.go Normal file
View File

@@ -0,0 +1,66 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package host
import (
"net/http"
"net/http/httputil"
"net/url"
"strings"
)
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
// ProxyHandler returns a new ReverseProxy that rewrites
// URLs to the scheme, host, and base path provided in target. If the
// target's path is "/base" and the incoming request was for "/dir",
// the target request will be for /base/dir.
//
// Relative to httputil.NewSingleHostReverseProxy with some additions.
// Used for the deprecated `LETSENCRYPT`.
func ProxyHandler(target *url.URL) *httputil.ReverseProxy {
targetQuery := target.RawQuery
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.Host = target.Host
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
}
return &httputil.ReverseProxy{Director: director}
}
// NewProxy returns a new host (server supervisor) which
// redirects all requests to the target.
// It uses the httputil.NewSingleHostReverseProxy.
//
// Usage:
// target,_ := url.Parse("https://mydomain.com")
// proxy := NewProxy("mydomain.com:80", target)
// proxy.ListenAndServe() // use of proxy.Shutdown to close the proxy server.
func NewProxy(hostAddr string, target *url.URL) *Supervisor {
proxyHandler := ProxyHandler(target)
proxy := New(&http.Server{
Addr: hostAddr,
Handler: proxyHandler,
})
return proxy
}

132
core/host/scheduler.go Normal file
View File

@@ -0,0 +1,132 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package host
import (
"sync/atomic"
)
type task struct {
runner TaskRunner
proc TaskProcess
// atomic-accessed, if != 0 means that is already
// canceled before it ever ran, this happens to interrupt handlers too.
alreadyCanceled int32
Cancel func()
}
func (t *task) isCanceled() bool {
return atomic.LoadInt32(&t.alreadyCanceled) != 0
}
type Scheduler struct {
onServeTasks []*task
onInterruptTasks []*task
}
type TaskCancelFunc func()
func (s *Scheduler) Schedule(runner TaskRunner) TaskCancelFunc {
t := new(task)
t.runner = runner
t.Cancel = func() {
// it's not running yet, so if canceled now
// set to already canceled to not run it at all.
atomic.StoreInt32(&t.alreadyCanceled, 1)
}
if _, ok := runner.(OnInterrupt); ok {
s.onInterruptTasks = append(s.onInterruptTasks, t)
} else {
s.onServeTasks = append(s.onServeTasks, t)
}
return func() {
t.Cancel()
}
}
func (s *Scheduler) ScheduleFunc(runner func(TaskProcess)) TaskCancelFunc {
return s.Schedule(TaskRunnerFunc(runner))
}
func cancelTasks(tasks []*task) {
for _, t := range tasks {
if atomic.LoadInt32(&t.alreadyCanceled) != 0 {
continue // canceled, don't run it
}
go t.Cancel()
}
}
func (s *Scheduler) CancelOnServeTasks() {
cancelTasks(s.onServeTasks)
}
func (s *Scheduler) CancelOnInterruptTasks() {
cancelTasks(s.onInterruptTasks)
}
func runTaskNow(task *task, host TaskHost) {
proc := newTaskProcess(host)
task.proc = proc
task.Cancel = func() {
proc.canceledChan <- struct{}{}
}
go task.runner.Run(proc)
}
func runTasks(tasks []*task, host TaskHost) {
for _, t := range tasks {
if t.isCanceled() {
continue
}
runTaskNow(t, host)
}
}
func (s *Scheduler) runOnServe(host TaskHost) {
runTasks(s.onServeTasks, host)
}
func (s *Scheduler) runOnInterrupt(host TaskHost) {
runTasks(s.onInterruptTasks, host)
}
func (s *Scheduler) visit(visitor func(*task)) {
for _, t := range s.onServeTasks {
visitor(t)
}
for _, t := range s.onInterruptTasks {
visitor(t)
}
}
func (s *Scheduler) notifyShutdown() {
s.visit(func(t *task) {
go func() {
t.proc.Host().doneChan <- struct{}{}
}()
})
}
func (s *Scheduler) notifyErr(err error) {
s.visit(func(t *task) {
go func() {
t.proc.Host().errChan <- err
}()
})
}
func (s *Scheduler) CopyTo(to *Scheduler) {
s.visit(func(t *task) {
rnner := t.runner
to.Schedule(rnner)
})
}

View File

@@ -0,0 +1,83 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package host
import (
"context"
"fmt"
"log"
"net"
"net/http"
"os"
"time"
)
type myTestTask struct {
delay time.Duration
logger *log.Logger
}
func (m myTestTask) Run(proc TaskProcess) {
ticker := time.NewTicker(m.delay)
defer ticker.Stop()
rans := 0
for {
select {
case _, ok := <-ticker.C:
{
if !ok {
m.logger.Println("ticker issue, closed channel, exiting from this task...")
return
}
rans++
m.logger.Println(fmt.Sprintf("%d", rans))
}
case <-proc.Done():
{
m.logger.Println("canceled, exiting from task AND SHUTDOWN the server...")
proc.Host().Shutdown(context.TODO())
return
}
}
}
}
func SchedulerSchedule() {
h := New(&http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
}),
})
logger := log.New(os.Stdout, "Supervisor: ", 0)
delaySeconds := 2
mytask := myTestTask{
delay: time.Duration(delaySeconds) * time.Second,
logger: logger,
}
cancel := h.Schedule(mytask)
ln, err := net.Listen("tcp4", ":9090")
if err != nil {
panic(err.Error())
}
logger.Println("server started...")
logger.Println("we will cancel the task after 2 runs (the third will be canceled)")
cancelAfterRuns := 2
time.AfterFunc(time.Duration(delaySeconds*cancelAfterRuns+(delaySeconds/2))*time.Second, func() {
cancel()
logger.Println("cancel sent")
})
h.Serve(ln)
// Output:
// Supervisor: server started...
// Supervisor: we will cancel the task after 2 runs (the third will be canceled)
// Supervisor: 1
// Supervisor: 2
// Supervisor: cancel sent
// Supervisor: canceled, exiting from task AND SHUTDOWN the server...
}

227
core/host/supervisor.go Normal file
View File

@@ -0,0 +1,227 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package host
import (
"context"
"crypto/tls"
"net"
"net/http"
"os"
"os/signal"
"sync"
"sync/atomic"
"github.com/kataras/iris/core/errors"
"github.com/kataras/iris/core/nettools"
"golang.org/x/crypto/acme/autocert"
)
// Supervisor is the wrapper and the manager for a compatible server
// and it's relative actions, called Tasks.
//
// Interfaces are separated to return relative functionality to them.
type Supervisor struct {
Scheduler
server *http.Server
closedManually int32 // future use, accessed atomically (non-zero means we've called the Shutdown)
shouldWait int32 // non-zero means that the host should wait for unblocking
unblockChan chan struct{}
shutdownChan chan struct{}
errChan chan error
mu sync.Mutex
}
func New(srv *http.Server) *Supervisor {
return &Supervisor{
server: srv,
unblockChan: make(chan struct{}, 1),
shutdownChan: make(chan struct{}),
errChan: make(chan error),
}
}
func (su *Supervisor) DeferFlow() {
atomic.StoreInt32(&su.shouldWait, 1)
}
func (su *Supervisor) RestoreFlow() {
if su.isWaiting() {
atomic.StoreInt32(&su.shouldWait, 0)
su.mu.Lock()
su.unblockChan <- struct{}{}
su.mu.Unlock()
}
}
func (su *Supervisor) isWaiting() bool {
return atomic.LoadInt32(&su.shouldWait) != 0
}
// Done is being received when in server Shutdown.
// This can be used to gracefully shutdown connections that have
// undergone NPN/ALPN protocol upgrade or that have been hijacked.
// This function should start protocol-specific graceful shutdown,
// but should not wait for shutdown to complete.
func (su *Supervisor) Done() <-chan struct{} {
return su.shutdownChan
}
// Err refences to the return value of Server's .Serve, not the server's specific error logger.
func (su *Supervisor) Err() <-chan error {
return su.errChan
}
func (su *Supervisor) notifyShutdown() {
go func() {
su.shutdownChan <- struct{}{}
}()
su.Scheduler.notifyShutdown()
}
func (su *Supervisor) notifyErr(err error) {
// if err == http.ErrServerClosed {
// return
// }
go func() {
su.errChan <- err
}()
su.Scheduler.notifyErr(err)
}
/// TODO:
// Remove all channels, do it with events
// or with channels but with a different channel on each task proc
// I don't know channels are not so safe, when go func and race risk..
// so better with callbacks....
func (su *Supervisor) supervise(blockFunc func() error) error {
// println("Running Serve from Supervisor")
// su.server: in order to Serve and Shutdown the underline server and no re-run the supervisors when .Shutdown -> .Serve.
// su.GetBlocker: set the Block() and Unblock(), which are checked after a shutdown or error.
// su.GetNotifier: only one supervisor is allowed to be notified about Close/Shutdown and Err.
// su.log: set this builder's logger in order to supervisor to be able to share a common logger.
host := createTaskHost(su)
// run the list of supervisors in different go-tasks by-design.
su.Scheduler.runOnServe(host)
if len(su.Scheduler.onInterruptTasks) > 0 {
// this can't be moved to the task interrupt's `Run` function
// because it will not catch more than one ctrl/cmd+c, so
// we do it here. These tasks are canceled already too.
go func() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt, os.Kill)
select {
case <-ch:
su.Scheduler.runOnInterrupt(host)
}
}()
}
err := blockFunc()
su.notifyErr(err)
if su.isWaiting() {
blockStatement:
for {
select {
case <-su.unblockChan:
break blockStatement
}
}
}
return err // start the server
}
func (su *Supervisor) newListener() (net.Listener, error) {
// this will not work on "unix" as network
// because UNIX doesn't supports the kind of
// restarts we may want for the server.
//
// User still be able to call .Serve instead.
l, err := nettools.TCPKeepAlive(su.server.Addr)
if err != nil {
return nil, err
}
if nettools.IsTLS(su.server) {
// means tls
tlsl := tls.NewListener(l, su.server.TLSConfig)
return tlsl, nil
}
return l, nil
}
func (su *Supervisor) Serve(l net.Listener) error {
return su.supervise(func() error { return su.server.Serve(l) })
}
func (su *Supervisor) ListenAndServe() error {
l, err := su.newListener()
if err != nil {
return err
}
return su.Serve(l)
}
func setupHTTP2(cfg *tls.Config) {
cfg.NextProtos = append(cfg.NextProtos, "h2") // HTTP2
}
func (su *Supervisor) ListenAndServeTLS(certFile string, keyFile string) error {
if certFile == "" || keyFile == "" {
return errors.New("certFile or keyFile missing")
}
cfg := new(tls.Config)
var err error
cfg.Certificates = make([]tls.Certificate, 1)
if cfg.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile); err != nil {
return err
}
setupHTTP2(cfg)
su.server.TLSConfig = cfg
return su.ListenAndServe()
}
func (su *Supervisor) ListenAndServeAutoTLS() error {
autoTLSManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
}
cfg := new(tls.Config)
cfg.GetCertificate = autoTLSManager.GetCertificate
setupHTTP2(cfg)
su.server.TLSConfig = cfg
return su.ListenAndServe()
}
// Shutdown gracefully shuts down the server without interrupting any
// active connections. Shutdown works by first closing all open
// listeners, then closing all idle connections, and then waiting
// indefinitely for connections to return to idle and then shut down.
// If the provided context expires before the shutdown is complete,
// then the context's error is returned.
//
// Shutdown does not attempt to close nor wait for hijacked
// connections such as WebSockets. The caller of Shutdown should
// separately notify such long-lived connections of shutdown and wait
// for them to close, if desired.
func (su *Supervisor) Shutdown(ctx context.Context) error {
// println("Running Shutdown from Supervisor")
atomic.AddInt32(&su.closedManually, 1) // future-use
su.notifyShutdown()
return su.server.Shutdown(ctx)
}

View File

@@ -0,0 +1,112 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package host
import (
"bytes"
"context"
"crypto/tls"
"log"
"net"
"net/http"
"strings"
"testing"
"github.com/iris-contrib/httpexpect"
)
const (
debug = false
)
func newTester(t *testing.T, baseURL string, handler http.Handler) *httpexpect.Expect {
var transporter http.RoundTripper
if strings.HasPrefix(baseURL, "http") { // means we are testing real serve time
transporter = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
} else { // means we are testing the handler itself
transporter = httpexpect.NewBinder(handler)
}
testConfiguration := httpexpect.Config{
BaseURL: baseURL,
Client: &http.Client{
Transport: transporter,
Jar: httpexpect.NewJar(),
},
Reporter: httpexpect.NewAssertReporter(t),
}
if debug {
testConfiguration.Printers = []httpexpect.Printer{
httpexpect.NewDebugPrinter(t, true),
}
}
return httpexpect.WithConfig(testConfiguration)
}
func testSupervisor(t *testing.T, creator func(*http.Server, []TaskRunner) *Supervisor) {
loggerOutput := &bytes.Buffer{}
logger := log.New(loggerOutput, "", 0)
const (
expectedHelloMessage = "Hello\n"
)
// http routing
var (
expectedBody = "this is the response body\n"
)
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(expectedBody))
})
// host (server wrapper and adapter) construction
srv := &http.Server{Handler: mux, ErrorLog: logger}
addr := "localhost:5525"
// serving
ln, err := net.Listen("tcp4", addr)
if err != nil {
t.Fatal(err)
}
helloMe := TaskRunnerFunc(func(proc TaskProcess) {
logger.Print(expectedHelloMessage)
})
host := creator(srv, []TaskRunner{helloMe})
defer host.Shutdown(context.TODO())
go host.Serve(ln)
// http testsing and various calls
// no need for time sleep because the following will take some time by theirselves
tester := newTester(t, "http://"+addr, mux)
tester.Request("GET", "/").Expect().Status(http.StatusOK).Body().Equal(expectedBody)
// WARNING: Data Race here because we try to read the logs
// but it's "safe" here.
// testing Task (recorded) message:
if got := loggerOutput.String(); expectedHelloMessage != got {
t.Fatalf("expected hello Task's message to be '%s' but got '%s'", expectedHelloMessage, got)
}
}
func TestSupervisor(t *testing.T) {
testSupervisor(t, func(srv *http.Server, tasks []TaskRunner) *Supervisor {
su := New(srv)
for _, t := range tasks {
su.Schedule(t)
}
return su
})
}

131
core/host/task.go Normal file
View File

@@ -0,0 +1,131 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package host
// the 24hour name was "Supervisor" but it's not cover its usage
// 100%, best name is Task or Thead, I'll chouse Task.
// and re-name the host to "Supervisor" because that is the really
// supervisor.
import (
"context"
"github.com/kataras/iris/core/nettools"
"net/http"
"os"
)
type (
FlowController interface {
DeferFlow()
RestoreFlow()
}
)
type TaskHost struct {
su *Supervisor
// Supervisor with access fields when server is running, i.e restrict access to "Schedule"
// Server that running, is active and open
// Flow controller
FlowController
// Various
pid int
doneChan chan struct{}
errChan chan error
}
func (h TaskHost) Done() <-chan struct{} {
return h.doneChan
}
func (h TaskHost) Err() <-chan error {
return h.errChan
}
func (h TaskHost) Serve() error {
// the underline server's serve, using the "latest known" listener from the supervisor.
l, err := h.su.newListener()
if err != nil {
return err
}
// if http.serverclosed ignroe the error, it will have this error
// from the previous close
if err := h.su.server.Serve(l); err != http.ErrServerClosed {
return err
}
return nil
}
// HostURL returns the listening full url (scheme+host)
// based on the supervisor's server's address.
func (h TaskHost) HostURL() string {
return nettools.ResolveURLFromServer(h.su.server)
}
// Hostname returns the underline server's hostname.
func (h TaskHost) Hostname() string {
return nettools.ResolveHostname(h.su.server.Addr)
}
func (h TaskHost) Shutdown(ctx context.Context) error {
// the underline server's Shutdown (otherwise we will cancel all tasks and do cycles)
return h.su.server.Shutdown(ctx)
}
func (h TaskHost) PID() int {
return h.pid
}
type TaskProcess struct {
canceledChan chan struct{}
host TaskHost
}
func (p TaskProcess) Done() <-chan struct{} {
return p.canceledChan
}
func (p TaskProcess) Host() TaskHost {
return p.host
}
func createTaskHost(su *Supervisor) TaskHost {
host := TaskHost{
su: su,
FlowController: su,
doneChan: make(chan struct{}),
errChan: make(chan error),
pid: os.Getpid(),
}
return host
}
func newTaskProcess(host TaskHost) TaskProcess {
return TaskProcess{
host: host,
canceledChan: make(chan struct{}),
}
}
// A TaskRunner is an independent stream of instructions in a Supervisor.
// A routine is similar to a sequential program.
// However, a routine itself is not a program,
// it can't run on its own, instead it runs within a Supervisor's context.
//
// The real usage of a routine is not about a single sequential thread,
// but rather using multiple tasks in a single Supervisor.
// Multiple tasks running at the same time and performing various tasks is referred as Multithreading.
// A Task is considered to be a lightweight process because it runs within the context of a Supervisor
// and takes advantage of resources allocated for that Supervisor and its Server.
type TaskRunner interface {
Run(TaskProcess)
}
type TaskRunnerFunc func(TaskProcess)
func (s TaskRunnerFunc) Run(proc TaskProcess) {
s(proc)
}

23
core/host/task_banner.go Normal file
View File

@@ -0,0 +1,23 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package host
import (
"fmt"
"io"
"runtime"
)
func WriteBannerTask(w io.Writer, banner string) TaskRunnerFunc {
return func(proc TaskProcess) {
listeningURI := proc.Host().HostURL()
interruptkey := "CTRL"
if runtime.GOOS == "darwin" {
interruptkey = "CMD"
}
w.Write([]byte(fmt.Sprintf("%s\n\nNow listening on: %s\nApplication started. Press %s+C to shut down.\n",
banner, listeningURI, interruptkey)))
}
}

View File

@@ -0,0 +1,47 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package host
import (
"context"
"fmt"
"net/http"
"time"
)
func TaskHostError() {
su := New(&http.Server{Addr: ":8273", Handler: http.DefaultServeMux})
su.ScheduleFunc(func(proc TaskProcess) {
select {
case err := <-proc.Host().Err():
fmt.Println(err.Error())
}
})
su.ScheduleFunc(func(proc TaskProcess) {
select {
case err := <-proc.Host().Err():
fmt.Println(err.Error())
}
})
su.ScheduleFunc(func(proc TaskProcess) {
select {
case err := <-proc.Host().Err():
fmt.Println(err.Error())
}
})
go su.ListenAndServe()
time.Sleep(1 * time.Second)
su.Shutdown(context.TODO())
time.Sleep(1 * time.Second)
// Output:
// http: Server closed
// http: Server closed
// http: Server closed
}

View File

@@ -0,0 +1,47 @@
// +build !linux
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package host
import (
"context"
"time"
"github.com/kataras/iris/core/gui"
)
// ShowTrayTask is a supervisor's built'n task which shows
// the iris tray icon to the taskbar (cross-platform).
//
// It's responsible for the server's status button.
func ShowTrayTask(version string, shutdownTimeout time.Duration) TaskRunnerFunc {
return func(proc TaskProcess) {
t := gui.Tray
// set the label "Version" to the framework's current Version.
t.SetVersion(version)
// active the status button(online/offline).
t.OnServerStatusChange(
// set the first callback (pressed when unchecked).
func() {
go proc.Host().Serve()
},
// set the second call back (pressed when checked, default status with its label setted to :"Offline".
func() {
// when server is shutting down it will send an "http closed" error ,
// that DeferFlow stops from returning that error and exiting the app
// postpone the execution flow, the interrupt signal will restore the flow
// when ctrl/cmd+C pressed.
proc.Host().DeferFlow()
ctx, cancel := context.WithTimeout(context.TODO(), shutdownTimeout)
defer cancel()
proc.Host().Shutdown(ctx)
})
// render the tray icon and block this scheduled task(goroutine.
t.Show()
}
}

View File

@@ -0,0 +1,28 @@
// +build linux
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package host
import (
"os"
"time"
)
// ShowTrayTask is a supervisor's built'n task which shows
// the iris tray icon to the taskbar (cross-platform).
//
// It's responsible for the server's status button.
func ShowTrayTask(version string, shutdownTimeout time.Duration) TaskRunnerFunc {
return func(proc TaskProcess) {
os.Stdout.WriteString("Tray icon is not enabled by-default for linux systems,\nyou have to install a dependency first and re-get the Iris pgk:\n")
os.Stdout.WriteString("$ sudo apt-get install libgtk-3-dev libappindicator3-dev\n")
os.Stdout.WriteString("$ go get -u github.com/kataras/iris\n")
// manually:
// os.Stdout.WriteString("remove $GOPATH/src/github.com/kataras/iris/core/host/task_gui_tray_linux.go\n")
// os.Stdout.WriteString("edit $GOPATH/src/github.com/kataras/iris/core/host/task_gui_tray.go and remove the // +build !linux\n")
// os.Stdout.WriteString("edit $GOPATH/src/github.com/kataras/iris/core/gui/tray.go and remove the // +build !linux\n")
}
}

View File

@@ -0,0 +1,29 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package host
import (
"context"
"time"
)
// OnInterrupt is a built'n supervisor task type which fires its
// value(Task) when an OS interrupt/kill signal received.
type OnInterrupt TaskRunnerFunc
func (t OnInterrupt) Run(proc TaskProcess) {
t(proc)
}
// ShutdownOnInterruptTask returns a supervisor's built'n task which
// shutdowns the server when InterruptSignalTask fire this task.
func ShutdownOnInterruptTask(shutdownTimeout time.Duration) TaskRunner {
return OnInterrupt(func(proc TaskProcess) {
ctx, cancel := context.WithTimeout(context.TODO(), shutdownTimeout)
defer cancel()
proc.Host().Shutdown(ctx)
proc.Host().RestoreFlow()
})
}

47
core/logger/dev.go Normal file
View File

@@ -0,0 +1,47 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package logger
import (
"fmt"
"io"
"strings"
"sync"
"time"
)
func NewDevLogger(omitTimeFor ...string) io.Writer {
mu := &sync.Mutex{} // for now and last log
lastLog := time.Now()
distanceDuration := 850 * time.Millisecond
return writerFunc(func(p []byte) (int, error) {
logMessage := string(p)
for _, s := range omitTimeFor {
if strings.Contains(logMessage, s) {
n, err := fmt.Print(logMessage)
lastLog = time.Now()
return n, err
}
}
mu.Lock()
defer mu.Unlock() // "slow" but we don't care here.
nowLog := time.Now()
if nowLog.Before(lastLog.Add(distanceDuration)) {
// don't use the log.Logger to print this message
// if the last one was printed before some seconds.
n, err := fmt.Println(logMessage) // fmt because we don't want the time, dev is dev so console.
lastLog = nowLog
return n, err
}
// begin with new line in order to have the time once at the top
// and the child logs below it.
n, err := fmt.Printf("%s \u2192\n%s\n", nowLog.Format("01/02/2006 03:04:05"), logMessage)
lastLog = nowLog
return n, err
})
}

50
core/logger/logger.go Normal file
View File

@@ -0,0 +1,50 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package logger
import (
"fmt"
"io"
)
// writerFunc is just an "extended" io.Writer which provides
// some "methods" which can help applications
// to adapt their existing loggers inside Iris.
type writerFunc func(p []byte) (n int, err error)
func (w writerFunc) Write(p []byte) (int, error) {
return w(p)
}
type formatPrintWriter interface {
Printf(string, ...interface{})
}
type stringWriter interface {
WriteString(string) (int, error)
}
// Log sends a message to the defined io.Writer logger, it's
// just a help function for internal use but it can be used to a cusotom middleware too.
//
// See AttachLogger too.
func Log(w io.Writer, format string, a ...interface{}) {
// check if the user's defined logger is one of the "high"
// level printers, if yes then use their functions to print instead
// of allocating new byte slices.
if fpw, ok := w.(formatPrintWriter); ok {
fpw.Printf(format, a...)
return
}
formattedMessage := fmt.Sprintf(format, a...)
if sWriter, ok := w.(stringWriter); ok {
sWriter.WriteString(formattedMessage)
return
}
w.Write([]byte(formattedMessage))
}

7
core/logger/noop.go Normal file
View File

@@ -0,0 +1,7 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package logger
var NoOpLogger = writerFunc(func([]byte) (int, error) { return -1, nil })

205
core/nettools/addr.go Normal file
View File

@@ -0,0 +1,205 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package nettools
import (
"os"
"regexp"
"strconv"
"strings"
)
var (
loopbackRegex *regexp.Regexp
loopbackSubRegex *regexp.Regexp
machineHostname string
)
func init() {
loopbackRegex, _ = regexp.Compile(`^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$`)
loopbackSubRegex, _ = regexp.Compile(`^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$`)
machineHostname, _ = os.Hostname()
}
// IsLoopbackSubdomain checks if a string is a subdomain or a hostname.
var IsLoopbackSubdomain = func(s string) bool {
if strings.HasPrefix(s, "127.0.0.1:") || s == "127.0.0.1" {
return true
}
valid := loopbackSubRegex.MatchString(s)
if !valid { // if regex failed to match it, then try with the pc's name.
if !strings.Contains(machineHostname, ".") { // if machine name's is not a loopback by itself
valid = s == machineHostname
}
}
return valid
}
// IsLoopbackHost tries to catch the local addresses when a developer
// navigates to a subdomain that its hostname differs from Application.Config.Addr.
// Developer may want to override this function to return always false
// in order to not allow different hostname from Application.Config.Addr in local environment (remote is not reached).
var IsLoopbackHost = func(requestHost string) bool {
// this func will be called if we have a subdomain actually, not otherwise, so we are
// safe to do some hacks.
// if subdomain.127.0.0.1:8080/path, we need to compare the 127.0.0.1
// if subdomain.localhost:8080/mypath, we need to compare the localhost
// if subdomain.127.0.0.1/mypath, we need to compare the 127.0.0.1
// if subdomain.127.0.0.1, we need to compare the 127.0.0.1
// find the first index of [:]8080 or [/]mypath or nothing(root with loopback address like 127.0.0.1)
// remember: we are not looking for .com or these things, if is up and running then the developer
// would probably not want to reach the server with different Application.Config.Addr than
// he/she declared.
portOrPathIdx := strings.LastIndexByte(requestHost, ':')
// this will not catch ipv6 loopbacks like subdomain.0000:0:0000::01.1:8080
// but, again, is for developers only, is hard to try to navigate with something like this,
// and if that happened, I provide a way to override the whole "algorithm" to a custom one via "IsLoopbackHost".
if portOrPathIdx == -1 {
portOrPathIdx = strings.LastIndexByte(requestHost, '/')
if portOrPathIdx == -1 {
portOrPathIdx = len(requestHost) // if not port or / then it should be something like subodmain.127.0.0.1
}
}
// remove the left part of subdomain[.]<- and the right part of ->[:]8080/[/]mypath
// so result should be 127.0.0.1/localhost/0.0.0.0 or any ip
subdomainFinishIdx := strings.IndexByte(requestHost, '.') + 1
if l := len(requestHost); l <= subdomainFinishIdx || l < portOrPathIdx {
return false // for any case to not panic here.
}
hostname := requestHost[subdomainFinishIdx:portOrPathIdx]
if hostname == "" {
return false
}
// we use regex here to catch all posibilities, we compiled the regex at init func
// so it shouldn't hurt so much, but we don't care a lot because it's a special case here
// because this function will be called only if developer him/herself can reach the server
// with a loopback/local address, so we are totally safe.
valid := loopbackRegex.MatchString(hostname)
if !valid { // if regex failed to match it, then try with the pc's name.
valid = hostname == machineHostname
}
return valid
}
const (
// defaultServerHostname returns the default hostname which is "localhost"
defaultServerHostname = "localhost"
// defaultServerPort returns the default port which is 8080, not used
defaultServerPort = 8080
)
var (
// defaultServerAddr the default server addr which is: localhost:8080
defaultServerAddr = defaultServerHostname + ":" + strconv.Itoa(defaultServerPort)
)
// ResolveAddr tries to convert a given string to an address which is compatible with net.Listener and server
func ResolveAddr(addr string) string {
// check if addr has :port, if not do it +:80 ,we need the hostname for many cases
a := addr
if a == "" {
// check for os environments
if oshost := os.Getenv("ADDR"); oshost != "" {
a = oshost
} else if oshost := os.Getenv("HOST"); oshost != "" {
a = oshost
} else if oshost := os.Getenv("HOSTNAME"); oshost != "" {
a = oshost
// check for port also here
if osport := os.Getenv("PORT"); osport != "" {
a += ":" + osport
}
} else if osport := os.Getenv("PORT"); osport != "" {
a = ":" + osport
} else {
a = ":http"
}
}
if portIdx := strings.IndexByte(a, ':'); portIdx == 0 {
if a[portIdx:] == ":https" {
a = defaultServerHostname + ":443"
} else {
// if contains only :port ,then the : is the first letter, so we dont have setted a hostname, lets set it
a = defaultServerHostname + a
}
}
return a
}
// ResolveHostname receives an addr of form host[:port] and returns the hostname part of it
// ex: localhost:8080 will return the `localhost`, mydomain.com:8080 will return the 'mydomain'
func ResolveHostname(addr string) string {
if idx := strings.IndexByte(addr, ':'); idx == 0 {
// only port, then return the localhost hostname
return "localhost"
} else if idx > 0 {
return addr[0:idx]
}
// it's already hostname
return addr
}
// ResolveVHost tries to get the hostname if port is no needed for Addr's usage.
// Addr is being used inside router->subdomains
// and inside {{ url }} template funcs.
// It should be the same as "browser's"
// usually they removing :80 or :443.
func ResolveVHost(addr string) string {
if addr == ":https" || addr == ":http" {
return "localhost"
}
if idx := strings.IndexByte(addr, ':'); idx == 0 {
// only port, then return the localhost hostname
return "localhost" + addr[idx:]
}
// with ':' in order to not replace the ipv6 loopback addresses
addr = strings.Replace(addr, "0.0.0.0:", "localhost:", 1)
port := ResolvePort(addr)
if port == 80 || port == 443 {
return ResolveHostname(addr)
}
return addr
}
const (
// SchemeHTTPS the "https" url scheme.
SchemeHTTPS = "https"
// SchemeHTTP the "http" url scheme.
SchemeHTTP = "http"
)
// ResolvePort receives an addr of form host[:port] and returns the port part of it
// ex: localhost:8080 will return the `8080`, mydomain.com will return the '80'
func ResolvePort(addr string) int {
if portIdx := strings.IndexByte(addr, ':'); portIdx != -1 {
afP := addr[portIdx+1:]
p, err := strconv.Atoi(afP)
if err == nil {
return p
} else if afP == SchemeHTTPS { // it's not number, check if it's :https
return 443
}
}
return 80
}
// ResolveScheme returns the scheme based on the "vhost"
func ResolveScheme(vhost string) string {
// pure check
if strings.HasPrefix(vhost, SchemeHTTPS) || ResolvePort(vhost) == 443 {
return SchemeHTTPS
}
return SchemeHTTP
}

View File

@@ -0,0 +1,47 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package nettools
import (
"testing"
)
func TestIsLoopbackHost(t *testing.T) {
tests := []struct {
host string
valid bool
}{
{"subdomain.127.0.0.1:8080", true},
{"subdomain.127.0.0.1", true},
{"subdomain.localhost:8080", true},
{"subdomain.localhost", true},
{"subdomain.127.0000.0000.1:8080", true},
{"subdomain.127.0000.0000.1", true},
{"subdomain.127.255.255.254:8080", true},
{"subdomain.127.255.255.254", true},
{"subdomain.0000:0:0000::01.1:8080", false},
{"subdomain.0000:0:0000::01", false},
{"subdomain.0000:0:0000::01.1:8080", false},
{"subdomain.0000:0:0000::01", false},
{"subdomain.0000:0000:0000:0000:0000:0000:0000:0001:8080", true},
{"subdomain.0000:0000:0000:0000:0000:0000:0000:0001", false},
{"subdomain.example:8080", false},
{"subdomain.example", false},
{"subdomain.example.com:8080", false},
{"subdomain.example.com", false},
{"subdomain.com", false},
{"subdomain", false},
{".subdomain", false},
{"127.0.0.1.com", false},
}
for i, tt := range tests {
if expected, got := tt.valid, IsLoopbackHost(tt.host); expected != got {
t.Fatalf("[%d] expected %t but got %t for %s", i, expected, got, tt.host)
}
}
}

41
core/nettools/server.go Normal file
View File

@@ -0,0 +1,41 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package nettools
import (
"net/http"
)
// used on host/supervisor/task and router/path
// IsTLS returns true if the "srv" contains any certificates
// or a get certificate function, meaning that is secure.
func IsTLS(srv *http.Server) bool {
if cfg := srv.TLSConfig; cfg != nil &&
(len(cfg.Certificates) > 0 || cfg.GetCertificate != nil) {
return true
}
return false
}
// ResolveSchemeFromServer tries to resolve a url scheme
// based on the server's configuration.
// Returns "https" on secure server,
// otherwise "http".
func ResolveSchemeFromServer(srv *http.Server) string {
if IsTLS(srv) {
return SchemeHTTPS
}
return SchemeHTTP
}
// ResolveURLFromServer returns the scheme+host from a server.
func ResolveURLFromServer(srv *http.Server) string {
scheme := ResolveSchemeFromServer(srv)
host := ResolveVHost(srv.Addr)
return scheme + "://" + host
}

163
core/nettools/tcp.go Normal file
View File

@@ -0,0 +1,163 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package nettools
import (
"crypto/tls"
"net"
"os"
"strings"
"time"
"github.com/kataras/iris/core/errors"
"golang.org/x/crypto/acme/autocert"
)
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by Run, ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
//
// A raw copy of standar library.
type tcpKeepAliveListener struct {
*net.TCPListener
}
// Accept accepts tcp connections aka clients.
func (l tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := l.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}
var (
errPortAlreadyUsed = errors.New("port is already used")
errRemoveUnix = errors.New("unexpected error when trying to remove unix socket file. Addr: %s | Trace: %s")
errChmod = errors.New("cannot chmod %#o for %q: %s")
errCertKeyMissing = errors.New("you should provide certFile and keyFile for TLS/SSL")
errParseTLS = errors.New("couldn't load TLS, certFile=%q, keyFile=%q. Trace: %s")
)
// TCP returns a new tcp(ipv6 if supported by network) and an error on failure.
func TCP(addr string) (net.Listener, error) {
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
return l, nil
}
// TCPKeepAlive returns a new tcp keep alive Listener and an error on failure.
func TCPKeepAlive(addr string) (ln net.Listener, err error) {
// if strings.HasPrefix(addr, "127.0.0.1") {
// // it's ipv4, use ipv4 tcp listener instead of the default ipv6. Don't.
// ln, err = net.Listen("tcp4", addr)
// } else {
// ln, err = TCP(addr)
// }
ln, err = TCP(addr)
if err != nil {
return nil, err
}
return tcpKeepAliveListener{ln.(*net.TCPListener)}, nil
}
// UNIX returns a new unix(file) Listener.
func UNIX(socketFile string, mode os.FileMode) (net.Listener, error) {
if errOs := os.Remove(socketFile); errOs != nil && !os.IsNotExist(errOs) {
return nil, errRemoveUnix.Format(socketFile, errOs.Error())
}
l, err := net.Listen("unix", socketFile)
if err != nil {
return nil, errPortAlreadyUsed.AppendErr(err)
}
if err = os.Chmod(socketFile, mode); err != nil {
return nil, errChmod.Format(mode, socketFile, err.Error())
}
return l, nil
}
// TLS returns a new TLS Listener and an error on failure.
func TLS(addr, certFile, keyFile string) (net.Listener, error) {
if certFile == "" || keyFile == "" {
return nil, errCertKeyMissing
}
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, errParseTLS.Format(certFile, keyFile, err)
}
return CERT(addr, cert)
}
// CERT returns a listener which contans tls.Config with the provided certificate, use for ssl.
func CERT(addr string, cert tls.Certificate) (net.Listener, error) {
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
PreferServerCipherSuites: true,
}
return tls.NewListener(l, tlsConfig), nil
}
// LETSENCRYPT returns a new Automatic TLS Listener using letsencrypt.org service
// receives three parameters,
// the first is the host of the server,
// second can be the server name(domain) or empty if skip verification is the expected behavior (not recommended)
// and the third is optionally, the cache directory, if you skip it then the cache directory is "./certcache"
// if you want to disable cache directory then simple give it a value of empty string ""
//
// does NOT supports localhost domains for testing.
//
// this is the recommended function to use when you're ready for production state.
func LETSENCRYPT(addr string, serverName string, cacheDirOptional ...string) (net.Listener, error) {
if portIdx := strings.IndexByte(addr, ':'); portIdx == -1 {
addr += ":443"
}
l, err := TCP(addr)
if err != nil {
return nil, err
}
cacheDir := "./certcache"
if len(cacheDirOptional) > 0 {
cacheDir = cacheDirOptional[0]
}
m := autocert.Manager{
Prompt: autocert.AcceptTOS,
} // HostPolicy is missing, if user wants it, then she/he should manually
if cacheDir == "" {
// then the user passed empty by own will, then I guess she/he doesnt' want any cache directory
} else {
m.Cache = autocert.DirCache(cacheDir)
}
tlsConfig := &tls.Config{GetCertificate: m.GetCertificate}
// use InsecureSkipVerify or ServerName to a value
if serverName == "" {
// if server name is invalid then bypass it
tlsConfig.InsecureSkipVerify = true
} else {
tlsConfig.ServerName = serverName
}
return tls.NewListener(l, tlsConfig), nil
}

659
core/router/api_builder.go Normal file
View File

@@ -0,0 +1,659 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package router
import (
"net/http"
"os"
"path"
"strings"
"time"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
"github.com/kataras/iris/core/router/macro"
)
const (
// MethodNone is a Virtual method
// to store the "offline" routes
MethodNone = "NONE"
)
var (
// AllMethods contains the valid http methods:
// "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD",
// "PATCH", "OPTIONS", "TRACE".
AllMethods = [...]string{
"GET",
"POST",
"PUT",
"DELETE",
"CONNECT",
"HEAD",
"PATCH",
"OPTIONS",
"TRACE",
}
)
// repository passed to all parties(subrouters), it's the object witch keeps
// all the routes.
type repository struct {
routes []*Route
}
func (r *repository) register(route *Route) {
r.routes = append(r.routes, route)
}
func (r *repository) get(routeName string) *Route {
for _, r := range r.routes {
if r.Name == routeName {
return r
}
}
return nil
}
func (r *repository) getAll() []*Route {
return r.routes
}
type RoutesProvider interface { // api builder
GetRoutes() []*Route
GetRoute(routeName string) *Route
}
// APIBuilder the visible API for constructing the router
// and child routers.
type APIBuilder struct {
// the api builder global macros registry
macros *macro.MacroMap
// the api builder global handlers per status code registry (used for custom http errors)
errorCodeHandlers *ErrorCodeHandlers
// the api builder global routes repository
routes *repository
// the api builder global route path reverser object
// used by the view engine but it can be used anywhere.
reverser *RoutePathReverser
// the per-party middleware
middleware context.Handlers
// the per-party routes (useful only for done middleware)
apiRoutes []*Route
// the per-party done middleware
doneHandlers context.Handlers
// the per-party
relativePath string
}
var _ Party = &APIBuilder{}
var _ RoutesProvider = &APIBuilder{} // passed to the default request handler (routerHandler)
// NewAPIBuilder creates & returns a new builder
// which is responsible to build the API and the router handler.
func NewAPIBuilder() *APIBuilder {
rb := &APIBuilder{
macros: defaultMacros(),
errorCodeHandlers: defaultErrorCodeHandlers(),
relativePath: "/",
routes: new(repository),
}
return rb
}
// Handle registers a route to the server's rb.
// if empty method is passed then handler(s) are being registered to all methods, same as .Any.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Handle(method string, registeredPath string, handlers ...context.Handler) (*Route, error) {
// if registeredPath[0] != '/' {
// return nil, errors.New("path should start with slash and should not be empty")
// }
if method == "" || method == "ALL" || method == "ANY" { // then use like it was .Any
return nil, rb.Any(registeredPath, handlers...)
}
// no clean path yet because of subdomain indicator/separator which contains a dot.
// but remove the first slash if the relative has already ending with a slash
// it's not needed because later on we do normalize/clean the path, but better do it here too
// for any future updates.
if rb.relativePath[len(rb.relativePath)-1] == '/' {
if registeredPath[0] == '/' {
registeredPath = registeredPath[1:]
}
}
fullpath := rb.relativePath + registeredPath // for now, keep the last "/" if any, "/xyz/"
routeHandlers := joinHandlers(rb.middleware, handlers)
// here we separate the subdomain and relative path
subdomain, path := exctractSubdomain(fullpath)
if len(rb.doneHandlers) > 0 {
routeHandlers = append(routeHandlers, rb.doneHandlers...) // register the done middleware, if any
}
r, err := NewRoute(method, subdomain, path, routeHandlers, rb.macros)
if err != nil {
return nil, err
}
// global
rb.routes.register(r)
// per -party
rb.apiRoutes = append(rb.apiRoutes, r)
// should we remove the rb.apiRoutes on the .Party (new children party) ?, No, because the user maybe use this party later
// should we add to the 'inheritance tree' the rb.apiRoutes, No, these are for this specific party only, because the user propably, will have unexpected behavior when using Use/Use, Done/DoneFunc
return r, nil
}
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
// Party could also be named as 'Join' or 'Node' or 'Group' , Party chosen because it is fun.
func (rb *APIBuilder) Party(relativePath string, handlers ...context.Handler) Party {
parentPath := rb.relativePath
dot := string(SubdomainIndicator[0])
if len(parentPath) > 0 && parentPath[0] == '/' && strings.HasSuffix(relativePath, dot) { // if ends with . , example: admin., it's subdomain->
parentPath = parentPath[1:] // remove first slash
}
fullpath := parentPath + relativePath
// append the parent's +child's handlers
middleware := joinHandlers(rb.middleware, handlers)
return &APIBuilder{
// global/api builder
macros: rb.macros,
routes: rb.routes,
errorCodeHandlers: rb.errorCodeHandlers,
doneHandlers: rb.doneHandlers,
// per-party/children
middleware: middleware,
relativePath: fullpath,
}
}
func (rb *APIBuilder) Macros() *macro.MacroMap {
return rb.macros
}
// GetRoutes returns the routes information,
// some of them can be changed at runtime some others not.
//
// Needs refresh of the router to Method or Path or Handlers changes to take place.
func (rb *APIBuilder) GetRoutes() []*Route {
return rb.routes.getAll()
}
// GetRoute returns the registered route based on its name, otherwise nil.
// One note: "routeName" should be case-sensitive.
func (rb *APIBuilder) GetRoute(routeName string) *Route {
return rb.routes.get(routeName)
}
// Use appends Handler(s) to the current Party's routes and child routes.
// If the current Party is the root, then it registers the middleware to all child Parties' routes too.
func (rb *APIBuilder) Use(handlers ...context.Handler) {
rb.middleware = append(rb.middleware, handlers...)
}
// Done appends to the very end, Handler(s) to the current Party's routes and child routes
// The difference from .Use is that this/or these Handler(s) are being always running last.
func (rb *APIBuilder) Done(handlers ...context.Handler) {
if len(rb.apiRoutes) > 0 { // register these middleware on previous-party-defined routes, it called after the party's route methods (Handle/HandleFunc/Get/Post/Put/Delete/...)
for i, n := 0, len(rb.apiRoutes); i < n; i++ {
routeInfo := rb.apiRoutes[i]
routeInfo.Handlers = append(routeInfo.Handlers, handlers...)
}
} else {
// register them on the doneHandlers, which will be used on Handle to append these middlweare as the last handler(s)
rb.doneHandlers = append(rb.doneHandlers, handlers...)
}
}
// UseGlobal registers Handler middleware to the beginning, prepends them instead of append
//
// Use it when you want to add a global middleware to all parties, to all routes in all subdomains
// It should be called right before Listen functions
func (rb *APIBuilder) UseGlobal(handlers ...context.Handler) {
for _, r := range rb.routes.routes {
r.Handlers = append(handlers, r.Handlers...) // prepend the handlers
}
rb.middleware = append(handlers, rb.middleware...) // set as middleware on the next routes too
// rb.Use(handlers...)
}
// None registers an "offline" route
// see context.ExecRoute(routeName) and
// party.Routes().Online(handleResultRouteInfo, "GET") and
// Offline(handleResultRouteInfo)
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) None(path string, handlers ...context.Handler) (*Route, error) {
return rb.Handle(MethodNone, path, handlers...)
}
// Get registers a route for the Get http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Get(path string, handlers ...context.Handler) (*Route, error) {
return rb.Handle(http.MethodGet, path, handlers...)
}
// Post registers a route for the Post http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Post(path string, handlers ...context.Handler) (*Route, error) {
return rb.Handle(http.MethodPost, path, handlers...)
}
// Put registers a route for the Put http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Put(path string, handlers ...context.Handler) (*Route, error) {
return rb.Handle(http.MethodPut, path, handlers...)
}
// Delete registers a route for the Delete http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Delete(path string, handlers ...context.Handler) (*Route, error) {
return rb.Handle(http.MethodDelete, path, handlers...)
}
// Connect registers a route for the Connect http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Connect(path string, handlers ...context.Handler) (*Route, error) {
return rb.Handle(http.MethodConnect, path, handlers...)
}
// Head registers a route for the Head http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Head(path string, handlers ...context.Handler) (*Route, error) {
return rb.Handle(http.MethodHead, path, handlers...)
}
// Options registers a route for the Options http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Options(path string, handlers ...context.Handler) (*Route, error) {
return rb.Handle(http.MethodOptions, path, handlers...)
}
// Patch registers a route for the Patch http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Patch(path string, handlers ...context.Handler) (*Route, error) {
return rb.Handle(http.MethodPatch, path, handlers...)
}
// Trace registers a route for the Trace http method.
//
// Returns a *Route and an error which will be filled if route wasn't registered successfully.
func (rb *APIBuilder) Trace(path string, handlers ...context.Handler) (*Route, error) {
return rb.Handle(http.MethodTrace, path, handlers...)
}
// Any registers a route for ALL of the http methods
// (Get,Post,Put,Head,Patch,Options,Connect,Delete).
func (rb *APIBuilder) Any(registeredPath string, handlers ...context.Handler) error {
for _, k := range AllMethods {
if _, err := rb.Handle(k, registeredPath, handlers...); err != nil {
return err
}
}
return nil
}
// StaticCacheDuration expiration duration for INACTIVE file handlers, it's the only one global configuration
// which can be changed.
var StaticCacheDuration = 20 * time.Second
const (
lastModifiedHeaderKey = "Last-Modified"
ifModifiedSinceHeaderKey = "If-Modified-Since"
contentDispositionHeaderKey = "Content-Disposition"
cacheControlHeaderKey = "Cache-Control"
contentEncodingHeaderKey = "Content-Encoding"
acceptEncodingHeaderKey = "Accept-Encoding"
// contentLengthHeaderKey represents the header["Content-Length"]
contentLengthHeaderKey = "Content-Length"
contentTypeHeaderKey = "Content-Type"
varyHeaderKey = "Vary"
)
func (rb *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) (*Route, error) {
if _, err := rb.Head(reqPath, h); err != nil {
return nil, err
}
return rb.Get(reqPath, h)
}
// StaticHandler returns a new Handler which is ready
// to serve all kind of static files.
//
// Note:
// The only difference from package-level `StaticHandler`
// is that this `StaticHandler`` receives a request path which
// is appended to the party's relative path and stripped here,
// so `iris.StripPath` is useless and should not being used here.
//
// Usage:
// app := iris.New()
// ...
// mySubdomainFsServer := app.Party("mysubdomain.")
// h := mySubdomainFsServer.StaticHandler("/static", "./static_files", false, false)
// /* http://mysubdomain.mydomain.com/static/css/style.css */
// mySubdomainFsServer.Get("/static", h)
// ...
//
func (rb *APIBuilder) StaticHandler(reqPath string, systemPath string, showList bool, enableGzip bool, exceptRoutes ...*Route) context.Handler {
return StripPrefix(rb.relativePath+reqPath,
StaticHandler(systemPath, showList, enableGzip))
}
// StaticServe serves a directory as web resource
// it's the simpliest form of the Static* functions
// Almost same usage as StaticWeb
// accepts only one required parameter which is the systemPath,
// the same path will be used to register the GET and HEAD method routes.
// If second parameter is empty, otherwise the requestPath is the second parameter
// it uses gzip compression (compression on each request, no file cache).
//
// Returns the GET *Route.
func (rb *APIBuilder) StaticServe(systemPath string, requestPath ...string) (*Route, error) {
var reqPath string
if len(requestPath) == 0 {
reqPath = strings.Replace(systemPath, string(os.PathSeparator), "/", -1) // replaces any \ to /
reqPath = strings.Replace(reqPath, "//", "/", -1) // for any case, replaces // to /
reqPath = strings.Replace(reqPath, ".", "", -1) // replace any dots (./mypath -> /mypath)
} else {
reqPath = requestPath[0]
}
return rb.Get(joinPath(reqPath, WildcardParam("file")), func(ctx context.Context) {
filepath := ctx.Params().Get("file")
spath := strings.Replace(filepath, "/", string(os.PathSeparator), -1)
spath = path.Join(systemPath, spath)
if !DirectoryExists(spath) {
ctx.NotFound()
return
}
if err := ctx.ServeFile(spath, true); err != nil {
ctx.StatusCode(http.StatusInternalServerError)
}
})
}
// StaticContent registers a GET and HEAD method routes to the requestPath
// that are ready to serve raw static bytes, memory cached.
//
// Returns the GET *Route.
func (rb *APIBuilder) StaticContent(reqPath string, cType string, content []byte) (*Route, error) {
modtime := time.Now()
h := func(ctx context.Context) {
if err := ctx.WriteWithExpiration(http.StatusOK, content, cType, modtime); err != nil {
ctx.Application().Log("error while serving []byte via StaticContent: %s", err.Error())
}
}
return rb.registerResourceRoute(reqPath, h)
}
// StaticEmbedded used when files are distributed inside the app executable, using go-bindata mostly
// First parameter is the request path, the path which the files in the vdir will be served to, for example "/static"
// Second parameter is the (virtual) directory path, for example "./assets"
// Third parameter is the Asset function
// Forth parameter is the AssetNames function.
//
// Returns the GET *Route.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/intermediate/serve-embedded-files
func (rb *APIBuilder) StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) (*Route, error) {
paramName := "path"
requestPath = joinPath(requestPath, WildcardParam(paramName))
if len(vdir) > 0 {
if vdir[0] == '.' { // first check for .wrong
vdir = vdir[1:]
}
if vdir[0] == '/' || vdir[0] == os.PathSeparator { // second check for /something, (or ./something if we had dot on 0 it will be removed
vdir = vdir[1:]
}
}
// collect the names we are care for, because not all Asset used here, we need the vdir's assets.
allNames := namesFn()
var names []string
for _, path := range allNames {
// check if path is the path name we care for
if !strings.HasPrefix(path, vdir) {
continue
}
names = append(names, cleanPath(path))
// path = strings.Replace(path, "\\", "/", -1) // replace system paths with double slashes
// path = strings.Replace(path, "./", "/", -1) // replace ./assets/favicon.ico to /assets/favicon.ico in order to be ready for compare with the reqPath later
// path = path[len(vdir):] // set it as the its 'relative' ( we should re-setted it when assetFn will be used)
// names = append(names, path)
}
if len(names) == 0 {
return nil, errors.New("unable to locate any embedded files located to the (virtual) directory: " + vdir)
}
modtime := time.Now()
h := func(ctx context.Context) {
reqPath := ctx.Params().Get(paramName)
for _, path := range names {
// in order to map "/" as "/index.html"
// as requested here: https://github.com/kataras/iris/issues/633#issuecomment-281691851
if path == "/index.html" {
if reqPath[len(reqPath)-1] == '/' {
reqPath = "/index.html"
}
}
if path != reqPath {
continue
}
cType := TypeByExtension(path)
fullpath := vdir + path
buf, err := assetFn(fullpath)
if err != nil {
continue
}
if err := ctx.WriteWithExpiration(http.StatusOK, buf, cType, modtime); err != nil {
ctx.StatusCode(http.StatusInternalServerError)
ctx.StopExecution()
}
return
}
// not found or error
ctx.NotFound()
}
return rb.registerResourceRoute(requestPath, h)
}
// errDirectoryFileNotFound returns an error with message: 'Directory or file %s couldn't found. Trace: +error trace'
var errDirectoryFileNotFound = errors.New("Directory or file %s couldn't found. Trace: %s")
// Favicon serves static favicon
// accepts 2 parameters, second is optional
// favPath (string), declare the system directory path of the __.ico
// requestPath (string), it's the route's path, by default this is the "/favicon.ico" because some browsers tries to get this by default first,
// you can declare your own path if you have more than one favicon (desktop, mobile and so on)
//
// this func will add a route for you which will static serve the /yuorpath/yourfile.ico to the /yourfile.ico
// (nothing special that you can't handle by yourself).
// Note that you have to call it on every favicon you have to serve automatically (desktop, mobile and so on).
//
// Returns the GET *Route.
func (rb *APIBuilder) Favicon(favPath string, requestPath ...string) (*Route, error) {
favPath = Abs(favPath)
f, err := os.Open(favPath)
if err != nil {
return nil, errDirectoryFileNotFound.Format(favPath, err.Error())
}
// ignore error f.Close()
defer f.Close()
fi, _ := f.Stat()
if fi.IsDir() { // if it's dir the try to get the favicon.ico
fav := path.Join(favPath, "favicon.ico")
f, err = os.Open(fav)
if err != nil {
//we try again with .png
return rb.Favicon(path.Join(favPath, "favicon.png"))
}
favPath = fav
fi, _ = f.Stat()
}
cType := TypeByFilename(favPath)
// copy the bytes here in order to cache and not read the ico on each request.
cacheFav := make([]byte, fi.Size())
if _, err = f.Read(cacheFav); err != nil {
// Here we are before actually run the server.
// So we could panic but we don't,
// we just interrupt with a message
// to the (user-defined) logger.
return nil, errDirectoryFileNotFound.
Format(favPath, "favicon: couldn't read the data bytes for file: "+err.Error())
}
modtime := ""
h := func(ctx context.Context) {
if modtime == "" {
modtime = fi.ModTime().UTC().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())
}
if t, err := time.Parse(ctx.Application().ConfigurationReadOnly().GetTimeFormat(), ctx.GetHeader(ifModifiedSinceHeaderKey)); err == nil && fi.ModTime().Before(t.Add(StaticCacheDuration)) {
ctx.ResponseWriter().Header().Del(contentTypeHeaderKey)
ctx.ResponseWriter().Header().Del(contentLengthHeaderKey)
ctx.StatusCode(http.StatusNotModified)
return
}
ctx.ResponseWriter().Header().Set(contentTypeHeaderKey, cType)
ctx.ResponseWriter().Header().Set(lastModifiedHeaderKey, modtime)
ctx.StatusCode(http.StatusOK)
if _, err := ctx.Write(cacheFav); err != nil {
ctx.Application().Log("error while trying to serve the favicon: %s", err.Error())
}
}
reqPath := "/favicon" + path.Ext(fi.Name()) //we could use the filename, but because standards is /favicon.ico/.png.
if len(requestPath) > 0 && requestPath[0] != "" {
reqPath = requestPath[0]
}
return rb.registerResourceRoute(reqPath, h)
}
// StaticWeb returns a handler that serves HTTP requests
// with the contents of the file system rooted at directory.
//
// first parameter: the route path
// second parameter: the system directory
// third OPTIONAL parameter: the exception routes
// (= give priority to these routes instead of the static handler)
// for more options look rb.StaticHandler.
//
// rb.StaticWeb("/static", "./static")
//
// As a special case, the returned file server redirects any request
// ending in "/index.html" to the same path, without the final
// "index.html".
//
// StaticWeb calls the StaticHandler(requestPath, systemPath, listingDirectories: false, gzip: false ).
//
// Returns the GET *Route.
func (rb *APIBuilder) StaticWeb(reqPath string, systemPath string, exceptRoutes ...*Route) (*Route, error) {
h := rb.StaticHandler(reqPath, systemPath, false, false, exceptRoutes...)
paramName := "file"
routePath := joinPath(reqPath, WildcardParam(paramName))
handler := func(ctx context.Context) {
h(ctx)
// re-check the content type here for any case,
// although the new code does it automatically but it's good to have it here.
if ctx.GetStatusCode() >= 200 && ctx.GetStatusCode() < 400 {
if fname := ctx.Params().Get(paramName); fname != "" {
cType := TypeByFilename(fname)
ctx.ContentType(cType)
}
}
}
return rb.registerResourceRoute(routePath, handler)
}
// OnErrorCode registers an error http status code
// based on the "statusCode" >= 400.
// The handler is being wrapepd by a generic
// handler which will try to reset
// the body if recorder was enabled
// and/or disable the gzip if gzip response recorder
// was active.
func (rb *APIBuilder) OnErrorCode(statusCode int, handler context.Handler) {
rb.errorCodeHandlers.Register(statusCode, handler)
}
// FireErrorCode executes an error http status code handler
// based on the context's status code.
//
// If a handler is not already registered,
// then it creates & registers a new trivial handler on the-fly.
func (rb *APIBuilder) FireErrorCode(ctx context.Context) {
rb.errorCodeHandlers.Fire(ctx)
}
// Layout oerrides the parent template layout with a more specific layout for this Party
// returns this Party, to continue as normal
// Usage:
// app := iris.New()
// my := app.Party("/my").Layout("layouts/mylayout.html")
// {
// my.Get("/", func(ctx context.Context) {
// ctx.MustRender("page1.html", nil)
// })
// }
func (rb *APIBuilder) Layout(tmplLayoutFile string) Party {
rb.Use(func(ctx context.Context) {
ctx.ViewLayout(tmplLayoutFile)
ctx.Next()
})
return rb
}
// joinHandlers uses to create a copy of all Handlers and return them in order to use inside the node
func joinHandlers(Handlers1 context.Handlers, Handlers2 context.Handlers) context.Handlers {
nowLen := len(Handlers1)
totalLen := nowLen + len(Handlers2)
// create a new slice of Handlers in order to store all handlers, the already handlers(Handlers) and the new
newHandlers := make(context.Handlers, totalLen)
//copy the already Handlers to the just created
copy(newHandlers, Handlers1)
//start from there we finish, and store the new Handlers too
copy(newHandlers[nowLen:], Handlers2)
return newHandlers
}

977
core/router/fs.go Normal file
View File

@@ -0,0 +1,977 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ & Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package router
import (
"fmt"
"io"
"mime/multipart"
"net/http"
"net/textproto"
"net/url"
"os"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
)
// Prioritize is a middleware which executes a route against this path
// when the request's Path has a prefix of the route's STATIC PART
// is not executing ExecRoute to determinate if it's valid, for performance reasons
// if this function is not enough for you and you want to test more than one parameterized path
// then use the: if c := ExecRoute(r); c == nil { /* move to the next, the route is not valid */ }
//
// You can find the Route by iris.Default.Routes().Lookup("theRouteName")
// you can set a route name as: myRoute := iris.Default.Get("/mypath", handler)("theRouteName")
// that will set a name to the route and returns its iris.Route instance for further usage.
//
// if the route found then it executes that and don't continue to the next handler
// if not found then continue to the next handler
func Prioritize(r *Route) context.Handler {
if r != nil {
return func(ctx context.Context) {
reqPath := ctx.Path()
staticPath := ResolveStaticPath(reqPath)
if strings.HasPrefix(reqPath, staticPath) {
ctx.Exec(r.Method, reqPath) // execute the route based on this request path
// we are done here.
return
}
// execute the next handler if no prefix
// here look, the only error we catch is the 404.
ctx.Next()
}
}
return func(ctx context.Context) { ctx.Next() }
}
// StaticHandler returns a new Handler which is ready
// to serve all kind of static files.
//
// Developers can wrap this handler using the `iris.StripPrefix`
// for a fixed static path when the result handler is being, finally, registered to a route.
//
//
// Usage:
// app := iris.New()
// ...
// fileserver := iris.StaticHandler("./static_files", false, false)
// h := iris.StripPrefix("/static", fileserver)
// /* http://mydomain.com/static/css/style.css */
// app.Get("/static", h)
// ...
//
func StaticHandler(systemPath string, showList bool, enableGzip bool, exceptRoutes ...*Route) context.Handler {
return NewStaticHandlerBuilder(systemPath).
Listing(showList).
Gzip(enableGzip).
Except(exceptRoutes...).
Build()
}
// StaticHandlerBuilder is the web file system's Handler builder
// use that or the iris.StaticHandler/StaticWeb methods
type StaticHandlerBuilder interface {
Gzip(enable bool) StaticHandlerBuilder
Listing(listDirectoriesOnOff bool) StaticHandlerBuilder
Except(r ...*Route) StaticHandlerBuilder
Build() context.Handler
}
// +------------------------------------------------------------+
// | |
// | Static Builder |
// | |
// +------------------------------------------------------------+
type fsHandler struct {
// user options, only directory is required.
directory http.Dir
gzip bool
listDirectories bool
// these are init on the Build() call
filesystem http.FileSystem
once sync.Once
exceptions []*Route
handler context.Handler
}
func toWebPath(systemPath string) string {
// winos slash to slash
webpath := strings.Replace(systemPath, "\\", "/", -1)
// double slashes to single
webpath = strings.Replace(webpath, "//", "/", -1)
// remove all dots
webpath = strings.Replace(webpath, ".", "", -1)
return webpath
}
// Abs calls filepath.Abs but ignores the error and
// returns the original value if any error occurred.
func Abs(path string) string {
absPath, err := filepath.Abs(path)
if err != nil {
return path
}
return absPath
}
// NewStaticHandlerBuilder returns a new Handler which serves static files
// supports gzip, no listing and much more
// Note that, this static builder returns a Handler
// it doesn't cares about the rest of your iris configuration.
//
// Use the iris.StaticHandler/StaticWeb in order to serve static files on more automatic way
// this builder is used by people who have more complicated application
// structure and want a fluent api to work on.
func NewStaticHandlerBuilder(dir string) StaticHandlerBuilder {
return &fsHandler{
directory: http.Dir(Abs(dir)),
// gzip is disabled by default
gzip: false,
// list directories disabled by default
listDirectories: false,
}
}
// Gzip if enable is true then gzip compression is enabled for this static directory
// Defaults to false
func (w *fsHandler) Gzip(enable bool) StaticHandlerBuilder {
w.gzip = enable
return w
}
// Listing turn on/off the 'show files and directories'.
// Defaults to false
func (w *fsHandler) Listing(listDirectoriesOnOff bool) StaticHandlerBuilder {
w.listDirectories = listDirectoriesOnOff
return w
}
// Except add a route exception,
// gives priority to that Route over the static handler.
func (w *fsHandler) Except(r ...*Route) StaticHandlerBuilder {
w.exceptions = append(w.exceptions, r...)
return w
}
type (
noListFile struct {
http.File
}
)
// Overrides the Readdir of the http.File in order to disable showing a list of the dirs/files
func (n noListFile) Readdir(count int) ([]os.FileInfo, error) {
return nil, nil
}
// Implements the http.Filesystem
// Do not call it.
func (w *fsHandler) Open(name string) (http.File, error) {
info, err := w.filesystem.Open(name)
if err != nil {
return nil, err
}
if !w.listDirectories {
return noListFile{info}, nil
}
return info, nil
}
// Build the handler (once) and returns it
func (w *fsHandler) Build() context.Handler {
// we have to ensure that Build is called ONLY one time,
// one instance per one static directory.
w.once.Do(func() {
w.filesystem = w.directory
fileserver := func(ctx context.Context) {
upath := ctx.Request().URL.Path
if !strings.HasPrefix(upath, "/") {
upath = "/" + upath
ctx.Request().URL.Path = upath
}
// Note the request.url.path is changed but request.RequestURI is not
// so on custom errors we use the requesturi instead.
// this can be changed
_, prevStatusCode := serveFile(ctx,
w.filesystem,
path.Clean(upath),
false,
w.listDirectories,
(w.gzip && ctx.ClientSupportsGzip()),
)
// check for any http errors after the file handler executed
if prevStatusCode >= 400 { // error found (404 or 400 or 500 usually)
if writer, ok := ctx.ResponseWriter().(*context.GzipResponseWriter); ok && writer != nil {
writer.ResetBody()
writer.Disable()
// ctx.ResponseWriter.Header().Del(contentType) // application/x-gzip sometimes lawl
// remove gzip headers
// headers := ctx.ResponseWriter.Header()
// headers[contentType] = nil
// headers["X-Content-Type-Options"] = nil
// headers[varyHeader] = nil
// headers[contentEncodingHeader] = nil
// headers[contentLength] = nil
}
ctx.StatusCode(prevStatusCode)
return
}
// go to the next middleware
ctx.Next()
}
if len(w.exceptions) > 0 {
middleware := make(context.Handlers, len(w.exceptions)+1)
for i := range w.exceptions {
middleware[i] = Prioritize(w.exceptions[i])
}
middleware[len(w.exceptions)] = fileserver
w.handler = func(ctx context.Context) {
ctxHandlers := ctx.Handlers()
ctx.SetHandlers(append(middleware, ctxHandlers...))
ctx.Handlers()[0](ctx)
}
return
}
w.handler = fileserver
})
return w.handler
}
// StripPrefix returns a handler that serves HTTP requests
// by removing the given prefix from the request URL's Path
// and invoking the handler h. StripPrefix handles a
// request for a path that doesn't begin with prefix by
// replying with an HTTP 404 not found error.
//
// Usage:
// fileserver := iris.StaticHandler("./static_files", false, false)
// h := iris.StripPrefix("/static", fileserver)
// app.Get("/static", h)
//
func StripPrefix(prefix string, h context.Handler) context.Handler {
if prefix == "" {
return h
}
// here we separate the path from the subdomain (if any), we care only for the path
// fixes a bug when serving static files via a subdomain
fixedPrefix := prefix
if dotWSlashIdx := strings.Index(fixedPrefix, SubdomainIndicator); dotWSlashIdx > 0 {
fixedPrefix = fixedPrefix[dotWSlashIdx+1:]
}
fixedPrefix = toWebPath(fixedPrefix)
return func(ctx context.Context) {
if p := strings.TrimPrefix(ctx.Request().URL.Path, fixedPrefix); len(p) < len(ctx.Request().URL.Path) {
ctx.Request().URL.Path = p
h(ctx)
} else {
ctx.NotFound()
}
}
}
// +------------------------------------------------------------+
// | |
// | serve file handler |
// | edited from net/http/fs.go in order to support GZIP with |
// | custom iris http errors and fallback to non-compressed data|
// | when not supported. |
// | |
// +------------------------------------------------------------+
var htmlReplacer = strings.NewReplacer(
"&", "&amp;",
"<", "&lt;",
">", "&gt;",
// "&#34;" is shorter than "&quot;".
`"`, "&#34;",
// "&#39;" is shorter than "&apos;" and apos was not in HTML until HTML5.
"'", "&#39;",
)
func dirList(ctx context.Context, f http.File) (string, int) {
dirs, err := f.Readdir(-1)
if err != nil {
// TODO: log err.Error() to the Server.ErrorLog, once it's possible
// for a handler to get at its Server via the http.ResponseWriter. See
// Issue 12438.
return "Error reading directory", http.StatusInternalServerError
}
sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
ctx.ContentType("text/html")
fmt.Fprintf(ctx.ResponseWriter(), "<pre>\n")
for _, d := range dirs {
name := d.Name()
if d.IsDir() {
name += "/"
}
// name may contain '?' or '#', which must be escaped to remain
// part of the URL path, and not indicate the start of a query
// string or fragment.
url := url.URL{Path: name}
fmt.Fprintf(ctx.ResponseWriter(), "<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name))
}
fmt.Fprintf(ctx.ResponseWriter(), "</pre>\n")
return "", http.StatusOK
}
// errSeeker is returned by ServeContent's sizeFunc when the content
// doesn't seek properly. The underlying Seeker's error text isn't
// included in the sizeFunc reply so it's not sent over HTTP to end
// users.
var errSeeker = errors.New("seeker can't seek")
// errNoOverlap is returned by serveContent's parseRange if first-byte-pos of
// all of the byte-range-spec values is greater than the content size.
var errNoOverlap = errors.New("invalid range: failed to overlap")
// The algorithm uses at most sniffLen bytes to make its decision.
const sniffLen = 512
// if name is empty, filename is unknown. (used for mime type, before sniffing)
// if modtime.IsZero(), modtime is unknown.
// content must be seeked to the beginning of the file.
// The sizeFunc is called at most once. Its error, if any, is sent in the HTTP response.
func serveContent(ctx context.Context, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker, gzip bool) (string, int) /* we could use the TransactionErrResult but prefer not to create new objects for each of the errors on static file handlers*/ {
setLastModified(ctx, modtime)
done, rangeReq := checkPreconditions(ctx, modtime)
if done {
return "", http.StatusNotModified
}
code := http.StatusOK
// If Content-Type isn't set, use the file's extension to find it, but
// if the Content-Type is unset explicitly, do not sniff the type.
ctypes, haveType := ctx.ResponseWriter().Header()["Content-Type"]
var ctype string
if !haveType {
ctype = TypeByExtension(filepath.Ext(name))
if ctype == "" {
// read a chunk to decide between utf-8 text and binary
var buf [sniffLen]byte
n, _ := io.ReadFull(content, buf[:])
ctype = http.DetectContentType(buf[:n])
_, err := content.Seek(0, io.SeekStart) // rewind to output whole file
if err != nil {
return "seeker can't seek", http.StatusInternalServerError
}
}
ctx.ContentType(ctype)
} else if len(ctypes) > 0 {
ctype = ctypes[0]
}
size, err := sizeFunc()
if err != nil {
return err.Error(), http.StatusInternalServerError
}
// handle Content-Range header.
sendSize := size
var sendContent io.Reader = content
if gzip {
_ = ctx.GzipResponseWriter()
}
if size >= 0 {
ranges, err := parseRange(rangeReq, size)
if err != nil {
if err == errNoOverlap {
ctx.Header("Content-Range", fmt.Sprintf("bytes */%d", size))
}
return err.Error(), http.StatusRequestedRangeNotSatisfiable
}
if sumRangesSize(ranges) > size {
// The total number of bytes in all the ranges
// is larger than the size of the file by
// itself, so this is probably an attack, or a
// dumb client. Ignore the range request.
ranges = nil
}
switch {
case len(ranges) == 1:
// RFC 2616, Section 14.16:
// "When an HTTP message includes the content of a single
// range (for example, a response to a request for a
// single range, or to a request for a set of ranges
// that overlap without any holes), this content is
// transmitted with a Content-Range header, and a
// Content-Length header showing the number of bytes
// actually transferred.
// ...
// A response to a request for a single range MUST NOT
// be sent using the multipart/byteranges media type."
ra := ranges[0]
if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
return err.Error(), http.StatusRequestedRangeNotSatisfiable
}
sendSize = ra.length
code = http.StatusPartialContent
ctx.Header("Content-Range", ra.contentRange(size))
case len(ranges) > 1:
sendSize = rangesMIMESize(ranges, ctype, size)
code = http.StatusPartialContent
pr, pw := io.Pipe()
mw := multipart.NewWriter(pw)
ctx.ContentType("multipart/byteranges; boundary=" + mw.Boundary())
sendContent = pr
defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
go func() {
for _, ra := range ranges {
part, err := mw.CreatePart(ra.mimeHeader(ctype, size))
if err != nil {
pw.CloseWithError(err)
return
}
if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
pw.CloseWithError(err)
return
}
if _, err := io.CopyN(part, content, ra.length); err != nil {
pw.CloseWithError(err)
return
}
}
mw.Close()
pw.Close()
}()
}
ctx.Header("Accept-Ranges", "bytes")
if ctx.ResponseWriter().Header().Get(contentEncodingHeaderKey) == "" {
ctx.Header(contentLengthHeaderKey, strconv.FormatInt(sendSize, 10))
}
}
ctx.StatusCode(code)
if ctx.Method() != http.MethodHead {
io.CopyN(ctx.ResponseWriter(), sendContent, sendSize)
}
return "", code
}
// scanETag determines if a syntactically valid ETag is present at s. If so,
// the ETag and remaining text after consuming ETag is returned. Otherwise,
// it returns "", "".
func scanETag(s string) (etag string, remain string) {
s = textproto.TrimString(s)
start := 0
if strings.HasPrefix(s, "W/") {
start = 2
}
if len(s[start:]) < 2 || s[start] != '"' {
return "", ""
}
// ETag is either W/"text" or "text".
// See RFC 7232 2.3.
for i := start + 1; i < len(s); i++ {
c := s[i]
switch {
// Character values allowed in ETags.
case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80:
case c == '"':
return string(s[:i+1]), s[i+1:]
default:
break
}
}
return "", ""
}
// etagStrongMatch reports whether a and b match using strong ETag comparison.
// Assumes a and b are valid ETags.
func etagStrongMatch(a, b string) bool {
return a == b && a != "" && a[0] == '"'
}
// etagWeakMatch reports whether a and b match using weak ETag comparison.
// Assumes a and b are valid ETags.
func etagWeakMatch(a, b string) bool {
return strings.TrimPrefix(a, "W/") == strings.TrimPrefix(b, "W/")
}
// condResult is the result of an HTTP request precondition check.
// See https://tools.ietf.org/html/rfc7232 section 3.
type condResult int
const (
condNone condResult = iota
condTrue
condFalse
)
func checkIfMatch(ctx context.Context) condResult {
im := ctx.GetHeader("If-Match")
if im == "" {
return condNone
}
for {
im = textproto.TrimString(im)
if len(im) == 0 {
break
}
if im[0] == ',' {
im = im[1:]
continue
}
if im[0] == '*' {
return condTrue
}
etag, remain := scanETag(im)
if etag == "" {
break
}
if etagStrongMatch(etag, ctx.ResponseWriter().Header().Get("Etag")) {
return condTrue
}
im = remain
}
return condFalse
}
func checkIfUnmodifiedSince(ctx context.Context, modtime time.Time) condResult {
ius := ctx.GetHeader("If-Unmodified-Since")
if ius == "" || isZeroTime(modtime) {
return condNone
}
if t, err := http.ParseTime(ius); err == nil {
// The Date-Modified header truncates sub-second precision, so
// use mtime < t+1s instead of mtime <= t to check for unmodified.
if modtime.Before(t.Add(1 * time.Second)) {
return condTrue
}
return condFalse
}
return condNone
}
func checkIfNoneMatch(ctx context.Context) condResult {
inm := ctx.GetHeader("If-None-Match")
if inm == "" {
return condNone
}
buf := inm
for {
buf = textproto.TrimString(buf)
if len(buf) == 0 {
break
}
if buf[0] == ',' {
buf = buf[1:]
}
if buf[0] == '*' {
return condFalse
}
etag, remain := scanETag(buf)
if etag == "" {
break
}
if etagWeakMatch(etag, ctx.ResponseWriter().Header().Get("Etag")) {
return condFalse
}
buf = remain
}
return condTrue
}
func checkIfModifiedSince(ctx context.Context, modtime time.Time) condResult {
if ctx.Method() != http.MethodGet && ctx.Method() != http.MethodHead {
return condNone
}
ims := ctx.GetHeader("If-Modified-Since")
if ims == "" || isZeroTime(modtime) {
return condNone
}
t, err := http.ParseTime(ims)
if err != nil {
return condNone
}
// The Date-Modified header truncates sub-second precision, so
// use mtime < t+1s instead of mtime <= t to check for unmodified.
if modtime.Before(t.Add(1 * time.Second)) {
return condFalse
}
return condTrue
}
func checkIfRange(ctx context.Context, modtime time.Time) condResult {
if ctx.Method() != http.MethodGet {
return condNone
}
ir := ctx.GetHeader("If-Range")
if ir == "" {
return condNone
}
etag, _ := scanETag(ir)
if etag != "" {
if etagStrongMatch(etag, ctx.ResponseWriter().Header().Get("Etag")) {
return condTrue
}
return condFalse
}
// The If-Range value is typically the ETag value, but it may also be
// the modtime date. See golang.org/issue/8367.
if modtime.IsZero() {
return condFalse
}
t, err := http.ParseTime(ir)
if err != nil {
return condFalse
}
if t.Unix() == modtime.Unix() {
return condTrue
}
return condFalse
}
var unixEpochTime = time.Unix(0, 0)
// isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
func isZeroTime(t time.Time) bool {
return t.IsZero() || t.Equal(unixEpochTime)
}
func setLastModified(ctx context.Context, modtime time.Time) {
if !isZeroTime(modtime) {
ctx.Header(lastModifiedHeaderKey, modtime.UTC().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat()))
}
}
func writeNotModified(ctx context.Context) {
// RFC 7232 section 4.1:
// a sender SHOULD NOT generate representation metadata other than the
// above listed fields unless said metadata exists for the purpose of
// guiding cache updates (e.g., Last-Modified might be useful if the
// response does not have an ETag field).
h := ctx.ResponseWriter().Header()
delete(h, contentTypeHeaderKey)
delete(h, contentLengthHeaderKey)
if h.Get("Etag") != "" {
delete(h, "Last-Modified")
}
ctx.StatusCode(http.StatusNotModified)
}
// checkPreconditions evaluates request preconditions and reports whether a precondition
// resulted in sending StatusNotModified or StatusPreconditionFailed.
func checkPreconditions(ctx context.Context, modtime time.Time) (done bool, rangeHeader string) {
// This function carefully follows RFC 7232 section 6.
ch := checkIfMatch(ctx)
if ch == condNone {
ch = checkIfUnmodifiedSince(ctx, modtime)
}
if ch == condFalse {
ctx.StatusCode(http.StatusPreconditionFailed)
return true, ""
}
switch checkIfNoneMatch(ctx) {
case condFalse:
if ctx.Method() == http.MethodGet || ctx.Method() == http.MethodHead {
writeNotModified(ctx)
return true, ""
}
ctx.StatusCode(http.StatusPreconditionFailed)
return true, ""
case condNone:
if checkIfModifiedSince(ctx, modtime) == condFalse {
writeNotModified(ctx)
return true, ""
}
}
rangeHeader = ctx.GetHeader("Range")
if rangeHeader != "" {
if checkIfRange(ctx, modtime) == condFalse {
rangeHeader = ""
}
}
return false, rangeHeader
}
// name is '/'-separated, not filepath.Separator.
func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bool, showList bool, gzip bool) (string, int) {
const indexPage = "/index.html"
// redirect .../index.html to .../
// can't use Redirect() because that would make the path absolute,
// which would be a problem running under StripPrefix
if strings.HasSuffix(ctx.Request().URL.Path, indexPage) {
localRedirect(ctx, "./")
return "", http.StatusMovedPermanently
}
f, err := fs.Open(name)
if err != nil {
msg, code := toHTTPError(err)
return msg, code
}
defer f.Close()
d, err := f.Stat()
if err != nil {
msg, code := toHTTPError(err)
return msg, code
}
if redirect {
// redirect to canonical path: / at end of directory url
// ctx.Request.URL.Path always begins with /
url := ctx.Request().URL.Path
if d.IsDir() {
if url[len(url)-1] != '/' {
localRedirect(ctx, path.Base(url)+"/")
return "", http.StatusMovedPermanently
}
} else {
if url[len(url)-1] == '/' {
localRedirect(ctx, "../"+path.Base(url))
return "", http.StatusMovedPermanently
}
}
}
// redirect if the directory name doesn't end in a slash
if d.IsDir() {
url := ctx.Request().URL.Path
if url[len(url)-1] != '/' {
localRedirect(ctx, path.Base(url)+"/")
return "", http.StatusMovedPermanently
}
}
// use contents of index.html for directory, if present
if d.IsDir() {
index := strings.TrimSuffix(name, "/") + indexPage
ff, err := fs.Open(index)
if err == nil {
defer ff.Close()
dd, err := ff.Stat()
if err == nil {
name = index
d = dd
f = ff
}
}
}
// Still a directory? (we didn't find an index.html file)
if d.IsDir() {
if !showList {
return "", http.StatusForbidden
}
if checkIfModifiedSince(ctx, d.ModTime()) == condFalse {
writeNotModified(ctx)
return "", http.StatusNotModified
}
ctx.Header("Last-Modified", d.ModTime().UTC().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat()))
return dirList(ctx, f)
}
// serveContent will check modification time
sizeFunc := func() (int64, error) { return d.Size(), nil }
return serveContent(ctx, d.Name(), d.ModTime(), sizeFunc, f, gzip)
}
// toHTTPError returns a non-specific HTTP error message and status code
// for a given non-nil error value. It's important that toHTTPError does not
// actually return err.Error(), since msg and httpStatus are returned to users,
// and historically Go's ServeContent always returned just "404 Not Found" for
// all errors. We don't want to start leaking information in error messages.
func toHTTPError(err error) (msg string, httpStatus int) {
if os.IsNotExist(err) {
return "404 page not found", http.StatusNotFound
}
if os.IsPermission(err) {
return "403 Forbidden", http.StatusForbidden
}
// Default:
return "500 Internal Server Error", http.StatusInternalServerError
}
// localRedirect gives a Moved Permanently response.
// It does not convert relative paths to absolute paths like Redirect does.
func localRedirect(ctx context.Context, newPath string) {
if q := ctx.Request().URL.RawQuery; q != "" {
newPath += "?" + q
}
ctx.Header("Location", newPath)
ctx.StatusCode(http.StatusMovedPermanently)
}
func containsDotDot(v string) bool {
if !strings.Contains(v, "..") {
return false
}
for _, ent := range strings.FieldsFunc(v, isSlashRune) {
if ent == ".." {
return true
}
}
return false
}
func isSlashRune(r rune) bool { return r == '/' || r == '\\' }
// httpRange specifies the byte range to be sent to the client.
type httpRange struct {
start, length int64
}
func (r httpRange) contentRange(size int64) string {
return fmt.Sprintf("bytes %d-%d/%d", r.start, r.start+r.length-1, size)
}
func (r httpRange) mimeHeader(contentType string, size int64) textproto.MIMEHeader {
return textproto.MIMEHeader{
"Content-Range": {r.contentRange(size)},
contentType: {contentType},
}
}
// parseRange parses a Range header string as per RFC 2616.
// errNoOverlap is returned if none of the ranges overlap.
func parseRange(s string, size int64) ([]httpRange, error) {
if s == "" {
return nil, nil // header not present
}
const b = "bytes="
if !strings.HasPrefix(s, b) {
return nil, errors.New("invalid range")
}
var ranges []httpRange
noOverlap := false
for _, ra := range strings.Split(s[len(b):], ",") {
ra = strings.TrimSpace(ra)
if ra == "" {
continue
}
i := strings.Index(ra, "-")
if i < 0 {
return nil, errors.New("invalid range")
}
start, end := strings.TrimSpace(ra[:i]), strings.TrimSpace(ra[i+1:])
var r httpRange
if start == "" {
// If no start is specified, end specifies the
// range start relative to the end of the file.
i, err := strconv.ParseInt(end, 10, 64)
if err != nil {
return nil, errors.New("invalid range")
}
if i > size {
i = size
}
r.start = size - i
r.length = size - r.start
} else {
i, err := strconv.ParseInt(start, 10, 64)
if err != nil || i < 0 {
return nil, errors.New("invalid range")
}
if i >= size {
// If the range begins after the size of the content,
// then it does not overlap.
noOverlap = true
continue
}
r.start = i
if end == "" {
// If no end is specified, range extends to end of the file.
r.length = size - r.start
} else {
i, err := strconv.ParseInt(end, 10, 64)
if err != nil || r.start > i {
return nil, errors.New("invalid range")
}
if i >= size {
i = size - 1
}
r.length = i - r.start + 1
}
}
ranges = append(ranges, r)
}
if noOverlap && len(ranges) == 0 {
// The specified ranges did not overlap with the content.
return nil, errNoOverlap
}
return ranges, nil
}
// countingWriter counts how many bytes have been written to it.
type countingWriter int64
func (w *countingWriter) Write(p []byte) (n int, err error) {
*w += countingWriter(len(p))
return len(p), nil
}
// rangesMIMESize returns the number of bytes it takes to encode the
// provided ranges as a multipart response.
func rangesMIMESize(ranges []httpRange, contentType string, contentSize int64) (encSize int64) {
var w countingWriter
mw := multipart.NewWriter(&w)
for _, ra := range ranges {
mw.CreatePart(ra.mimeHeader(contentType, contentSize))
encSize += ra.length
}
mw.Close()
encSize += int64(w)
return
}
func sumRangesSize(ranges []httpRange) (size int64) {
for _, ra := range ranges {
size += ra.length
}
return
}
// DirectoryExists returns true if a directory(or file) exists, otherwise false
func DirectoryExists(dir string) bool {
if _, err := os.Stat(dir); os.IsNotExist(err) {
return false
}
return true
}

252
core/router/handler.go Normal file
View File

@@ -0,0 +1,252 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package router
import (
"html"
"net/http"
"sort"
"strings"
"sync/atomic"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/nettools"
"github.com/kataras/iris/core/router/httprouter"
)
// RequestHandler the middle man between acquiring a context and releasing it.
// By-default is the router algorithm.
type RequestHandler interface {
// HandleRequest is same as context.Handler but its usage is only about routing,
// separate the concept here.
HandleRequest(context.Context)
// Build should builds the handler, it's being called on router's BuildRouter.
Build(provider RoutesProvider) error
}
type tree struct {
Method string
// subdomain is empty for default-hostname routes,
// ex: mysubdomain.
Subdomain string
Entry *httprouter.Node
}
type routerHandler struct {
trees []*tree
vhost atomic.Value // is a string setted at the first it founds a subdomain, we need that here in order to reduce the resolveVHost calls
hosts bool // true if at least one route contains a Subdomain.
}
var _ RequestHandler = &routerHandler{}
func (h *routerHandler) getTree(method, subdomain string) *tree {
for i := range h.trees {
t := h.trees[i]
if t.Method == method && t.Subdomain == subdomain {
return t
}
}
return nil
}
func (h *routerHandler) addRoute(method, subdomain, path string, handlers context.Handlers) error {
// get or create a tree and add the route
t := h.getTree(method, subdomain)
if t == nil {
//first time we register a route to this method with this domain
t = &tree{Method: method, Subdomain: subdomain, Entry: new(httprouter.Node)}
h.trees = append(h.trees, t)
}
if err := t.Entry.AddRoute(path, handlers); err != nil {
return err
}
return nil
}
func NewDefaultHandler() RequestHandler {
h := &routerHandler{}
return h
}
func (h *routerHandler) Build(provider RoutesProvider) error {
registeredRoutes := provider.GetRoutes()
h.trees = h.trees[0:0] // reset, inneed when rebuilding.
// sort, subdomains goes first.
sort.Slice(registeredRoutes, func(i, j int) bool {
return len(registeredRoutes[i].Subdomain) >= len(registeredRoutes[j].Subdomain)
})
for _, r := range registeredRoutes {
if r.Subdomain != "" {
h.hosts = true
}
// the only "bad" with this is if the user made an error
// on route, it will be stacked shown in this build state
// and no in the lines of the user's action, they should read
// the docs better. Or TODO: add a link here in order to help new users.
if err := h.addRoute(r.Method, r.Subdomain, r.Path, r.Handlers); err != nil {
return err
}
}
return nil
}
func (h *routerHandler) HandleRequest(ctx context.Context) {
method := ctx.Method()
for i := range h.trees {
t := h.trees[i]
if method != t.Method {
continue
}
// Changed my mind for subdomains, there are unnecessary steps here
// most servers don't need these and on other servers may force the server to send a 404 not found
// on a valid subdomain, by commenting my previous implementation we allow any request host to be discovarable for subdomains.
// if h.hosts && t.Subdomain != "" {
// if h.vhost.Load() == nil {
// h.vhost.Store(nettools.ResolveVHost(ctx.Application().ConfigurationReadOnly().GetAddr()))
// }
// host := h.vhost.Load().(string)
// requestHost := ctx.Host()
// if requestHost != host {
// // we have a subdomain
// if strings.Contains(t.Subdomain, DynamicSubdomainIndicator) {
// } else {
// // if subdomain+host is not the request host
// // and
// // if request host didn't matched the server's host
// // check if reached the server
// // with a local address, this case is the developer him/herself,
// // if both of them failed then continue and ignore this tree.
// if t.Subdomain+host != requestHost && !nettools.IsLoopbackHost(requestHost) {
// // go to the next tree, we have a subdomain but it is not the correct
// continue
// }
// }
// } else {
// //("it's subdomain but the request is not the same as the vhost)
// continue
// }
// }
// new, simpler and without the need of known the real host:
if h.hosts && t.Subdomain != "" {
requestHost := ctx.Host()
if nettools.IsLoopbackSubdomain(requestHost) {
// this fixes a bug when listening on
// 127.0.0.1:8080 for example
// and have a wildcard subdomain and a route registered to root domain.
continue // it's not a subdomain, it's something like 127.0.0.1 probably
}
// it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty
if t.Subdomain == DynamicSubdomainIndicator {
// mydomain.com -> invalid
// localhost -> invalid
// sub.mydomain.com -> valid
// sub.localhost -> valid
serverHost := ctx.Application().ConfigurationReadOnly().GetVHost()
if serverHost == requestHost {
continue // it's not a subdomain, it's a full domain (with .com...)
}
dotIdx := strings.IndexByte(requestHost, '.')
slashIdx := strings.IndexByte(requestHost, '/')
if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) {
// if "." was found anywhere but not at the first path segment (host).
} else {
continue
}
// continue to that, any subdomain is valid.
} else if !strings.HasPrefix(requestHost, t.Subdomain) { // t.Subdomain contains the dot.
continue
}
}
handlers, params, mustRedirect := t.Entry.ResolveRoute(ctx)
if len(handlers) > 0 {
ctx.SetParams(params)
ctx.SetHandlers(handlers)
ctx.Handlers()[0](ctx)
// to remove the .Next(maybe not a good idea), reduces the performance a bit:
// ctx.Handlers()[0](ctx) // execute the first, as soon as possible
// // execute the chain of handlers, carefully
// current := ctx.HandlerIndex(-1)
// for {
// if ctx.IsStopped() || current >= n {
// break
// }
// ctx.HandlerIndex(current)
// ctx.Handlers()[current](ctx)
// current++
// if i := ctx.HandlerIndex(-1); i > current { // navigate to previous handler is not allowed
// current = i
// }
// }
return
} else if mustRedirect && !ctx.Application().ConfigurationReadOnly().GetDisablePathCorrection() { // && ctx.Method() == MethodConnect {
urlToRedirect := ctx.Path()
pathLen := len(urlToRedirect)
if pathLen > 1 {
if urlToRedirect[pathLen-1] == '/' {
urlToRedirect = urlToRedirect[:pathLen-1] // remove the last /
} else {
// it has path prefix, it doesn't ends with / and it hasn't be found, then just append the slash
urlToRedirect = urlToRedirect + "/"
}
statusForRedirect := http.StatusMovedPermanently // StatusMovedPermanently, this document is obselte, clients caches this.
if t.Method == http.MethodPost ||
t.Method == http.MethodPut ||
t.Method == http.MethodDelete {
statusForRedirect = http.StatusTemporaryRedirect // To maintain POST data
}
ctx.Redirect(urlToRedirect, statusForRedirect)
// RFC2616 recommends that a short note "SHOULD" be included in the
// response because older user agents may not understand 301/307.
// Shouldn't send the response for POST or HEAD; that leaves GET.
if t.Method == http.MethodGet {
note := "<a href=\"" +
html.EscapeString(urlToRedirect) +
"\">Moved Permanently</a>.\n"
ctx.ResponseWriter().WriteString(note)
}
return
}
}
// not found
break
}
if ctx.Application().ConfigurationReadOnly().GetFireMethodNotAllowed() {
var methodAllowed string
for i := range h.trees {
t := h.trees[i]
methodAllowed = t.Method // keep track of the allowed method of the last checked tree
if ctx.Method() != methodAllowed {
continue
}
}
// RCF rfc2616 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
// The response MUST include an Allow header containing a list of valid methods for the requested resource.
ctx.Header("Allow", methodAllowed)
ctx.StatusCode(http.StatusMethodNotAllowed)
return
}
ctx.StatusCode(http.StatusNotFound)
}

View File

@@ -0,0 +1,56 @@
Copyright (c) 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Gerasimos Maropoulos nor the name of his
username, kataras, may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Third-Parties:
Copyright (c) 2013 Julien Schmidt. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The names of the contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL JULIEN SCHMIDT BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,456 @@
// Copyright 2013 Julien Schmidt. All rights reserved.
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of the below source code is governed by the BSD 3-Clause license.
package httprouter
import (
"strings"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
)
func min(a, b int) int {
if a <= b {
return a
}
return b
}
func countParams(path string) uint8 {
var n uint
for i := 0; i < len(path); i++ {
if path[i] != ':' && path[i] != '*' {
continue
}
n++
}
if n >= 255 {
return 255
}
return uint8(n)
}
type nodeType uint8
const (
static nodeType = iota // default
root
param
catchAll
)
// Node is the default request handler's tree's entry type.
// Examples of its algorithm can be found via googling or via youtube
// search term: trie, tree sort, data structures: tree, reversed tree, sort tree etc...
type Node struct {
path string
wildChild bool
nType nodeType
maxParams uint8
indices string
children []*Node
handle context.Handlers
priority uint32
}
// increments priority of the given child and reorders if necessary
func (n *Node) incrementChildPrio(pos int) int {
n.children[pos].priority++
prio := n.children[pos].priority
// adjust position (move to front)
newPos := pos
for newPos > 0 && n.children[newPos-1].priority < prio {
// swap Node positions
n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1]
newPos--
}
// build new index char string
if newPos != pos {
n.indices = n.indices[:newPos] + // unchanged prefix, might be empty
n.indices[pos:pos+1] + // the index char we move
n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos'
}
return newPos
}
// AddRoute adds a route with the given handler to the path.
func (n *Node) AddRoute(path string, handle context.Handlers) error {
fullPath := path
n.priority++
numParams := countParams(path)
// non-empty tree
if len(n.path) > 0 || len(n.children) > 0 {
walk:
for {
// Update maxParams of the current Node
if numParams > n.maxParams {
n.maxParams = numParams
}
// Find the longest common prefix.
// This also implies that the common prefix contains no ':' or '*'
// since the existing key can't contain those chars.
i := 0
max := min(len(path), len(n.path))
for i < max && path[i] == n.path[i] {
i++
}
// Split edge
if i < len(n.path) {
child := Node{
path: n.path[i:],
wildChild: n.wildChild,
nType: static,
indices: n.indices,
children: n.children,
handle: n.handle,
priority: n.priority - 1,
}
// Update maxParams (max of all children)
for i := range child.children {
if child.children[i].maxParams > child.maxParams {
child.maxParams = child.children[i].maxParams
}
}
n.children = []*Node{&child}
// []byte for proper unicode char conversion, see #65
n.indices = string([]byte{n.path[i]})
n.path = path[:i]
n.handle = nil
n.wildChild = false
}
// Make new Node a child of this Node
if i < len(path) {
path = path[i:]
if n.wildChild {
n = n.children[0]
n.priority++
// Update maxParams of the child Node
if numParams > n.maxParams {
n.maxParams = numParams
}
numParams--
// Check if the wildcard matches
if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
// Check for longer wildcard, e.g. :name and :names
(len(n.path) >= len(path) || path[len(n.path)] == '/') {
continue walk
} else {
// Wildcard conflict
pathSeg := strings.SplitN(path, "/", 2)[0]
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
return errors.New("'" + pathSeg +
"' in new path '" + fullPath +
"' conflicts with existing wildcard '" + n.path +
"' in existing prefix '" + prefix +
"'")
}
}
c := path[0]
// slash after param
if n.nType == param && c == '/' && len(n.children) == 1 {
n = n.children[0]
n.priority++
continue walk
}
// Check if a child with the next path byte exists
for i := 0; i < len(n.indices); i++ {
if c == n.indices[i] {
i = n.incrementChildPrio(i)
n = n.children[i]
continue walk
}
}
// Otherwise insert it
if c != ':' && c != '*' {
// []byte for proper unicode char conversion, see #65
n.indices += string([]byte{c})
child := &Node{
maxParams: numParams,
}
n.children = append(n.children, child)
n.incrementChildPrio(len(n.indices) - 1)
n = child
}
return n.insertChild(numParams, path, fullPath, handle)
} else if i == len(path) { // Make Node a (in-path) leaf
if n.handle != nil {
return errors.New("a handle is already registered for path '" + fullPath + "'")
}
n.handle = handle
}
return nil
}
} else { // Empty tree
n.insertChild(numParams, path, fullPath, handle)
n.nType = root
}
return nil
}
func (n *Node) insertChild(numParams uint8, path, fullPath string, handle context.Handlers) error {
var offset int // already handled bytes of the path
// find prefix until first wildcard (beginning with ':'' or '*'')
for i, max := 0, len(path); numParams > 0; i++ {
c := path[i]
if c != ':' && c != '*' {
continue
}
// find wildcard end (either '/' or path end)
end := i + 1
for end < max && path[end] != '/' {
switch path[end] {
// the wildcard name must not contain ':' and '*'
case ':', '*':
return errors.New("only one wildcard per path segment is allowed, has: '" +
path[i:] + "' in path '" + fullPath + "'")
default:
end++
}
}
// check if this Node existing children which would be
// unreachable if we insert the wildcard here
if len(n.children) > 0 {
return errors.New("wildcard route '" + path[i:end] +
"' conflicts with existing children in path '" + fullPath + "'")
}
// check if the wildcard has a name
if end-i < 2 {
return errors.New("wildcards must be named with a non-empty name in path '" + fullPath + "'")
}
if c == ':' { // param
// split path at the beginning of the wildcard
if i > 0 {
n.path = path[offset:i]
offset = i
}
child := &Node{
nType: param,
maxParams: numParams,
}
n.children = []*Node{child}
n.wildChild = true
n = child
n.priority++
numParams--
// if the path doesn't end with the wildcard, then there
// will be another non-wildcard subpath starting with '/'
if end < max {
n.path = path[offset:end]
offset = end
child := &Node{
maxParams: numParams,
priority: 1,
}
n.children = []*Node{child}
n = child
}
} else { // catchAll
if end != max || numParams > 1 {
return errors.New("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
}
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
return errors.New("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
}
// currently fixed width 1 for '/'
i--
if path[i] != '/' {
return errors.New("no / before catch-all in path '" + fullPath + "'")
}
n.path = path[offset:i]
// first Node: catchAll Node with empty path
child := &Node{
wildChild: true,
nType: catchAll,
maxParams: 1,
}
n.children = []*Node{child}
n.indices = string(path[i])
n = child
n.priority++
// second Node: Node holding the variable
child = &Node{
path: path[i:],
nType: catchAll,
maxParams: 1,
handle: handle,
priority: 1,
}
n.children = []*Node{child}
return nil
}
}
// insert remaining path part and handle to the leaf
n.path = path[offset:]
n.handle = handle
return nil
}
// ResolveRoute sets the handlers registered to a given path which is acquiring by ctx.Path().
// The values of
// wildcards are saved to the context's Values.
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
// made if a handle exists with an extra (without the) trailing slash for the
// given context.
//
// ResolveRoute finds the correct registered route from the Node when the ctx.Handlers() > 0.
func (n *Node) ResolveRoute(ctx context.Context) (handlers context.Handlers, p context.RequestParams, tsr bool) { //(p context.RequestParams, tsr bool) {
path := ctx.Request().URL.Path
handlers = ctx.Handlers()
// values := ctx.Values()
p = ctx.Params()
walk: // outer loop for walking the tree
for {
if len(path) > len(n.path) {
if path[:len(n.path)] == n.path {
path = path[len(n.path):]
// If this Node does not have a wildcard (param or catchAll)
// child, we can just look up the next child Node and continue
// to walk down the tree
if !n.wildChild {
c := path[0]
for i := 0; i < len(n.indices); i++ {
if c == n.indices[i] {
n = n.children[i]
continue walk
}
}
// Nothing found.
// We can recommend to redirect to the same URL without a
// trailing slash if a leaf exists for that path.
tsr = (path == "/" && n.handle != nil)
return
}
// handle wildcard child
n = n.children[0]
switch n.nType {
case param:
// find param end (either '/' or path end)
end := 0
for end < len(path) && path[end] != '/' {
end++
}
// save param value
if cap(p) < int(n.maxParams) {
p = make(context.RequestParams, 0, n.maxParams)
}
i := len(p)
p = p[:i+1] // expand
p[i].Key = n.path[1:]
p[i].Value = path[:end]
// we need to go deeper!
if end < len(path) {
if len(n.children) > 0 {
path = path[end:]
n = n.children[0]
continue walk
}
// ... but we can't
tsr = (len(path) == end+1)
return
}
if handlers = n.handle; handlers != nil {
return
} else if len(n.children) == 1 {
// No handle found. Check if a handle for this path + a
// trailing slash exists for TSR recommendation
n = n.children[0]
tsr = (n.path == "/" && n.handle != nil)
}
return
case catchAll:
// save param value
if cap(p) < int(n.maxParams) {
p = make(context.RequestParams, 0, n.maxParams)
}
i := len(p)
p = p[:i+1] // expand
p[i].Key = n.path[2:]
p[i].Value = path
handlers = n.handle
return
default:
// invalid Node type here
return
}
}
} else if path == n.path {
// We should have reached the Node containing the handle.
// Check if this Node has a handle registered.
if handlers = n.handle; handlers != nil {
return
}
if path == "/" && n.wildChild && n.nType != root {
tsr = true
return
}
// No handle found. Check if a handle for this path + a
// trailing slash exists for trailing slash recommendation
for i := 0; i < len(n.indices); i++ {
if n.indices[i] == '/' {
n = n.children[i]
tsr = (len(n.path) == 1 && n.handle != nil) ||
(n.nType == catchAll && n.children[0].handle != nil)
return
}
}
return
}
// Nothing found. We can recommend to redirect to the same URL with an
// extra trailing slash if a leaf exists for that path
tsr = (path == "/") ||
(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
path == n.path[:len(n.path)-1] && n.handle != nil)
return
}
}

247
core/router/macro.go Normal file
View File

@@ -0,0 +1,247 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package router
import (
"net/http"
"strconv"
"strings"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
"github.com/kataras/iris/core/router/macro"
"github.com/kataras/iris/core/router/macro/interpreter/ast"
)
// defaultMacros returns a new macro map which
// contains the default router's named param types functions.
func defaultMacros() *macro.MacroMap {
macros := macro.NewMacroMap()
// registers the String and Int default macro funcs
// user can add or override of his own funcs later on
// i.e:
// app.Macro.String.RegisterFunc("equal", func(eqWith string) func(string) bool {
// return func(paramValue string) bool {
// return eqWith == paramValue
// }})
registerBuiltinsMacroFuncs(macros)
return macros
}
func registerBuiltinsMacroFuncs(out *macro.MacroMap) {
// register the String which is the default type if not
// parameter type is specified or
// if a given parameter into path given but the func doesn't exist on the
// parameter type's function list.
//
// these can be overridden by the user, later on.
registerStringMacroFuncs(out.String)
registerIntMacroFuncs(out.Int)
registerAlphabeticalMacroFuncs(out.Alphabetical)
registerFileMacroFuncs(out.File)
registerPathMacroFuncs(out.Path)
}
// String
// anything one part
func registerStringMacroFuncs(out *macro.Macro) {
// this can be used everywhere, it's to help users to define custom regexp expressions
// on all macros
out.RegisterFunc("regexp", func(expr string) macro.EvaluatorFunc {
regexpEvaluator := macro.MustNewEvaluatorFromRegexp(expr)
return regexpEvaluator
})
// checks if param value starts with the 'prefix' arg
out.RegisterFunc("prefix", func(prefix string) macro.EvaluatorFunc {
return func(paramValue string) bool {
return strings.HasPrefix(paramValue, prefix)
}
})
// checks if param value ends with the 'suffix' arg
out.RegisterFunc("suffix", func(suffix string) macro.EvaluatorFunc {
return func(paramValue string) bool {
return strings.HasSuffix(paramValue, suffix)
}
})
// checks if param value contains the 's' arg
out.RegisterFunc("contains", func(s string) macro.EvaluatorFunc {
return func(paramValue string) bool {
return strings.Contains(paramValue, s)
}
})
// checks if param value's length is at least 'min'
out.RegisterFunc("min", func(min int) macro.EvaluatorFunc {
return func(paramValue string) bool {
return len(paramValue) >= min
}
})
// checks if param value's length is not bigger than 'max'
out.RegisterFunc("max", func(max int) macro.EvaluatorFunc {
return func(paramValue string) bool {
return max >= len(paramValue)
}
})
}
// Int
// only numbers (0-9)
func registerIntMacroFuncs(out *macro.Macro) {
// checks if the param value's int representation is
// bigger or equal than 'min'
out.RegisterFunc("min", func(min int) macro.EvaluatorFunc {
return func(paramValue string) bool {
n, err := strconv.Atoi(paramValue)
if err != nil {
return false
}
return n >= min
}
})
// checks if the param value's int representation is
// smaller or equal than 'max'
out.RegisterFunc("max", func(max int) macro.EvaluatorFunc {
return func(paramValue string) bool {
n, err := strconv.Atoi(paramValue)
if err != nil {
return false
}
return n <= max
}
})
// checks if the param value's int representation is
// between min and max, including 'min' and 'max'
out.RegisterFunc("range", func(min, max int) macro.EvaluatorFunc {
return func(paramValue string) bool {
n, err := strconv.Atoi(paramValue)
if err != nil {
return false
}
if n < min || n > max {
return false
}
return true
}
})
}
// Alphabetical
// letters only (upper or lowercase)
func registerAlphabeticalMacroFuncs(out *macro.Macro) {
}
// File
// letters (upper or lowercase)
// numbers (0-9)
// underscore (_)
// dash (-)
// point (.)
// no spaces! or other character
func registerFileMacroFuncs(out *macro.Macro) {
}
// Path
// File+slashes(anywhere)
// should be the latest param, it's the wildcard
func registerPathMacroFuncs(out *macro.Macro) {
}
// compileRoutePathAndHandlers receives a route info and returns its parsed/"compiled" path
// and the new handlers (prepend all the macro's handler, if any).
//
// It's not exported for direct use.
func compileRoutePathAndHandlers(handlers context.Handlers, tmpl *macro.Template) (string, context.Handlers, error) {
// parse the path to node's path, now.
path, err := convertTmplToNodePath(tmpl)
if err != nil {
return tmpl.Src, handlers, err
}
// prepend the macro handler to the route, now,
// right before the register to the tree, so routerbuilder.UseGlobal will work as expected.
if len(tmpl.Params) > 0 {
macroEvaluatorHandler := convertTmplToHandler(tmpl)
// may return nil if no really need a macro handler evaluator
if macroEvaluatorHandler != nil {
handlers = append(context.Handlers{macroEvaluatorHandler}, handlers...)
}
}
return path, handlers, nil
}
func convertTmplToNodePath(tmpl *macro.Template) (string, error) {
routePath := tmpl.Src
// if it has started with {} and it's valid
// then the tmpl.Params will be filled,
// so no any further check needed
for i, p := range tmpl.Params {
if p.Type == ast.ParamTypePath {
if i != len(tmpl.Params)-1 {
return "", errors.New("parameter type \"ParamTypePath\" is allowed to exists to the very last of a path")
}
routePath = strings.Replace(routePath, p.Src, WildcardParam(p.Name), 1)
} else {
routePath = strings.Replace(routePath, p.Src, Param(p.Name), 1)
}
}
return routePath, nil
}
// note: returns nil if not needed, the caller(router) should be check for that before adding that on route's Middleware
func convertTmplToHandler(tmpl *macro.Template) context.Handler {
needMacroHandler := false
// check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params.
// 1. if we don't have, then we don't need to add a handler before the main route's handler (as I said, no performance if macro is not really used)
// 2. if we don't have any named params then we don't need a handler too.
for _, p := range tmpl.Params {
if len(p.Funcs) == 0 && (p.Type == ast.ParamTypeString || p.Type == ast.ParamTypePath) && p.ErrCode == http.StatusNotFound {
} else {
needMacroHandler = true
}
}
if !needMacroHandler {
return nil
}
return func(tmpl macro.Template) context.Handler {
return func(ctx context.Context) {
for _, p := range tmpl.Params {
paramValue := ctx.Params().Get(p.Name)
// first, check for type evaluator
if !p.TypeEvaluator(paramValue) {
ctx.StatusCode(p.ErrCode)
ctx.StopExecution()
return
}
// then check for all of its functions
for _, evalFunc := range p.Funcs {
if !evalFunc(paramValue) {
ctx.StatusCode(p.ErrCode)
ctx.StopExecution()
return
}
}
}
// if all passed, just continue
ctx.Next()
}
}(*tmpl)
}

27
core/router/macro/LICENSE Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Gerasimos Maropoulos nor the name of his
username, kataras, may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,122 @@
// Copyright 2017 Gerasimos Maropoulos. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ast
import (
"fmt"
"strconv"
)
type ParamType uint8
const (
ParamTypeUnExpected ParamType = iota
// /myparam1
ParamTypeString
// /42
ParamTypeInt
// /myparam
ParamTypeAlphabetical
// /main.css
ParamTypeFile
// /myparam1/myparam2
ParamTypePath
)
var paramTypes = map[string]ParamType{
"string": ParamTypeString,
"int": ParamTypeInt,
"alphabetical": ParamTypeAlphabetical,
"file": ParamTypeFile,
"path": ParamTypePath,
// could be named also:
// "tail":
// "wild"
// "wildcard"
}
func LookupParamType(ident string) ParamType {
if typ, ok := paramTypes[ident]; ok {
return typ
}
return ParamTypeUnExpected
}
type ParamStatement struct {
Src string // the original unparsed source, i.e: {id:int range(1,5) else 404}
Name string // id
Type ParamType // int
Funcs []ParamFunc // range
ErrorCode int // 404
}
type ParamFuncArg interface{}
func ParamFuncArgInt64(a ParamFuncArg) (int64, bool) {
if v, ok := a.(int64); ok {
return v, false
}
return -1, false
}
func ParamFuncArgToInt64(a ParamFuncArg) (int64, error) {
switch a.(type) {
case int64:
return a.(int64), nil
case string:
return strconv.ParseInt(a.(string), 10, 64)
case int:
return int64(a.(int)), nil
default:
return -1, fmt.Errorf("unexpected function argument type: %q", a)
}
}
func ParamFuncArgInt(a ParamFuncArg) (int, bool) {
if v, ok := a.(int); ok {
return v, false
}
return -1, false
}
func ParamFuncArgToInt(a ParamFuncArg) (int, error) {
switch a.(type) {
case int:
return a.(int), nil
case string:
return strconv.Atoi(a.(string))
case int64:
return int(a.(int64)), nil
default:
return -1, fmt.Errorf("unexpected function argument type: %q", a)
}
}
func ParamFuncArgString(a ParamFuncArg) (string, bool) {
if v, ok := a.(string); ok {
return v, false
}
return "", false
}
func ParamFuncArgToString(a ParamFuncArg) (string, error) {
switch a.(type) {
case string:
return a.(string), nil
case int:
return strconv.Itoa(a.(int)), nil
case int64:
return strconv.FormatInt(a.(int64), 10), nil
default:
return "", fmt.Errorf("unexpected function argument type: %q", a)
}
}
// range(1,5)
type ParamFunc struct {
Name string // range
Args []ParamFuncArg // [1,5]
}

View File

@@ -0,0 +1,186 @@
// Copyright 2017 Gerasimos Maropoulos. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lexer
import (
"github.com/kataras/iris/core/router/macro/interpreter/token"
)
type Lexer struct {
input string
pos int // current pos in input, current char
readPos int // current reading pos in input, after current char
ch byte // current char under examination
}
func New(src string) *Lexer {
l := &Lexer{
input: src,
}
// step to the first character in order to be ready
l.readChar()
return l
}
func (l *Lexer) readChar() {
if l.readPos >= len(l.input) {
l.ch = 0
} else {
l.ch = l.input[l.readPos]
}
l.pos = l.readPos
l.readPos += 1
}
const (
Begin = '{' // token.LBRACE
End = '}' // token.RBRACE
)
func resolveTokenType(ch byte) token.TokenType {
switch ch {
case Begin:
return token.LBRACE
case End:
return token.RBRACE
// Let's keep it simple, no evaluation for logical operators, we are not making a new programming language, keep it simple makis.
// ||
// case '|':
// if l.peekChar() == '|' {
// ch := l.ch
// l.readChar()
// t = token.Token{Type: token.OR, Literal: string(ch) + string(l.ch)}
// }
// ==
case ':':
return token.COLON
case '(':
return token.LPAREN
case ')':
return token.RPAREN
case ',':
return token.COMMA
// literals
case 0:
return token.EOF
default:
return token.IDENT //
}
}
func (l *Lexer) NextToken() (t token.Token) {
l.skipWhitespace()
typ := resolveTokenType(l.ch)
t.Type = typ
switch typ {
case token.EOF:
t.Literal = ""
case token.IDENT:
if isLetter(l.ch) {
// letters
lit := l.readIdentifier()
typ := token.LookupIdent(lit)
t = l.newToken(typ, lit)
return
}
if isDigit(l.ch) {
// numbers
lit := l.readNumber()
t = l.newToken(token.INT, lit)
return
}
t = l.newTokenRune(token.ILLEGAL, l.ch)
default:
t = l.newTokenRune(typ, l.ch)
}
l.readChar() // set the pos to the next
return
}
func (l *Lexer) NextDynamicToken() (t token.Token) {
// calculate anything, even spaces.
// numbers
lit := l.readNumber()
if lit != "" {
return l.newToken(token.INT, lit)
}
lit = l.readIdentifierFuncArgument()
return l.newToken(token.IDENT, lit)
}
// used to skip any illegal token if inside parenthesis, used to be able to set custom regexp inside a func.
func (l *Lexer) readIdentifierFuncArgument() string {
pos := l.pos
for resolveTokenType(l.ch) != token.RPAREN {
l.readChar()
}
return l.input[pos:l.pos]
}
// useful when we want to peek but no continue, i.e empty param functions 'even()'
func (l *Lexer) PeekNextTokenType() token.TokenType {
if len(l.input)-1 > l.pos {
ch := l.input[l.pos]
return resolveTokenType(ch)
}
return resolveTokenType(0) // EOF
}
func (l *Lexer) newToken(tokenType token.TokenType, lit string) token.Token {
t := token.Token{
Type: tokenType,
Literal: lit,
Start: l.pos,
End: l.pos,
}
// remember, l.pos is the last char
// and we want to include both start and end
// in order to be easy to the user to see by just marking the expression
if l.pos > 1 && len(lit) > 1 {
t.End = l.pos - 1
t.Start = t.End - len(lit) + 1
}
return t
}
func (l *Lexer) newTokenRune(tokenType token.TokenType, ch byte) token.Token {
return l.newToken(tokenType, string(ch))
}
func (l *Lexer) skipWhitespace() {
for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
l.readChar()
}
}
func (l *Lexer) readIdentifier() string {
pos := l.pos
for isLetter(l.ch) {
l.readChar()
}
return l.input[pos:l.pos]
}
func isLetter(ch byte) bool {
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
}
func (l *Lexer) readNumber() string {
pos := l.pos
for isDigit(l.ch) {
l.readChar()
}
return l.input[pos:l.pos]
}
func isDigit(ch byte) bool {
return '0' <= ch && ch <= '9'
}

View File

@@ -0,0 +1,58 @@
// Copyright 2017 Gerasimos Maropoulos. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lexer
import (
"testing"
"github.com/kataras/iris/core/router/macro/interpreter/token"
)
func TestNextToken(t *testing.T) {
input := `{id:int min(1) max(5) else 404}`
tests := []struct {
expectedType token.TokenType
expectedLiteral string
}{
{token.LBRACE, "{"}, // 0
{token.IDENT, "id"}, // 1
{token.COLON, ":"}, // 2
{token.IDENT, "int"}, // 3
{token.IDENT, "min"}, // 4
{token.LPAREN, "("}, // 5
{token.INT, "1"}, // 6
{token.RPAREN, ")"}, // 7
{token.IDENT, "max"}, // 8
{token.LPAREN, "("}, // 9
{token.INT, "5"}, // 10
{token.RPAREN, ")"}, // 11
{token.ELSE, "else"}, // 12
{token.INT, "404"}, // 13
{token.RBRACE, "}"}, // 14
}
l := New(input)
for i, tt := range tests {
tok := l.NextToken()
if tok.Type != tt.expectedType {
t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q",
i, tt.expectedType, tok.Type)
}
if tok.Literal != tt.expectedLiteral {
t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q",
i, tt.expectedLiteral, tok.Literal)
}
}
}
// EMEINA STO:
// 30/232 selida apto making a interpeter in Go.
// den ekana to skipWhitespaces giati skeftomai
// an borei na to xreiastw 9a dw aurio.

View File

@@ -0,0 +1,180 @@
// Copyright 2017 Gerasimos Maropoulos. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package parser
import (
"fmt"
"strconv"
"strings"
"github.com/kataras/iris/core/router/macro/interpreter/ast"
"github.com/kataras/iris/core/router/macro/interpreter/lexer"
"github.com/kataras/iris/core/router/macro/interpreter/token"
)
func Parse(fullpath string) ([]*ast.ParamStatement, error) {
pathParts := strings.SplitN(fullpath, "/", -1)
p := new(ParamParser)
statements := make([]*ast.ParamStatement, 0)
for i, s := range pathParts {
if s == "" { // if starts with /
continue
}
// if it's not a named path parameter of the new syntax then continue to the next
if s[0] != lexer.Begin || s[len(s)-1] != lexer.End {
continue
}
p.Reset(s)
stmt, err := p.Parse()
if err != nil {
// exit on first error
return nil, err
}
// if we have param type path but it's not the last path part
if stmt.Type == ast.ParamTypePath && i < len(pathParts)-1 {
return nil, fmt.Errorf("param type 'path' should be lived only inside the last path segment, but was inside: %s", s)
}
statements = append(statements, stmt)
}
return statements, nil
}
type ParamParser struct {
src string
errors []string
}
func NewParamParser(src string) *ParamParser {
p := new(ParamParser)
p.Reset(src)
return p
}
func (p *ParamParser) Reset(src string) {
p.src = src
p.errors = []string{}
}
func (p *ParamParser) appendErr(format string, a ...interface{}) {
p.errors = append(p.errors, fmt.Sprintf(format, a...))
}
const DefaultParamErrorCode = 404
const DefaultParamType = ast.ParamTypeString
func parseParamFuncArg(t token.Token) (a ast.ParamFuncArg, err error) {
if t.Type == token.INT {
return ast.ParamFuncArgToInt(t.Literal)
}
return t.Literal, nil
}
func (p ParamParser) Error() error {
if len(p.errors) > 0 {
return fmt.Errorf(strings.Join(p.errors, "\n"))
}
return nil
}
func (p *ParamParser) Parse() (*ast.ParamStatement, error) {
l := lexer.New(p.src)
stmt := &ast.ParamStatement{
ErrorCode: DefaultParamErrorCode,
Type: DefaultParamType,
Src: p.src,
}
lastParamFunc := ast.ParamFunc{}
for {
t := l.NextToken()
if t.Type == token.EOF {
if stmt.Name == "" {
p.appendErr("[1:] parameter name is missing")
}
break
}
switch t.Type {
case token.LBRACE:
// name, alphabetical and _, param names are not allowed to contain any number.
nextTok := l.NextToken()
stmt.Name = nextTok.Literal
case token.COLON:
// type
nextTok := l.NextToken()
paramType := ast.LookupParamType(nextTok.Literal)
if paramType == ast.ParamTypeUnExpected {
p.appendErr("[%d:%d] unexpected parameter type: %s", t.Start, t.End, nextTok.Literal)
}
stmt.Type = paramType
// param func
case token.IDENT:
lastParamFunc.Name = t.Literal
case token.LPAREN:
// param function without arguments ()
if l.PeekNextTokenType() == token.RPAREN {
// do nothing, just continue to the RPAREN
continue
}
argValTok := l.NextDynamicToken() // catch anything from "(" and forward, until ")", because we need to
// be able to use regex expression as a macro type's func argument too.
argVal, err := parseParamFuncArg(argValTok)
if err != nil {
p.appendErr("[%d:%d] expected param func argument to be a string or number but got %s", t.Start, t.End, argValTok.Literal)
continue
}
// fmt.Printf("argValTok: %#v\n", argValTok)
// fmt.Printf("argVal: %#v\n", argVal)
lastParamFunc.Args = append(lastParamFunc.Args, argVal)
case token.COMMA:
argValTok := l.NextToken()
argVal, err := parseParamFuncArg(argValTok)
if err != nil {
p.appendErr("[%d:%d] expected param func argument to be a string or number type but got %s", t.Start, t.End, argValTok.Literal)
continue
}
lastParamFunc.Args = append(lastParamFunc.Args, argVal)
case token.RPAREN:
stmt.Funcs = append(stmt.Funcs, lastParamFunc)
lastParamFunc = ast.ParamFunc{} // reset
case token.ELSE:
errCodeTok := l.NextToken()
if errCodeTok.Type != token.INT {
p.appendErr("[%d:%d] expected error code to be an integer but got %s", t.Start, t.End, errCodeTok.Literal)
continue
}
errCode, err := strconv.Atoi(errCodeTok.Literal)
if err != nil {
// this is a bug on lexer if throws because we already check for token.INT
p.appendErr("[%d:%d] unexpected lexer error while trying to convert error code to an integer, %s", t.Start, t.End, err.Error())
continue
}
stmt.ErrorCode = errCode
case token.RBRACE:
// check if } but not {
if stmt.Name == "" {
p.appendErr("[%d:%d] illegal token: }, forgot '{' ?", t.Start, t.End)
}
break
case token.ILLEGAL:
p.appendErr("[%d:%d] illegal token: %s", t.Start, t.End, t.Literal)
default:
p.appendErr("[%d:%d] unexpected token type: %q with value %s", t.Start, t.End, t.Type, t.Literal)
}
}
return stmt, p.Error()
}

View File

@@ -0,0 +1,258 @@
// Copyright 2017 Gerasimos Maropoulos. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package parser
import (
"fmt"
"reflect"
"strings"
"testing"
"github.com/kataras/iris/core/router/macro/interpreter/ast"
)
func TestParseParamError(t *testing.T) {
// fail
illegalChar := '$'
input := "{id" + string(illegalChar) + "int range(1,5) else 404}"
p := NewParamParser(input)
_, err := p.Parse()
if err == nil {
t.Fatalf("expecting not empty error on input '%s'", input)
}
illIdx := strings.IndexRune(input, illegalChar)
expectedErr := fmt.Sprintf("[%d:%d] illegal token: %s", illIdx, illIdx, "$")
if got := err.Error(); got != expectedErr {
t.Fatalf("expecting error to be '%s' but got: %s", expectedErr, got)
}
//
// success
input2 := "{id:int range(1,5) else 404}"
p.Reset(input2)
_, err = p.Parse()
if err != nil {
t.Fatalf("expecting empty error on input '%s', but got: %s", input2, err.Error())
}
//
}
func TestParseParam(t *testing.T) {
tests := []struct {
valid bool
expectedStatement ast.ParamStatement
}{
{true,
ast.ParamStatement{
Src: "{id:int min(1) max(5) else 404}",
Name: "id",
Type: ast.ParamTypeInt,
Funcs: []ast.ParamFunc{
{
Name: "min",
Args: []ast.ParamFuncArg{1}},
{
Name: "max",
Args: []ast.ParamFuncArg{5}},
},
ErrorCode: 404,
}}, // 0
{true,
ast.ParamStatement{
Src: "{id:int range(1,5)}",
Name: "id",
Type: ast.ParamTypeInt,
Funcs: []ast.ParamFunc{
{
Name: "range",
Args: []ast.ParamFuncArg{1, 5}},
},
ErrorCode: 404,
}}, // 1
{true,
ast.ParamStatement{
Src: "{file:path contains(.)}",
Name: "file",
Type: ast.ParamTypePath,
Funcs: []ast.ParamFunc{
{
Name: "contains",
Args: []ast.ParamFuncArg{"."}},
},
ErrorCode: 404,
}}, // 2
{true,
ast.ParamStatement{
Src: "{username:alphabetical}",
Name: "username",
Type: ast.ParamTypeAlphabetical,
ErrorCode: 404,
}}, // 3
{true,
ast.ParamStatement{
Src: "{myparam}",
Name: "myparam",
Type: ast.ParamTypeString,
ErrorCode: 404,
}}, // 4
{false,
ast.ParamStatement{
Src: "{myparam_:thisianunexpected}",
Name: "myparam_",
Type: ast.ParamTypeUnExpected,
ErrorCode: 404,
}}, // 5
{false, // false because it will give an error of unexpeced token type with value 2
ast.ParamStatement{
Src: "{myparam2}",
Name: "myparam", // expected "myparam" because we don't allow integers to the parameter names.
Type: ast.ParamTypeString,
ErrorCode: 404,
}}, // 6
{true,
ast.ParamStatement{
Src: "{id:int even()}", // test param funcs without any arguments (LPAREN peek for RPAREN)
Name: "id",
Type: ast.ParamTypeInt,
Funcs: []ast.ParamFunc{
{
Name: "even"},
},
ErrorCode: 404,
}}, // 7
}
var p *ParamParser = new(ParamParser)
for i, tt := range tests {
p.Reset(tt.expectedStatement.Src)
resultStmt, err := p.Parse()
if tt.valid && err != nil {
t.Fatalf("tests[%d] - error %s", i, err.Error())
} else if !tt.valid && err == nil {
t.Fatalf("tests[%d] - expected to be a failure", i)
}
if resultStmt != nil { // is valid here
if !reflect.DeepEqual(tt.expectedStatement, *resultStmt) {
t.Fatalf("tests[%d] - wrong statement, expected and result differs. Details:\n%#v\n%#v", i, tt.expectedStatement, *resultStmt)
}
}
}
}
func TestParse(t *testing.T) {
tests := []struct {
path string
valid bool
expectedStatements []ast.ParamStatement
}{
{"/api/users/{id:int min(1) max(5) else 404}", true,
[]ast.ParamStatement{{
Src: "{id:int min(1) max(5) else 404}",
Name: "id",
Type: ast.ParamTypeInt,
Funcs: []ast.ParamFunc{
{
Name: "min",
Args: []ast.ParamFuncArg{1}},
{
Name: "max",
Args: []ast.ParamFuncArg{5}},
},
ErrorCode: 404,
},
}}, // 0
{"/admin/{id:int range(1,5)}", true,
[]ast.ParamStatement{{
Src: "{id:int range(1,5)}",
Name: "id",
Type: ast.ParamTypeInt,
Funcs: []ast.ParamFunc{
{
Name: "range",
Args: []ast.ParamFuncArg{1, 5}},
},
ErrorCode: 404,
},
}}, // 1
{"/files/{file:path contains(.)}", true,
[]ast.ParamStatement{{
Src: "{file:path contains(.)}",
Name: "file",
Type: ast.ParamTypePath,
Funcs: []ast.ParamFunc{
{
Name: "contains",
Args: []ast.ParamFuncArg{"."}},
},
ErrorCode: 404,
},
}}, // 2
{"/profile/{username:alphabetical}", true,
[]ast.ParamStatement{{
Src: "{username:alphabetical}",
Name: "username",
Type: ast.ParamTypeAlphabetical,
ErrorCode: 404,
},
}}, // 3
{"/something/here/{myparam}", true,
[]ast.ParamStatement{{
Src: "{myparam}",
Name: "myparam",
Type: ast.ParamTypeString,
ErrorCode: 404,
},
}}, // 4
{"/unexpected/{myparam_:thisianunexpected}", false,
[]ast.ParamStatement{{
Src: "{myparam_:thisianunexpected}",
Name: "myparam_",
Type: ast.ParamTypeUnExpected,
ErrorCode: 404,
},
}}, // 5
{"/p2/{myparam2}", false, // false because it will give an error of unexpeced token type with value 2
[]ast.ParamStatement{{
Src: "{myparam2}",
Name: "myparam", // expected "myparam" because we don't allow integers to the parameter names.
Type: ast.ParamTypeString,
ErrorCode: 404,
},
}}, // 6
{"/assets/{file:path}/invalid", false, // path should be in the end segment
[]ast.ParamStatement{{
Src: "{file:path}",
Name: "file",
Type: ast.ParamTypePath,
ErrorCode: 404,
},
}}, // 7
}
for i, tt := range tests {
statements, err := Parse(tt.path)
if tt.valid && err != nil {
t.Fatalf("tests[%d] - error %s", i, err.Error())
} else if !tt.valid && err == nil {
t.Fatalf("tests[%d] - expected to be a failure", i)
}
for j := range statements {
for l := range tt.expectedStatements {
if !reflect.DeepEqual(tt.expectedStatements[l], *statements[j]) {
t.Fatalf("tests[%d] - wrong statements, expected and result differs. Details:\n%#v\n%#v", i, tt.expectedStatements[l], *statements[j])
}
}
}
}
}

View File

@@ -0,0 +1,53 @@
// Copyright 2017 Gerasimos Maropoulos. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package token
type TokenType int
type Token struct {
Type TokenType
Literal string
Start int // including the first char
End int // including the last char
}
// /about/{fullname:alphabetical}
// /profile/{anySpecialName:string}
// {id:int range(1,5) else 404}
// /admin/{id:int eq(1) else 402}
// /file/{filepath:file else 405}
const (
EOF = iota // 0
ILLEGAL
// Identifiers + literals
LBRACE // {
RBRACE // }
// PARAM_IDENTIFIER // id
COLON // :
LPAREN // (
RPAREN // )
// PARAM_FUNC_ARG // 1
COMMA
IDENT // string or keyword
// Keywords
keywords_start
ELSE // else
keywords_end
INT // 42
)
const eof rune = 0
var keywords = map[string]TokenType{
"else": ELSE,
}
func LookupIdent(ident string) TokenType {
if tok, ok := keywords[ident]; ok {
return tok
}
return IDENT
}

229
core/router/macro/macro.go Normal file
View File

@@ -0,0 +1,229 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package macro
import (
"fmt"
"reflect"
"regexp"
"unicode"
"github.com/kataras/iris/core/router/macro/interpreter/ast"
)
// final evaluator signature for both param types and param funcs
type EvaluatorFunc func(paramValue string) bool
func NewEvaluatorFromRegexp(expr string) (EvaluatorFunc, error) {
if expr == "" {
return nil, fmt.Errorf("empty regex expression")
}
// add the last $ if missing (and not wildcard(?))
if i := expr[len(expr)-1]; i != '$' && i != '*' {
expr += "$"
}
r, err := regexp.Compile(expr)
if err != nil {
return nil, err
}
return r.MatchString, nil
}
func MustNewEvaluatorFromRegexp(expr string) EvaluatorFunc {
r, err := NewEvaluatorFromRegexp(expr)
if err != nil {
panic(err)
}
return r
}
var (
goodParamFuncReturnType = reflect.TypeOf(func(string) bool { return false })
goodParamFuncReturnType2 = reflect.TypeOf(EvaluatorFunc(func(string) bool { return false }))
)
func goodParamFunc(typ reflect.Type) bool {
// should be a func
// which returns a func(string) bool
if typ.Kind() == reflect.Func {
if typ.NumOut() == 1 {
typOut := typ.Out(0)
if typOut == goodParamFuncReturnType || typOut == goodParamFuncReturnType2 {
return true
}
}
}
return false
}
// goodParamFuncName reports whether the function name is a valid identifier.
func goodParamFuncName(name string) bool {
if name == "" {
return false
}
// valid names are only letters and _
for _, r := range name {
switch {
case r == '_':
case !unicode.IsLetter(r):
return false
}
}
return true
}
// the convertBuilderFunc return value is generating at boot time.
// convertFunc converts an interface to a valid full param function.
func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder {
typFn := reflect.TypeOf(fn)
if !goodParamFunc(typFn) {
return nil
}
numFields := typFn.NumIn()
return func(args []ast.ParamFuncArg) EvaluatorFunc {
if len(args) != numFields {
// no variadics support, for now.
panic("args should be the same len as numFields")
}
var argValues []reflect.Value
for i := 0; i < numFields; i++ {
field := typFn.In(i)
arg := args[i]
if field.Kind() != reflect.TypeOf(arg).Kind() {
panic("fields should have the same type")
}
argValues = append(argValues, reflect.ValueOf(arg))
}
evalFn := reflect.ValueOf(fn).Call(argValues)[0].Interface()
var evaluator EvaluatorFunc
// check for typed and not typed
if _v, ok := evalFn.(EvaluatorFunc); ok {
evaluator = _v
} else if _v, ok = evalFn.(func(string) bool); ok {
evaluator = _v
}
return func(paramValue string) bool {
return evaluator(paramValue)
}
}
}
type (
Macro struct {
Evaluator EvaluatorFunc
funcs []ParamFunc
}
ParamEvaluatorBuilder func([]ast.ParamFuncArg) EvaluatorFunc
ParamFunc struct {
Name string
Func ParamEvaluatorBuilder
}
)
func newMacro(evaluator EvaluatorFunc) *Macro {
return &Macro{Evaluator: evaluator}
}
// at boot time, per param
func (m *Macro) RegisterFunc(funcName string, fn interface{}) {
fullFn := convertBuilderFunc(fn)
m.registerFunc(funcName, fullFn)
}
func (m *Macro) registerFunc(funcName string, fullFn ParamEvaluatorBuilder) {
if !goodParamFuncName(funcName) {
return
}
for _, fn := range m.funcs {
if fn.Name == funcName {
fn.Func = fullFn
return
}
}
m.funcs = append(m.funcs, ParamFunc{
Name: funcName,
Func: fullFn,
})
}
func (m *Macro) getFunc(funcName string) ParamEvaluatorBuilder {
for _, fn := range m.funcs {
if fn.Name == funcName {
if fn.Func == nil {
continue
}
return fn.Func
}
}
return nil
}
type MacroMap struct {
// string type
// anything
String *Macro
// int type
// only numbers (0-9)
Int *Macro
// alphabetical/letter type
// letters only (upper or lowercase)
Alphabetical *Macro
// file type
// letters (upper or lowercase)
// numbers (0-9)
// underscore (_)
// dash (-)
// point (.)
// no spaces! or other character
File *Macro
// path type
// anything, should be the last part
Path *Macro
}
func NewMacroMap() *MacroMap {
return &MacroMap{
// it allows everything, so no need for a regexp here.
String: newMacro(func(string) bool { return true }),
Int: newMacro(MustNewEvaluatorFromRegexp("^[0-9]+$")),
Alphabetical: newMacro(MustNewEvaluatorFromRegexp("^[a-zA-Z ]+$")),
File: newMacro(MustNewEvaluatorFromRegexp("^[a-zA-Z0-9_.-]*$")),
// it allows everything, we have String and Path as different
// types because I want to give the opportunity to the user
// to organise the macro functions based on wildcard or single dynamic named path parameter.
// Should be the last.
Path: newMacro(func(string) bool { return true }),
}
}
func (m *MacroMap) Lookup(typ ast.ParamType) *Macro {
switch typ {
case ast.ParamTypeInt:
return m.Int
case ast.ParamTypeAlphabetical:
return m.Alphabetical
case ast.ParamTypeFile:
return m.File
case ast.ParamTypePath:
return m.Path
default:
return m.String
}
}

View File

@@ -0,0 +1,190 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package macro
import (
"reflect"
"testing"
)
// Most important tests to look:
// ../parser/parser_test.go
// ../lexer/lexer_test.go
func TestGoodParamFunc(t *testing.T) {
good1 := func(min int, max int) func(string) bool {
return func(paramValue string) bool {
return true
}
}
good2 := func(min int, max int) func(string) bool {
return func(paramValue string) bool {
return true
}
}
notgood1 := func(min int, max int) bool {
return false
}
if !goodParamFunc(reflect.TypeOf(good1)) {
t.Fatalf("expected good1 func to be good but it's not")
}
if !goodParamFunc(reflect.TypeOf(good2)) {
t.Fatalf("expected good2 func to be good but it's not")
}
if goodParamFunc(reflect.TypeOf(notgood1)) {
t.Fatalf("expected notgood1 func to be the worst")
}
}
func TestGoodParamFuncName(t *testing.T) {
tests := []struct {
name string
good bool
}{
{"range", true},
{"_range", true},
{"range_", true},
{"r_ange", true},
// numbers or other symbols are invalid.
{"range1", false},
{"2range", false},
{"r@nge", false},
{"rang3", false},
}
for i, tt := range tests {
isGood := goodParamFuncName(tt.name)
if tt.good && !isGood {
t.Fatalf("tests[%d] - expecting valid name but got invalid for name %s", i, tt.name)
} else if !tt.good && isGood {
t.Fatalf("tests[%d] - expecting invalid name but got valid for name %s", i, tt.name)
}
}
}
func testEvaluatorRaw(macroEvaluator *Macro, input string, pass bool, i int, t *testing.T) {
if got := macroEvaluator.Evaluator(input); pass != got {
t.Fatalf("tests[%d] - expecting %v but got %v", i, pass, got)
}
}
func TestStringEvaluatorRaw(t *testing.T) {
f := NewMacroMap()
tests := []struct {
pass bool
input string
}{
{true, "astring"}, // 0
{true, "astringwith_numb3rS_and_symbol$"}, // 1
{true, "32321"}, // 2
{true, "main.css"}, // 3
{true, "/assets/main.css"}, // 4
// false never
} // 0
for i, tt := range tests {
testEvaluatorRaw(f.String, tt.input, tt.pass, i, t)
}
}
func TestIntEvaluatorRaw(t *testing.T) {
f := NewMacroMap()
tests := []struct {
pass bool
input string
}{
{false, "astring"}, // 0
{false, "astringwith_numb3rS_and_symbol$"}, // 1
{true, "32321"}, // 2
{false, "main.css"}, // 3
{false, "/assets/main.css"}, // 4
}
for i, tt := range tests {
testEvaluatorRaw(f.Int, tt.input, tt.pass, i, t)
}
}
func TestAlphabeticalEvaluatorRaw(t *testing.T) {
f := NewMacroMap()
tests := []struct {
pass bool
input string
}{
{true, "astring"}, // 0
{false, "astringwith_numb3rS_and_symbol$"}, // 1
{false, "32321"}, // 2
{false, "main.css"}, // 3
{false, "/assets/main.css"}, // 4
}
for i, tt := range tests {
testEvaluatorRaw(f.Alphabetical, tt.input, tt.pass, i, t)
}
}
func TestFileEvaluatorRaw(t *testing.T) {
f := NewMacroMap()
tests := []struct {
pass bool
input string
}{
{true, "astring"}, // 0
{false, "astringwith_numb3rS_and_symbol$"}, // 1
{true, "32321"}, // 2
{true, "main.css"}, // 3
{false, "/assets/main.css"}, // 4
}
for i, tt := range tests {
testEvaluatorRaw(f.File, tt.input, tt.pass, i, t)
}
}
func TestPathEvaluatorRaw(t *testing.T) {
f := NewMacroMap()
pathTests := []struct {
pass bool
input string
}{
{true, "astring"}, // 0
{true, "astringwith_numb3rS_and_symbol$"}, // 1
{true, "32321"}, // 2
{true, "main.css"}, // 3
{true, "/assets/main.css"}, // 4
{true, "disk/assets/main.css"}, // 5
}
for i, tt := range pathTests {
testEvaluatorRaw(f.Path, tt.input, tt.pass, i, t)
}
}
// func TestMapRegisterFunc(t *testing.T) {
// m := NewMacroMap()
// m.String.RegisterFunc("prefix", func(prefix string) EvaluatorFunc {
// return func(paramValue string) bool {
// return strings.HasPrefix(paramValue, prefix)
// }
// })
// p, err := Parse("/user/@kataras")
// if err != nil {
// t.Fatalf(err)
// }
// // p.Params = append(p.)
// testEvaluatorRaw(m.String, p.Src, false, 0, t)
// }

View File

@@ -0,0 +1,67 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package macro
import (
"github.com/kataras/iris/core/router/macro/interpreter/ast"
"github.com/kataras/iris/core/router/macro/interpreter/parser"
)
type Template struct {
// Src is the original template given by the client
Src string
Params []TemplateParam
}
type TemplateParam struct {
Src string // the unparsed param'false source
// Type is not useful anywhere here but maybe
// it's useful on host to decide how to convert the path template to specific router's syntax
Type ast.ParamType
Name string
ErrCode int
TypeEvaluator EvaluatorFunc
Funcs []EvaluatorFunc
}
func Parse(src string, macros *MacroMap) (*Template, error) {
params, err := parser.Parse(src)
if err != nil {
return nil, err
}
t := new(Template)
t.Src = src
for _, p := range params {
funcMap := macros.Lookup(p.Type)
typEval := funcMap.Evaluator
tmplParam := TemplateParam{
Src: p.Src,
Type: p.Type,
Name: p.Name,
ErrCode: p.ErrorCode,
TypeEvaluator: typEval,
}
for _, paramfn := range p.Funcs {
tmplFn := funcMap.getFunc(paramfn.Name)
if tmplFn == nil { // if not find on this type, check for String's which is for global funcs too
tmplFn = macros.String.getFunc(paramfn.Name)
if tmplFn == nil { // if not found then just skip this param
continue
}
}
evalFn := tmplFn(paramfn.Args)
if evalFn == nil {
continue
}
tmplParam.Funcs = append(tmplParam.Funcs, evalFn)
}
t.Params = append(t.Params, tmplParam)
}
return t, nil
}

596
core/router/mime.go Normal file
View File

@@ -0,0 +1,596 @@
package router
import (
"mime"
"path/filepath"
)
var types = map[string]string{
".3dm": "x-world/x-3dmf",
".3dmf": "x-world/x-3dmf",
".7z": "application/x-7z-compressed",
".a": "application/octet-stream",
".aab": "application/x-authorware-bin",
".aam": "application/x-authorware-map",
".aas": "application/x-authorware-seg",
".abc": "text/vndabc",
".ace": "application/x-ace-compressed",
".acgi": "text/html",
".afl": "video/animaflex",
".ai": "application/postscript",
".aif": "audio/aiff",
".aifc": "audio/aiff",
".aiff": "audio/aiff",
".aim": "application/x-aim",
".aip": "text/x-audiosoft-intra",
".alz": "application/x-alz-compressed",
".ani": "application/x-navi-animation",
".aos": "application/x-nokia-9000-communicator-add-on-software",
".aps": "application/mime",
".apk": "application/vnd.android.package-archive",
".arc": "application/x-arc-compressed",
".arj": "application/arj",
".art": "image/x-jg",
".asf": "video/x-ms-asf",
".asm": "text/x-asm",
".asp": "text/asp",
".asx": "application/x-mplayer2",
".au": "audio/basic",
".avi": "video/x-msvideo",
".avs": "video/avs-video",
".bcpio": "application/x-bcpio",
".bin": "application/mac-binary",
".bmp": "image/bmp",
".boo": "application/book",
".book": "application/book",
".boz": "application/x-bzip2",
".bsh": "application/x-bsh",
".bz2": "application/x-bzip2",
".bz": "application/x-bzip",
".c++": "text/plain",
".c": "text/x-c",
".cab": "application/vnd.ms-cab-compressed",
".cat": "application/vndms-pkiseccat",
".cc": "text/x-c",
".ccad": "application/clariscad",
".cco": "application/x-cocoa",
".cdf": "application/cdf",
".cer": "application/pkix-cert",
".cha": "application/x-chat",
".chat": "application/x-chat",
".chrt": "application/vnd.kde.kchart",
".class": "application/java",
".com": "text/plain",
".conf": "text/plain",
".cpio": "application/x-cpio",
".cpp": "text/x-c",
".cpt": "application/mac-compactpro",
".crl": "application/pkcs-crl",
".crt": "application/pkix-cert",
".crx": "application/x-chrome-extension",
".csh": "text/x-scriptcsh",
".css": "text/css",
".csv": "text/csv",
".cxx": "text/plain",
".dar": "application/x-dar",
".dcr": "application/x-director",
".deb": "application/x-debian-package",
".deepv": "application/x-deepv",
".def": "text/plain",
".der": "application/x-x509-ca-cert",
".dif": "video/x-dv",
".dir": "application/x-director",
".divx": "video/divx",
".dl": "video/dl",
".dmg": "application/x-apple-diskimage",
".doc": "application/msword",
".dot": "application/msword",
".dp": "application/commonground",
".drw": "application/drafting",
".dump": "application/octet-stream",
".dv": "video/x-dv",
".dvi": "application/x-dvi",
".dwf": "drawing/x-dwf=(old)",
".dwg": "application/acad",
".dxf": "application/dxf",
".dxr": "application/x-director",
".el": "text/x-scriptelisp",
".elc": "application/x-bytecodeelisp=(compiled=elisp)",
".eml": "message/rfc822",
".env": "application/x-envoy",
".eps": "application/postscript",
".es": "application/x-esrehber",
".etx": "text/x-setext",
".evy": "application/envoy",
".exe": "application/octet-stream",
".f77": "text/x-fortran",
".f90": "text/x-fortran",
".f": "text/x-fortran",
".fdf": "application/vndfdf",
".fif": "application/fractals",
".fli": "video/fli",
".flo": "image/florian",
".flv": "video/x-flv",
".flx": "text/vndfmiflexstor",
".fmf": "video/x-atomic3d-feature",
".for": "text/x-fortran",
".fpx": "image/vndfpx",
".frl": "application/freeloader",
".funk": "audio/make",
".g3": "image/g3fax",
".g": "text/plain",
".gif": "image/gif",
".gl": "video/gl",
".gsd": "audio/x-gsm",
".gsm": "audio/x-gsm",
".gsp": "application/x-gsp",
".gss": "application/x-gss",
".gtar": "application/x-gtar",
".gz": "application/x-compressed",
".gzip": "application/x-gzip",
".h": "text/x-h",
".hdf": "application/x-hdf",
".help": "application/x-helpfile",
".hgl": "application/vndhp-hpgl",
".hh": "text/x-h",
".hlb": "text/x-script",
".hlp": "application/hlp",
".hpg": "application/vndhp-hpgl",
".hpgl": "application/vndhp-hpgl",
".hqx": "application/binhex",
".hta": "application/hta",
".htc": "text/x-component",
".htm": "text/html",
".html": "text/html",
".htmls": "text/html",
".htt": "text/webviewhtml",
".htx": "text/html",
".ice": "x-conference/x-cooltalk",
".ico": "image/x-icon",
".ics": "text/calendar",
".icz": "text/calendar",
".idc": "text/plain",
".ief": "image/ief",
".iefs": "image/ief",
".iges": "application/iges",
".igs": "application/iges",
".ima": "application/x-ima",
".imap": "application/x-httpd-imap",
".inf": "application/inf",
".ins": "application/x-internett-signup",
".ip": "application/x-ip2",
".isu": "video/x-isvideo",
".it": "audio/it",
".iv": "application/x-inventor",
".ivr": "i-world/i-vrml",
".ivy": "application/x-livescreen",
".jam": "audio/x-jam",
".jav": "text/x-java-source",
".java": "text/x-java-source",
".jcm": "application/x-java-commerce",
".jfif-tbnl": "image/jpeg",
".jfif": "image/jpeg",
".jnlp": "application/x-java-jnlp-file",
".jpe": "image/jpeg",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".jps": "image/x-jps",
".js": "application/javascript",
".json": "application/json",
".jut": "image/jutvision",
".kar": "audio/midi",
".karbon": "application/vnd.kde.karbon",
".kfo": "application/vnd.kde.kformula",
".flw": "application/vnd.kde.kivio",
".kml": "application/vnd.google-earth.kml+xml",
".kmz": "application/vnd.google-earth.kmz",
".kon": "application/vnd.kde.kontour",
".kpr": "application/vnd.kde.kpresenter",
".kpt": "application/vnd.kde.kpresenter",
".ksp": "application/vnd.kde.kspread",
".kwd": "application/vnd.kde.kword",
".kwt": "application/vnd.kde.kword",
".ksh": "text/x-scriptksh",
".la": "audio/nspaudio",
".lam": "audio/x-liveaudio",
".latex": "application/x-latex",
".lha": "application/lha",
".lhx": "application/octet-stream",
".list": "text/plain",
".lma": "audio/nspaudio",
".log": "text/plain",
".lsp": "text/x-scriptlisp",
".lst": "text/plain",
".lsx": "text/x-la-asf",
".ltx": "application/x-latex",
".lzh": "application/octet-stream",
".lzx": "application/lzx",
".m1v": "video/mpeg",
".m2a": "audio/mpeg",
".m2v": "video/mpeg",
".m3u": "audio/x-mpegurl",
".m": "text/x-m",
".man": "application/x-troff-man",
".manifest": "text/cache-manifest",
".map": "application/x-navimap",
".mar": "text/plain",
".mbd": "application/mbedlet",
".mc$": "application/x-magic-cap-package-10",
".mcd": "application/mcad",
".mcf": "text/mcf",
".mcp": "application/netmc",
".me": "application/x-troff-me",
".mht": "message/rfc822",
".mhtml": "message/rfc822",
".mid": "application/x-midi",
".midi": "application/x-midi",
".mif": "application/x-frame",
".mime": "message/rfc822",
".mjf": "audio/x-vndaudioexplosionmjuicemediafile",
".mjpg": "video/x-motion-jpeg",
".mm": "application/base64",
".mme": "application/base64",
".mod": "audio/mod",
".moov": "video/quicktime",
".mov": "video/quicktime",
".movie": "video/x-sgi-movie",
".mp2": "audio/mpeg",
".mp3": "audio/mpeg3",
".mp4": "video/mp4",
".mpa": "audio/mpeg",
".mpc": "application/x-project",
".mpe": "video/mpeg",
".mpeg": "video/mpeg",
".mpg": "video/mpeg",
".mpga": "audio/mpeg",
".mpp": "application/vndms-project",
".mpt": "application/x-project",
".mpv": "application/x-project",
".mpx": "application/x-project",
".mrc": "application/marc",
".ms": "application/x-troff-ms",
".mv": "video/x-sgi-movie",
".my": "audio/make",
".mzz": "application/x-vndaudioexplosionmzz",
".nap": "image/naplps",
".naplps": "image/naplps",
".nc": "application/x-netcdf",
".ncm": "application/vndnokiaconfiguration-message",
".nif": "image/x-niff",
".niff": "image/x-niff",
".nix": "application/x-mix-transfer",
".nsc": "application/x-conference",
".nvd": "application/x-navidoc",
".o": "application/octet-stream",
".oda": "application/oda",
".odb": "application/vnd.oasis.opendocument.database",
".odc": "application/vnd.oasis.opendocument.chart",
".odf": "application/vnd.oasis.opendocument.formula",
".odg": "application/vnd.oasis.opendocument.graphics",
".odi": "application/vnd.oasis.opendocument.image",
".odm": "application/vnd.oasis.opendocument.text-master",
".odp": "application/vnd.oasis.opendocument.presentation",
".ods": "application/vnd.oasis.opendocument.spreadsheet",
".odt": "application/vnd.oasis.opendocument.text",
".oga": "audio/ogg",
".ogg": "audio/ogg",
".ogv": "video/ogg",
".omc": "application/x-omc",
".omcd": "application/x-omcdatamaker",
".omcr": "application/x-omcregerator",
".otc": "application/vnd.oasis.opendocument.chart-template",
".otf": "application/vnd.oasis.opendocument.formula-template",
".otg": "application/vnd.oasis.opendocument.graphics-template",
".oth": "application/vnd.oasis.opendocument.text-web",
".oti": "application/vnd.oasis.opendocument.image-template",
".otm": "application/vnd.oasis.opendocument.text-master",
".otp": "application/vnd.oasis.opendocument.presentation-template",
".ots": "application/vnd.oasis.opendocument.spreadsheet-template",
".ott": "application/vnd.oasis.opendocument.text-template",
".p10": "application/pkcs10",
".p12": "application/pkcs-12",
".p7a": "application/x-pkcs7-signature",
".p7c": "application/pkcs7-mime",
".p7m": "application/pkcs7-mime",
".p7r": "application/x-pkcs7-certreqresp",
".p7s": "application/pkcs7-signature",
".p": "text/x-pascal",
".part": "application/pro_eng",
".pas": "text/pascal",
".pbm": "image/x-portable-bitmap",
".pcl": "application/vndhp-pcl",
".pct": "image/x-pict",
".pcx": "image/x-pcx",
".pdb": "chemical/x-pdb",
".pdf": "application/pdf",
".pfunk": "audio/make",
".pgm": "image/x-portable-graymap",
".pic": "image/pict",
".pict": "image/pict",
".pkg": "application/x-newton-compatible-pkg",
".pko": "application/vndms-pkipko",
".pl": "text/x-scriptperl",
".plx": "application/x-pixclscript",
".pm4": "application/x-pagemaker",
".pm5": "application/x-pagemaker",
".pm": "text/x-scriptperl-module",
".png": "image/png",
".pnm": "application/x-portable-anymap",
".pot": "application/mspowerpoint",
".pov": "model/x-pov",
".ppa": "application/vndms-powerpoint",
".ppm": "image/x-portable-pixmap",
".pps": "application/mspowerpoint",
".ppt": "application/mspowerpoint",
".ppz": "application/mspowerpoint",
".pre": "application/x-freelance",
".prt": "application/pro_eng",
".ps": "application/postscript",
".psd": "application/octet-stream",
".pvu": "paleovu/x-pv",
".pwz": "application/vndms-powerpoint",
".py": "text/x-scriptphyton",
".pyc": "application/x-bytecodepython",
".qcp": "audio/vndqcelp",
".qd3": "x-world/x-3dmf",
".qd3d": "x-world/x-3dmf",
".qif": "image/x-quicktime",
".qt": "video/quicktime",
".qtc": "video/x-qtc",
".qti": "image/x-quicktime",
".qtif": "image/x-quicktime",
".ra": "audio/x-pn-realaudio",
".ram": "audio/x-pn-realaudio",
".rar": "application/x-rar-compressed",
".ras": "application/x-cmu-raster",
".rast": "image/cmu-raster",
".rexx": "text/x-scriptrexx",
".rf": "image/vndrn-realflash",
".rgb": "image/x-rgb",
".rm": "application/vndrn-realmedia",
".rmi": "audio/mid",
".rmm": "audio/x-pn-realaudio",
".rmp": "audio/x-pn-realaudio",
".rng": "application/ringing-tones",
".rnx": "application/vndrn-realplayer",
".roff": "application/x-troff",
".rp": "image/vndrn-realpix",
".rpm": "audio/x-pn-realaudio-plugin",
".rt": "text/vndrn-realtext",
".rtf": "text/richtext",
".rtx": "text/richtext",
".rv": "video/vndrn-realvideo",
".s": "text/x-asm",
".s3m": "audio/s3m",
".s7z": "application/x-7z-compressed",
".saveme": "application/octet-stream",
".sbk": "application/x-tbook",
".scm": "text/x-scriptscheme",
".sdml": "text/plain",
".sdp": "application/sdp",
".sdr": "application/sounder",
".sea": "application/sea",
".set": "application/set",
".sgm": "text/x-sgml",
".sgml": "text/x-sgml",
".sh": "text/x-scriptsh",
".shar": "application/x-bsh",
".shtml": "text/x-server-parsed-html",
".sid": "audio/x-psid",
".skd": "application/x-koan",
".skm": "application/x-koan",
".skp": "application/x-koan",
".skt": "application/x-koan",
".sit": "application/x-stuffit",
".sitx": "application/x-stuffitx",
".sl": "application/x-seelogo",
".smi": "application/smil",
".smil": "application/smil",
".snd": "audio/basic",
".sol": "application/solids",
".spc": "text/x-speech",
".spl": "application/futuresplash",
".spr": "application/x-sprite",
".sprite": "application/x-sprite",
".spx": "audio/ogg",
".src": "application/x-wais-source",
".ssi": "text/x-server-parsed-html",
".ssm": "application/streamingmedia",
".sst": "application/vndms-pkicertstore",
".step": "application/step",
".stl": "application/sla",
".stp": "application/step",
".sv4cpio": "application/x-sv4cpio",
".sv4crc": "application/x-sv4crc",
".svf": "image/vnddwg",
".svg": "image/svg+xml",
".svr": "application/x-world",
".swf": "application/x-shockwave-flash",
".t": "application/x-troff",
".talk": "text/x-speech",
".tar": "application/x-tar",
".tbk": "application/toolbook",
".tcl": "text/x-scripttcl",
".tcsh": "text/x-scripttcsh",
".tex": "application/x-tex",
".texi": "application/x-texinfo",
".texinfo": "application/x-texinfo",
".text": "text/plain",
".tgz": "application/gnutar",
".tif": "image/tiff",
".tiff": "image/tiff",
".tr": "application/x-troff",
".tsi": "audio/tsp-audio",
".tsp": "application/dsptype",
".tsv": "text/tab-separated-values",
".turbot": "image/florian",
".txt": "text/plain",
".uil": "text/x-uil",
".uni": "text/uri-list",
".unis": "text/uri-list",
".unv": "application/i-deas",
".uri": "text/uri-list",
".uris": "text/uri-list",
".ustar": "application/x-ustar",
".uu": "text/x-uuencode",
".uue": "text/x-uuencode",
".vcd": "application/x-cdlink",
".vcf": "text/x-vcard",
".vcard": "text/x-vcard",
".vcs": "text/x-vcalendar",
".vda": "application/vda",
".vdo": "video/vdo",
".vew": "application/groupwise",
".viv": "video/vivo",
".vivo": "video/vivo",
".vmd": "application/vocaltec-media-desc",
".vmf": "application/vocaltec-media-file",
".voc": "audio/voc",
".vos": "video/vosaic",
".vox": "audio/voxware",
".vqe": "audio/x-twinvq-plugin",
".vqf": "audio/x-twinvq",
".vql": "audio/x-twinvq-plugin",
".vrml": "application/x-vrml",
".vrt": "x-world/x-vrt",
".vsd": "application/x-visio",
".vst": "application/x-visio",
".vsw": "application/x-visio",
".w60": "application/wordperfect60",
".w61": "application/wordperfect61",
".w6w": "application/msword",
".wav": "audio/wav",
".wb1": "application/x-qpro",
".wbmp": "image/vnd.wap.wbmp",
".web": "application/vndxara",
".wiz": "application/msword",
".wk1": "application/x-123",
".wmf": "windows/metafile",
".wml": "text/vnd.wap.wml",
".wmlc": "application/vnd.wap.wmlc",
".wmls": "text/vnd.wap.wmlscript",
".wmlsc": "application/vnd.wap.wmlscriptc",
".word": "application/msword",
".wp5": "application/wordperfect",
".wp6": "application/wordperfect",
".wp": "application/wordperfect",
".wpd": "application/wordperfect",
".wq1": "application/x-lotus",
".wri": "application/mswrite",
".wrl": "application/x-world",
".wrz": "model/vrml",
".wsc": "text/scriplet",
".wsrc": "application/x-wais-source",
".wtk": "application/x-wintalk",
".x-png": "image/png",
".xbm": "image/x-xbitmap",
".xdr": "video/x-amt-demorun",
".xgz": "xgl/drawing",
".xif": "image/vndxiff",
".xl": "application/excel",
".xla": "application/excel",
".xlb": "application/excel",
".xlc": "application/excel",
".xld": "application/excel",
".xlk": "application/excel",
".xll": "application/excel",
".xlm": "application/excel",
".xls": "application/excel",
".xlt": "application/excel",
".xlv": "application/excel",
".xlw": "application/excel",
".xm": "audio/xm",
".xml": "text/xml",
".xmz": "xgl/movie",
".xpix": "application/x-vndls-xpix",
".xpm": "image/x-xpixmap",
".xsr": "video/x-amt-showrun",
".xwd": "image/x-xwd",
".xyz": "chemical/x-pdb",
".z": "application/x-compress",
".zip": "application/zip",
".zoo": "application/octet-stream",
".zsh": "text/x-scriptzsh",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".docm": "application/vnd.ms-word.document.macroEnabled.12",
".dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
".dotm": "application/vnd.ms-word.template.macroEnabled.12",
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".xlsm": "application/vnd.ms-excel.sheet.macroEnabled.12",
".xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
".xltm": "application/vnd.ms-excel.template.macroEnabled.12",
".xlsb": "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
".xlam": "application/vnd.ms-excel.addin.macroEnabled.12",
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".pptm": "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
".ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
".ppsm": "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
".potx": "application/vnd.openxmlformats-officedocument.presentationml.template",
".potm": "application/vnd.ms-powerpoint.template.macroEnabled.12",
".ppam": "application/vnd.ms-powerpoint.addin.macroEnabled.12",
".sldx": "application/vnd.openxmlformats-officedocument.presentationml.slide",
".sldm": "application/vnd.ms-powerpoint.slide.macroEnabled.12",
".thmx": "application/vnd.ms-officetheme",
".onetoc": "application/onenote",
".onetoc2": "application/onenote",
".onetmp": "application/onenote",
".onepkg": "application/onenote",
".xpi": "application/x-xpinstall",
}
func init() {
for ext, typ := range types {
// skip errors
mime.AddExtensionType(ext, typ)
}
}
// TypeByExtension returns the MIME type associated with the file extension ext.
// The extension ext should begin with a leading dot, as in ".html".
// When ext has no associated type, typeByExtension returns "".
//
// Extensions are looked up first case-sensitively, then case-insensitively.
//
// The built-in table is small but on unix it is augmented by the local
// system's mime.types file(s) if available under one or more of these
// names:
//
// /etc/mime.types
// /etc/apache2/mime.types
// /etc/apache/mime.types
//
// On Windows, MIME types are extracted from the registry.
//
// Text types have the charset parameter set to "utf-8" by default.
func TypeByExtension(ext string) (typ string) {
if len(ext) < 2 {
return
}
if ext[0] != '.' { // try to take it by filename
typ = TypeByFilename(ext)
if typ == "" {
ext = "." + ext // if error or something wrong then prepend the dot
}
}
if typ == "" {
typ = mime.TypeByExtension(ext)
}
// mime.TypeByExtension returns as text/plain; | charset=utf-8 the static .js (not always)
if ext == ".js" && (typ == "text/plain" || typ == "text/plain; charset=utf-8") {
if ext == ".js" {
typ = "application/javascript"
}
}
return typ
}
// TypeByFilename, saem as TypeByExtension
// but receives a filename path instead.
func TypeByFilename(fullFilename string) string {
ext := filepath.Ext(fullFilename)
return TypeByExtension(ext)
}

171
core/router/party.go Normal file
View File

@@ -0,0 +1,171 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package router
import (
"github.com/kataras/iris/context"
) // Party is here to separate the concept of
// api builder and the sub api builder.
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
// Party could also be named as 'Join' or 'Node' or 'Group' , Party chosen because it is fun.
//
// Look the "APIBuilder" for its implementation.
type Party interface {
// Party creates and returns a new child Party with the following features.
Party(relativePath string, handlers ...context.Handler) Party
// Use appends Handler(s) to the current Party's routes and child routes.
// If the current Party is the root, then it registers the middleware to all child Parties' routes too.
Use(handlers ...context.Handler)
// Done appends to the very end, Handler(s) to the current Party's routes and child routes
// The difference from .Use is that this/or these Handler(s) are being always running last.
Done(handlers ...context.Handler)
// Handle registers a route to the server's router.
// if empty method is passed then handler(s) are being registered to all methods, same as .Any.
//
// Returns the read-only route information.
Handle(method string, registeredPath string, handlers ...context.Handler) (*Route, error)
// None registers an "offline" route
// see context.ExecRoute(routeName) and
// party.Routes().Online(handleResultregistry.*Route, "GET") and
// Offline(handleResultregistry.*Route)
//
// Returns the read-only route information.
None(path string, handlers ...context.Handler) (*Route, error)
// Get registers a route for the Get http method.
//
// Returns the read-only route information.
Get(path string, handlers ...context.Handler) (*Route, error)
// Post registers a route for the Post http method.
//
// Returns the read-only route information.
Post(path string, handlers ...context.Handler) (*Route, error)
// Put registers a route for the Put http method.
//
// Returns the read-only route information.
Put(path string, handlers ...context.Handler) (*Route, error)
// Delete registers a route for the Delete http method.
//
// Returns the read-only route information.
Delete(path string, handlers ...context.Handler) (*Route, error)
// Connect registers a route for the Connect http method.
//
// Returns the read-only route information.
Connect(path string, handlers ...context.Handler) (*Route, error)
// Head registers a route for the Head http method.
//
// Returns the read-only route information.
Head(path string, handlers ...context.Handler) (*Route, error)
// Options registers a route for the Options http method.
//
// Returns the read-only route information.
Options(path string, handlers ...context.Handler) (*Route, error)
// Patch registers a route for the Patch http method.
//
// Returns the read-only route information.
Patch(path string, handlers ...context.Handler) (*Route, error)
// Trace registers a route for the Trace http method.
//
// Returns the read-only route information.
Trace(path string, handlers ...context.Handler) (*Route, error)
// Any registers a route for ALL of the http methods
// (Get,Post,Put,Head,Patch,Options,Connect,Delete).
Any(registeredPath string, handlers ...context.Handler) error
// StaticHandler returns a new Handler which is ready
// to serve all kind of static files.
//
// Note:
// The only difference from package-level `StaticHandler`
// is that this `StaticHandler`` receives a request path which
// is appended to the party's relative path and stripped here,
// so `iris.StripPath` is useless and should not being used here.
//
// Usage:
// app := iris.New()
// ...
// mySubdomainFsServer := app.Party("mysubdomain.")
// h := mySubdomainFsServer.StaticHandler("/static", "./static_files", false, false)
// /* http://mysubdomain.mydomain.com/static/css/style.css */
// mySubdomainFsServer.Get("/static", h)
// ...
//
StaticHandler(requestPath string, systemPath string, showList bool, enableGzip bool, exceptRoutes ...*Route) context.Handler
// StaticServe serves a directory as web resource
// it's the simpliest form of the Static* functions
// Almost same usage as StaticWeb
// accepts only one required parameter which is the systemPath,
// the same path will be used to register the GET and HEAD method routes.
// If second parameter is empty, otherwise the requestPath is the second parameter
// it uses gzip compression (compression on each request, no file cache).
//
// Returns the GET *Route.
StaticServe(systemPath string, requestPath ...string) (*Route, error)
// StaticContent registers a GET and HEAD method routes to the requestPath
// that are ready to serve raw static bytes, memory cached.
//
// Returns the GET *Route.
StaticContent(requestPath string, cType string, content []byte) (*Route, error)
// StaticEmbedded used when files are distributed inside the app executable, using go-bindata mostly
// First parameter is the request path, the path which the files in the vdir will be served to, for example "/static"
// Second parameter is the (virtual) directory path, for example "./assets"
// Third parameter is the Asset function
// Forth parameter is the AssetNames function.
//
// Returns the GET *Route.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/intermediate/serve-embedded-files
StaticEmbedded(requestPath string, vdir string, assetFn func(name string) ([]byte, error), namesFn func() []string) (*Route, error)
// Favicon serves static favicon
// accepts 2 parameters, second is optional
// favPath (string), declare the system directory path of the __.ico
// requestPath (string), it's the route's path, by default this is the "/favicon.ico" because some browsers tries to get this by default first,
// you can declare your own path if you have more than one favicon (desktop, mobile and so on)
//
// this func will add a route for you which will static serve the /yuorpath/yourfile.ico to the /yourfile.ico
// (nothing special that you can't handle by yourself).
// Note that you have to call it on every favicon you have to serve automatically (desktop, mobile and so on).
//
// Returns the GET *Route.
Favicon(favPath string, requestPath ...string) (*Route, error)
// StaticWeb returns a handler that serves HTTP requests
// with the contents of the file system rooted at directory.
//
// first parameter: the route path
// second parameter: the system directory
// third OPTIONAL parameter: the exception routes
// (= give priority to these routes instead of the static handler)
// for more options look router.StaticHandler.
//
// router.StaticWeb("/static", "./static")
//
// As a special case, the returned file server redirects any request
// ending in "/index.html" to the same path, without the final
// "index.html".
//
// StaticWeb calls the StaticHandler(requestPath, systemPath, listingDirectories: false, gzip: false ).
//
// Returns the GET *Route.
StaticWeb(requestPath string, systemPath string, exceptRoutes ...*Route) (*Route, error)
// Layout oerrides the parent template layout with a more specific layout for this Party
// returns this Party, to continue as normal
// Usage:
// app := iris.New()
// my := app.Party("/my").Layout("layouts/mylayout.html")
// {
// my.Get("/", func(ctx context.Context) {
// ctx.MustRender("page1.html", nil)
// })
// }
Layout(tmplLayoutFile string) Party
}

252
core/router/path.go Normal file
View File

@@ -0,0 +1,252 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package router
import (
"net/http"
"path"
"strconv"
"strings"
"github.com/esemplastic/unis"
"github.com/kataras/iris/core/nettools"
)
const (
// ParamStart the character in string representation where the underline router starts its dynamic named parameter.
ParamStart = ":"
// WildcardParamStart the character in string representation where the underline router starts its dynamic wildcard
// path parameter.
WildcardParamStart = "*"
)
// ResolveStaticPath receives a (dynamic) path and tries to return its static path part.
func ResolveStaticPath(original string) string {
i := strings.Index(original, ParamStart)
v := strings.Index(original, WildcardParamStart)
return unis.NewChain(
unis.NewConditional(
unis.NewRangeEnd(i),
unis.NewRangeEnd(v)),
cleanPath).Process(original)
}
// Param receives a parameter name prefixed with the ParamStart symbol.
func Param(name string) string {
return prefix(name, ParamStart)
}
// WildcardParam receives a parameter name prefixed with the WildcardParamStart symbol.
func WildcardParam(name string) string {
if len(name) == 0 {
return ""
}
return prefix(name, WildcardParamStart)
}
func prefix(str string, prefix string) string {
return unis.NewExclusivePrepender(prefix).Process(str)
}
func joinPath(path1 string, path2 string) string {
return path.Join(path1, path2)
}
// cleanPath applies the following rules
// iteratively until no further processing can be done:
//
// 1. Replace multiple slashes with a single slash.
// 3. Eliminate each inner .. path name element (the parent directory)
// along with the non-.. element that precedes it.
// 4. Eliminate .. elements that begin a rooted path:
// that is, replace "/.." by "/" at the beginning of a path.
//
// The returned path ends in a slash only if it is the root "/".
var cleanPath = unis.NewChain(
unis.NewSuffixRemover("/"),
unis.NewTargetedJoiner(0, '/'),
unis.ProcessorFunc(path.Clean),
unis.NewReplacer(map[string]string{
"//": "/",
"\\": "/",
}),
unis.ProcessorFunc(func(s string) string {
if s == "" || s == "." {
return "/"
}
return s
}),
)
const (
// DynamicSubdomainIndicator where a registered path starts with '*.' then it contains a dynamic subdomain, if subdomain == "*." then its dynamic
//
// used internally by URLPath and the router serve.
DynamicSubdomainIndicator = "*."
// SubdomainIndicator where './' exists in a registered path then it contains subdomain
//
// used on router builder
SubdomainIndicator = "./"
)
func newSubdomainDivider(sep string) unis.DividerFunc {
// invert if indiciator not found
// because we need the first parameter to be the subdomain
// even if empty, but the second parameter
// should be the path, in order to normalize it
// (because of the reason of subdomains shouldn't be normalized as path)
subdomainDevider := unis.NewInvertOnFailureDivider(unis.NewDivider(sep))
return func(fullpath string) (string, string) {
subdomain, path := subdomainDevider.Divide(fullpath)
return subdomain, path //cleanPath(path)
}
}
// ExctractSubdomain checks if the path has subdomain and if it's
// it splits the subdomain and path and returns them, otherwise it returns
// an empty subdomain and the given path.
//
// First return value is the subdomain, second is the path.
var exctractSubdomain = newSubdomainDivider(SubdomainIndicator)
// RoutePathReverserOption option signature for the RoutePathReverser.
type RoutePathReverserOption func(*RoutePathReverser)
// WithScheme is an option for the RoutepathReverser,
// it sets the optional field "vscheme",
// v for virtual.
// if vscheme is empty then it will try to resolve it from
// the RoutePathReverser's vhost field.
//
// See WithHost or WithServer to enable the URL feature.
func WithScheme(scheme string) RoutePathReverserOption {
return func(ps *RoutePathReverser) {
ps.vscheme = scheme
}
}
// WithHost enables the RoutePathReverser's URL feature.
// Both "WithHost" and "WithScheme" can be different from
// the real server's listening address, i.e nginx in front.
func WithHost(host string) RoutePathReverserOption {
return func(ps *RoutePathReverser) {
ps.vhost = host
if ps.vscheme == "" {
ps.vscheme = nettools.ResolveScheme(host)
}
}
}
// WithServer enables the RoutePathReverser's URL feature.
// It receives an *http.Server and tries to resolve
// a scheme and a host to be used in the URL function.
func WithServer(srv *http.Server) RoutePathReverserOption {
return func(ps *RoutePathReverser) {
ps.vhost = nettools.ResolveVHost(srv.Addr)
ps.vscheme = nettools.ResolveSchemeFromServer(srv)
}
}
// RoutePathReverser contains methods that helps to reverse a
// (dynamic) path from a specific route,
// route name is required because a route may being registered
// on more than one http method.
type RoutePathReverser struct {
provider RoutesProvider
// both vhost and vscheme are being used, optionally, for the URL feature.
vhost string
vscheme string
}
// NewRoutePathReverser returns a new path reverser based on
// a routes provider, needed to get a route based on its name.
// Options is required for the URL function.
// See WithScheme and WithHost or WithServer.
func NewRoutePathReverser(apiRoutesProvider RoutesProvider, options ...RoutePathReverserOption) *RoutePathReverser {
ps := &RoutePathReverser{
provider: apiRoutesProvider,
}
for _, o := range options {
o(ps)
}
return ps
}
// Path returns a route path based on a route name and any dynamic named parameter's values-only.
func (ps *RoutePathReverser) Path(routeName string, paramValues ...interface{}) string {
r := ps.provider.GetRoute(routeName)
if r == nil {
return ""
}
if len(paramValues) == 0 {
return r.Path
}
return r.ResolvePath(toStringSlice(paramValues)...)
}
func toStringSlice(args []interface{}) []string {
var argsString []string
if len(args) > 0 {
argsString = make([]string, len(args), len(args))
}
for i, v := range args {
if s, ok := v.(string); ok {
argsString[i] = s
} else if num, ok := v.(int); ok {
argsString[i] = strconv.Itoa(num)
} else if b, ok := v.(bool); ok {
argsString[i] = strconv.FormatBool(b)
} else if arr, ok := v.([]string); ok {
if len(arr) > 0 {
argsString[i] = arr[0]
argsString = append(argsString, arr[1:]...)
}
}
}
return argsString
}
// Remove the URL for now, it complicates things for the whole framework without a specific benefits,
// developers can just concat the subdomain, (host can be auto-retrieve by browser using the Path).
// URL same as Path but returns the full uri, i.e https://mysubdomain.mydomain.com/hello/kataras
func (ps *RoutePathReverser) URL(routeName string, paramValues ...interface{}) (url string) {
if ps.vhost == "" || ps.vscheme == "" {
return "not supported"
}
r := ps.provider.GetRoute(routeName)
if r == nil {
return
}
if len(paramValues) == 0 {
return r.Path
}
args := toStringSlice(paramValues)
host := ps.vhost
scheme := ps.vscheme
// if it's dynamic subdomain then the first argument is the subdomain part
// for this part we are responsible not the custom routers
if r.Subdomain == DynamicSubdomainIndicator {
subdomain := args[0]
host = subdomain + "." + host
args = args[1:] // remove the subdomain part for the arguments,
}
if parsedPath := r.ResolvePath(args...); parsedPath != "" {
url = scheme + "://" + host + parsedPath
}
return
}

128
core/router/route.go Normal file
View File

@@ -0,0 +1,128 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package router
import (
"fmt"
"strings"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router/macro"
)
type Route struct {
Name string // "userRoute"
Method string // "GET"
Subdomain string // "admin."
tmpl *macro.Template // Tmpl().Src: "/api/user/{id:int}"
Path string // "/api/user/:id"
Handlers context.Handlers
// FormattedPath all dynamic named parameters (if any) replaced with %v,
// used by Application to validate param values of a Route based on its name.
FormattedPath string
}
func NewRoute(method, subdomain, unparsedPath string,
handlers context.Handlers, macros *macro.MacroMap) (*Route, error) {
tmpl, err := macro.Parse(unparsedPath, macros)
if err != nil {
return nil, err
}
path, handlers, err := compileRoutePathAndHandlers(handlers, tmpl)
if err != nil {
return nil, err
}
path = cleanPath(path) // maybe unnecessary here but who cares in this moment
defaultName := method + subdomain + path
formattedPath := formatPath(path)
route := &Route{
Name: defaultName,
Method: method,
Subdomain: subdomain,
tmpl: tmpl,
Path: path,
Handlers: handlers,
FormattedPath: formattedPath,
}
return route, nil
}
// Tmpl returns the path template, i
// it contains the parsed template
// for the route's path.
// May contain zero named parameters.
//
// Developer can get his registered path
// via Tmpl().Src, Route.Path is the path
// converted to match the underline router's specs.
func (r Route) Tmpl() macro.Template {
return *r.tmpl
}
// IsOnline returns true if the route is marked as "online" (state).
func (r Route) IsOnline() bool {
return r.Method != MethodNone
}
// formats the parsed to the underline httprouter's path syntax.
// path = "/api/users/:id"
// return "/api/users/%v"
//
// path = "/files/*file"
// return /files/%v
//
// path = "/:username/messages/:messageid"
// return "/%v/messages/%v"
// we don't care about performance here, it's prelisten.
func formatPath(path string) string {
if strings.Contains(path, ParamStart) || strings.Contains(path, WildcardParamStart) {
var (
startRune = ParamStart[0]
wildcardStartRune = WildcardParamStart[0]
)
var formattedParts []string
parts := strings.Split(path, "/")
for _, part := range parts {
if len(part) == 0 {
continue
}
if part[0] == startRune || part[0] == wildcardStartRune {
// is param or wildcard param
part = "%v"
}
formattedParts = append(formattedParts, part)
}
return "/" + strings.Join(formattedParts, "/")
}
// the whole path is static just return it
return path
}
// ResolvePath returns the formatted path's %v replaced with the args.
func (r Route) ResolvePath(args ...string) string {
rpath, formattedPath := r.Path, r.FormattedPath
if rpath == formattedPath {
// static, no need to pass args
return rpath
}
// check if we have /*, if yes then join all arguments to one as path and pass that as parameter
if rpath[len(rpath)-1] == WildcardParamStart[0] {
parameter := strings.Join(args, "/")
return fmt.Sprintf(formattedPath, parameter)
}
// else return the formattedPath with its args,
// the order matters.
for _, s := range args {
formattedPath = strings.Replace(formattedPath, "%v", s, 1)
}
return formattedPath
}

156
core/router/router.go Normal file
View File

@@ -0,0 +1,156 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package router
import (
"net/http"
"sync"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
)
type Router struct {
mu sync.Mutex // for Downgrade, WrapRouter & BuildRouter,
// not indeed but we don't to risk its usage by third-parties.
requestHandler RequestHandler // build-accessible, can be changed to define a custom router or proxy, used on RefreshRouter too.
mainHandler http.HandlerFunc // init-accessible
wrapperFunc func(http.ResponseWriter, *http.Request, http.HandlerFunc)
cPool *context.Pool // used on RefreshRouter
routesProvider RoutesProvider
}
// NewRouter returns a new empty Router.
func NewRouter() *Router { return &Router{} }
// RefreshRouter re-builds the router. Should be called when a route's state
// changed (i.e Method changed at serve-time).
func (router *Router) RefreshRouter() error {
return router.BuildRouter(router.cPool, router.requestHandler, router.routesProvider)
}
// BuildRouter builds the router based on
// the context factory (explicit pool in this case),
// the request handler which manages how the main handler will multiplexes the routes
// provided by the third parameter, routerProvider (it's the api builder in this case) and
// its wrapper.
//
// Use of RefreshRouter to re-build the router if needed.
func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHandler, routesProvider RoutesProvider) error {
if requestHandler == nil {
return errors.New("router: request handler is nil")
}
if cPool == nil {
return errors.New("router: context pool is nil")
}
// build the handler using the routesProvider
if err := requestHandler.Build(routesProvider); err != nil {
return err
}
router.mu.Lock()
defer router.mu.Unlock()
// store these for RefreshRouter's needs.
router.cPool = cPool
router.requestHandler = requestHandler
router.routesProvider = routesProvider
// the important
router.mainHandler = func(w http.ResponseWriter, r *http.Request) {
ctx := cPool.Acquire(w, r)
router.requestHandler.HandleRequest(ctx)
cPool.Release(ctx)
}
if router.wrapperFunc != nil { // if wrapper used then attach that as the router service
router.mainHandler = NewWrapper(router.wrapperFunc, router.mainHandler).ServeHTTP
}
return nil
}
// Downgrade "downgrades", alters the router supervisor service(Router.mainHandler)
// algorithm to a custom one,
// be aware to change the global variables of 'ParamStart' and 'ParamWildcardStart'.
// can be used to implement a custom proxy or
// a custom router which should work with raw ResponseWriter, *Request
// instead of the Context(which agaiin, can be retrieved by the Cramework's context pool).
//
// Note: Downgrade will by-pass the Wrapper, the caller is responsible for everything.
// Downgrade is thread-safe.
func (router *Router) Downgrade(newMainHandler http.HandlerFunc) {
router.mu.Lock()
router.mainHandler = newMainHandler
router.mu.Unlock()
}
// WrapRouter adds a wrapper on the top of the main router.
// Usually it's useful for third-party middleware
// when need to wrap the entire application with a middleware like CORS.
//
// Developers can add more than one wrappers,
// those wrappers' execution comes from last to first.
// That means that the second wrapper will wrap the first, and so on.
//
// Before build.
func (router *Router) WrapRouter(wrapperFunc func(w http.ResponseWriter, r *http.Request, firstNextIsTheRouter http.HandlerFunc)) {
router.mu.Lock()
defer router.mu.Unlock()
if wrapperFunc == nil {
return
}
if router.wrapperFunc != nil {
// wrap into one function, from bottom to top, end to begin.
nextWrapper := wrapperFunc
prevWrapper := router.wrapperFunc
wrapperFunc = func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if next != nil {
nexthttpFunc := http.HandlerFunc(func(_w http.ResponseWriter, _r *http.Request) {
prevWrapper(_w, _r, next)
})
nextWrapper(w, r, nexthttpFunc)
}
}
}
router.wrapperFunc = wrapperFunc
}
// ServeHTTPC serves the raw context, useful if we have already a context, it by-pass the wrapper.
func (router *Router) ServeHTTPC(ctx context.Context) {
router.requestHandler.HandleRequest(ctx)
}
func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
router.mainHandler(w, r)
}
type wrapper struct {
router http.HandlerFunc // http.HandlerFunc to catch the CURRENT state of its .ServeHTTP on case of future change.
wrapperFunc func(http.ResponseWriter, *http.Request, http.HandlerFunc)
}
// NewWrapper returns a new http.Handler wrapped by the 'wrapperFunc'
// the "next" is the final "wrapped" input parameter.
//
// Application is responsible to make it to work on more than one wrappers
// via composition or func clojure.
func NewWrapper(wrapperFunc func(w http.ResponseWriter, r *http.Request, routerNext http.HandlerFunc), wrapped http.HandlerFunc) http.Handler {
return &wrapper{
wrapperFunc: wrapperFunc,
router: wrapped,
}
}
func (wr *wrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
wr.wrapperFunc(w, r, wr.router)
}

144
core/router/status.go Normal file
View File

@@ -0,0 +1,144 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package router
import (
"net/http" // just for status codes
"sync"
"github.com/kataras/iris/context"
)
// ErrorCodeHandler is the entry
// of the list of all http error code handlers.
type ErrorCodeHandler struct {
StatusCode int
Handler context.Handler
mu sync.Mutex
}
// Fire executes the specific an error http error status.
// it's being wrapped to make sure that the handler
// will render correctly.
func (ch *ErrorCodeHandler) Fire(ctx context.Context) {
// if we can reset the body
if w, ok := ctx.IsRecording(); ok {
// reset if previous content and it's recorder
w.Reset()
} else if w, ok := ctx.ResponseWriter().(*context.GzipResponseWriter); ok {
// reset and disable the gzip in order to be an expected form of http error result
w.ResetBody()
w.Disable()
} else {
// if we can't reset the body and the body has been filled
// which means that the status code already sent,
// then do not fire this custom error code.
if ctx.ResponseWriter().Written() != -1 {
return
}
}
// ctx.StopExecution() // not uncomment this, is here to remember why to.
// note for me: I don't stopping the execution of the other handlers
// because may the user want to add a fallback error code
// i.e
// users := app.Party("/users")
// users.Done(func(ctx context.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }})
ch.Handler(ctx)
}
func (ch *ErrorCodeHandler) updateHandler(h context.Handler) {
ch.mu.Lock()
ch.Handler = h
ch.mu.Unlock()
}
// ErrorCodeHandlers contains the http error code handlers.
// User of this struct can register, get
// a status code handler based on a status code or
// fire based on a receiver context.
type ErrorCodeHandlers struct {
handlers []*ErrorCodeHandler
}
func defaultErrorCodeHandlers() *ErrorCodeHandlers {
chs := new(ErrorCodeHandlers)
// register some common error handlers.
// Note that they can be registered on-fly but
// we don't want to reduce the performance even
// on the first failed request.
for _, statusCode := range []int{
http.StatusNotFound,
http.StatusMethodNotAllowed,
http.StatusInternalServerError} {
chs.Register(statusCode, statusText(statusCode))
}
return chs
}
func statusText(statusCode int) context.Handler {
return func(ctx context.Context) {
if _, err := ctx.WriteString(http.StatusText(statusCode)); err != nil {
// ctx.Application().Log("(status code: %d) %s",
// err.Error(), statusCode)
}
}
}
// Get returns an http error handler based on the "statusCode".
// If not found it returns nil.
func (s *ErrorCodeHandlers) Get(statusCode int) *ErrorCodeHandler {
for i, n := 0, len(s.handlers); i < n; i++ {
if h := s.handlers[i]; h.StatusCode == statusCode {
return h
}
}
return nil
}
// Register registers an error http status code
// based on the "statusCode" >= 400.
// The handler is being wrapepd by a generic
// handler which will try to reset
// the body if recorder was enabled
// and/or disable the gzip if gzip response recorder
// was active.
func (s *ErrorCodeHandlers) Register(statusCode int, handler context.Handler) *ErrorCodeHandler {
if statusCode < 400 {
return nil
}
h := s.Get(statusCode)
if h == nil {
ch := &ErrorCodeHandler{
StatusCode: statusCode,
Handler: handler,
}
s.handlers = append(s.handlers, ch)
// create new and add it
return ch
}
// otherwise update the handler
h.updateHandler(handler)
return h
}
// Fire executes an error http status code handler
// based on the context's status code.
//
// If a handler is not already registered,
// then it creates & registers a new trivial handler on the-fly.
func (s *ErrorCodeHandlers) Fire(ctx context.Context) {
statusCode := ctx.GetStatusCode()
if statusCode < 400 {
return
}
ch := s.Get(statusCode)
if ch == nil {
ch = s.Register(statusCode, statusText(statusCode))
}
ch.Fire(ctx)
}