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:
100
core/errors/errors.go
Normal file
100
core/errors/errors.go
Normal 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
233
core/gui/LICENSE
Normal 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
366
core/gui/icon/iconunix.go
Normal 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
366
core/gui/icon/iconwin.go
Normal 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
272
core/gui/tray.go
Normal 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()
|
||||
}
|
||||
109
core/handlerconv/from_std.go
Normal file
109
core/handlerconv/from_std.go
Normal 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
66
core/host/proxy.go
Normal 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
132
core/host/scheduler.go
Normal 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)
|
||||
})
|
||||
}
|
||||
83
core/host/scheduler_test.go
Normal file
83
core/host/scheduler_test.go
Normal 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
227
core/host/supervisor.go
Normal 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)
|
||||
}
|
||||
112
core/host/supervisor_test.go
Normal file
112
core/host/supervisor_test.go
Normal 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
131
core/host/task.go
Normal 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
23
core/host/task_banner.go
Normal 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)))
|
||||
}
|
||||
}
|
||||
47
core/host/task_example_test.go
Normal file
47
core/host/task_example_test.go
Normal 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
|
||||
}
|
||||
47
core/host/task_gui_tray.go
Normal file
47
core/host/task_gui_tray.go
Normal 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()
|
||||
}
|
||||
}
|
||||
28
core/host/task_gui_tray_linux.go
Normal file
28
core/host/task_gui_tray_linux.go
Normal 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")
|
||||
}
|
||||
}
|
||||
29
core/host/task_interrupt.go
Normal file
29
core/host/task_interrupt.go
Normal 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
47
core/logger/dev.go
Normal 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
50
core/logger/logger.go
Normal 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
7
core/logger/noop.go
Normal 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
205
core/nettools/addr.go
Normal 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
|
||||
}
|
||||
47
core/nettools/addr_test.go
Normal file
47
core/nettools/addr_test.go
Normal 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
41
core/nettools/server.go
Normal 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
163
core/nettools/tcp.go
Normal 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
659
core/router/api_builder.go
Normal 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
977
core/router/fs.go
Normal 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(
|
||||
"&", "&",
|
||||
"<", "<",
|
||||
">", ">",
|
||||
// """ is shorter than """.
|
||||
`"`, """,
|
||||
// "'" is shorter than "'" and apos was not in HTML until HTML5.
|
||||
"'", "'",
|
||||
)
|
||||
|
||||
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
252
core/router/handler.go
Normal 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)
|
||||
}
|
||||
56
core/router/httprouter/LICENSE
Normal file
56
core/router/httprouter/LICENSE
Normal 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.
|
||||
|
||||
|
||||
456
core/router/httprouter/node.go
Normal file
456
core/router/httprouter/node.go
Normal 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
247
core/router/macro.go
Normal 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
27
core/router/macro/LICENSE
Normal 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.
|
||||
122
core/router/macro/interpreter/ast/ast.go
Normal file
122
core/router/macro/interpreter/ast/ast.go
Normal 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]
|
||||
}
|
||||
186
core/router/macro/interpreter/lexer/lexer.go
Normal file
186
core/router/macro/interpreter/lexer/lexer.go
Normal 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'
|
||||
}
|
||||
58
core/router/macro/interpreter/lexer/lexer_test.go
Normal file
58
core/router/macro/interpreter/lexer/lexer_test.go
Normal 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.
|
||||
180
core/router/macro/interpreter/parser/parser.go
Normal file
180
core/router/macro/interpreter/parser/parser.go
Normal 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()
|
||||
}
|
||||
258
core/router/macro/interpreter/parser/parser_test.go
Normal file
258
core/router/macro/interpreter/parser/parser_test.go
Normal 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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
53
core/router/macro/interpreter/token/token.go
Normal file
53
core/router/macro/interpreter/token/token.go
Normal 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
229
core/router/macro/macro.go
Normal 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
|
||||
}
|
||||
}
|
||||
190
core/router/macro/macro_test.go
Normal file
190
core/router/macro/macro_test.go
Normal 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)
|
||||
// }
|
||||
67
core/router/macro/template.go
Normal file
67
core/router/macro/template.go
Normal 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
596
core/router/mime.go
Normal 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
171
core/router/party.go
Normal 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
252
core/router/path.go
Normal 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
128
core/router/route.go
Normal 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
156
core/router/router.go
Normal 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
144
core/router/status.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user