Publish the new version :airplane: | 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
2017-06-03 22:22:52 +02:00
|
|
|
package context
|
|
|
|
|
2017-11-04 01:59:21 +01:00
|
|
|
import (
|
2020-04-28 00:58:56 +02:00
|
|
|
"os"
|
2020-04-25 01:30:19 +02:00
|
|
|
"path/filepath"
|
2017-11-04 01:59:21 +01:00
|
|
|
"reflect"
|
2020-04-27 14:48:09 +02:00
|
|
|
"regexp"
|
2017-11-04 01:59:21 +01:00
|
|
|
"runtime"
|
2019-06-21 18:43:25 +02:00
|
|
|
"strings"
|
2020-04-27 14:48:09 +02:00
|
|
|
"sync"
|
2017-11-04 01:59:21 +01:00
|
|
|
)
|
|
|
|
|
2020-04-28 00:58:56 +02:00
|
|
|
var (
|
|
|
|
// PackageName is the Iris Go module package name.
|
|
|
|
PackageName = strings.TrimSuffix(reflect.TypeOf(Handlers{}).PkgPath(), "/context")
|
|
|
|
|
|
|
|
// WorkingDir is the (initial) current directory.
|
|
|
|
WorkingDir, _ = os.Getwd()
|
|
|
|
)
|
|
|
|
|
2020-04-27 14:48:09 +02:00
|
|
|
var (
|
2020-08-17 20:53:17 +02:00
|
|
|
handlerNames = make(map[*NameExpr]string)
|
2020-04-27 14:48:09 +02:00
|
|
|
handlerNamesMu sync.RWMutex
|
|
|
|
)
|
|
|
|
|
|
|
|
// SetHandlerName sets a handler name that could be
|
|
|
|
// fetched through `HandlerName`. The "original" should be
|
|
|
|
// the Go's original regexp-featured (can be retrieved through a `HandlerName` call) function name.
|
|
|
|
// The "replacement" should be the custom, human-text of that function name.
|
|
|
|
//
|
|
|
|
// If the name starts with "iris" then it replaces that string with the
|
|
|
|
// full Iris module package name,
|
|
|
|
// e.g. iris/middleware/logger.(*requestLoggerMiddleware).ServeHTTP-fm to
|
|
|
|
// github.com/kataras/iris/v12/middleware/logger.(*requestLoggerMiddleware).ServeHTTP-fm
|
|
|
|
// for convenient between Iris versions.
|
|
|
|
func SetHandlerName(original string, replacement string) {
|
|
|
|
if strings.HasPrefix(original, "iris") {
|
|
|
|
original = PackageName + strings.TrimPrefix(original, "iris")
|
|
|
|
}
|
|
|
|
|
|
|
|
handlerNamesMu.Lock()
|
2020-04-28 21:34:36 +02:00
|
|
|
// If regexp syntax is wrong
|
|
|
|
// then its `MatchString` will compare through literal. Fixes an issue
|
|
|
|
// when a handler name is declared as it's and cause regex parsing expression error,
|
|
|
|
// e.g. `iris/cache/client.(*Handler).ServeHTTP-fm`
|
|
|
|
regex, _ := regexp.Compile(original)
|
2020-08-17 20:53:17 +02:00
|
|
|
handlerNames[&NameExpr{
|
2020-04-28 21:34:36 +02:00
|
|
|
literal: original,
|
|
|
|
regex: regex,
|
|
|
|
}] = replacement
|
2020-04-27 14:48:09 +02:00
|
|
|
handlerNamesMu.Unlock()
|
|
|
|
}
|
|
|
|
|
2020-08-17 20:53:17 +02:00
|
|
|
// NameExpr regex or literal comparison through `MatchString`.
|
|
|
|
type NameExpr struct {
|
2020-04-28 21:34:36 +02:00
|
|
|
regex *regexp.Regexp
|
|
|
|
literal string
|
|
|
|
}
|
|
|
|
|
2020-08-17 20:53:17 +02:00
|
|
|
// MatchString reports whether "s" is literal of "literal"
|
|
|
|
// or it matches the regex expression at "regex".
|
|
|
|
func (expr *NameExpr) MatchString(s string) bool {
|
2020-04-28 21:34:36 +02:00
|
|
|
if expr.literal == s { // if matches as string, as it's.
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if expr.regex != nil {
|
|
|
|
return expr.regex.MatchString(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-06-10 02:56:42 +02:00
|
|
|
// A Handler responds to an HTTP request.
|
|
|
|
// It writes reply headers and data to the Context.ResponseWriter() and then return.
|
|
|
|
// Returning signals that the request is finished;
|
|
|
|
// it is not valid to use the Context after or concurrently with the completion of the Handler call.
|
|
|
|
//
|
|
|
|
// Depending on the HTTP client software, HTTP protocol version,
|
2017-07-10 17:32:42 +02:00
|
|
|
// and any intermediaries between the client and the iris server,
|
2020-04-27 14:48:09 +02:00
|
|
|
// it may not be possible to read from the Context.Request().Body after writing to the Context.ResponseWriter().
|
2017-06-10 02:56:42 +02:00
|
|
|
// Cautious handlers should read the Context.Request().Body first, and then reply.
|
|
|
|
//
|
|
|
|
// Except for reading the body, handlers should not modify the provided Context.
|
|
|
|
//
|
|
|
|
// If Handler panics, the server (the caller of Handler) assumes that the effect of the panic was isolated to the active request.
|
|
|
|
// It recovers the panic, logs a stack trace to the server error log, and hangs up the connection.
|
2020-07-10 22:21:09 +02:00
|
|
|
type Handler func(*Context)
|
Publish the new version :airplane: | 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
2017-06-03 22:22:52 +02:00
|
|
|
|
2017-06-10 02:56:42 +02:00
|
|
|
// Handlers is just a type of slice of []Handler.
|
|
|
|
//
|
|
|
|
// See `Handler` for more.
|
Publish the new version :airplane: | 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
2017-06-03 22:22:52 +02:00
|
|
|
type Handlers []Handler
|
2017-11-04 01:59:21 +01:00
|
|
|
|
2020-04-25 01:30:19 +02:00
|
|
|
func valueOf(v interface{}) reflect.Value {
|
|
|
|
if val, ok := v.(reflect.Value); ok {
|
|
|
|
return val
|
|
|
|
}
|
|
|
|
|
|
|
|
return reflect.ValueOf(v)
|
|
|
|
}
|
|
|
|
|
2019-06-21 18:43:25 +02:00
|
|
|
// HandlerName returns the handler's function name.
|
2020-04-27 14:48:09 +02:00
|
|
|
// See `Context.HandlerName` method to get function name of the current running handler in the chain.
|
|
|
|
// See `SetHandlerName` too.
|
2020-04-25 01:30:19 +02:00
|
|
|
func HandlerName(h interface{}) string {
|
|
|
|
pc := valueOf(h).Pointer()
|
2020-04-27 14:48:09 +02:00
|
|
|
name := runtime.FuncForPC(pc).Name()
|
|
|
|
handlerNamesMu.RLock()
|
2020-04-28 21:34:36 +02:00
|
|
|
for expr, newName := range handlerNames {
|
|
|
|
if expr.MatchString(name) {
|
2020-04-27 14:48:09 +02:00
|
|
|
name = newName
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2020-04-28 21:34:36 +02:00
|
|
|
|
2020-04-27 14:48:09 +02:00
|
|
|
handlerNamesMu.RUnlock()
|
|
|
|
|
2020-04-28 21:34:36 +02:00
|
|
|
return trimHandlerName(name)
|
2017-11-04 01:59:21 +01:00
|
|
|
}
|
2019-01-19 22:33:33 +01:00
|
|
|
|
2020-08-12 06:20:07 +02:00
|
|
|
// HandlersNames returns a slice of "handlers" names
|
|
|
|
// separated by commas. Can be used for debugging
|
|
|
|
// or to determinate if end-developer
|
|
|
|
// called the same exactly Use/UseRouter/Done... API methods
|
|
|
|
// so framework can give a warning.
|
|
|
|
func HandlersNames(handlers ...interface{}) string {
|
|
|
|
if len(handlers) == 1 {
|
|
|
|
if hs, ok := handlers[0].(Handlers); ok {
|
|
|
|
asInterfaces := make([]interface{}, 0, len(hs))
|
|
|
|
for _, h := range hs {
|
|
|
|
asInterfaces = append(asInterfaces, h)
|
|
|
|
}
|
|
|
|
|
|
|
|
return HandlersNames(asInterfaces...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
names := make([]string, 0, len(handlers))
|
|
|
|
for _, h := range handlers {
|
|
|
|
names = append(names, HandlerName(h))
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Join(names, ",")
|
|
|
|
}
|
|
|
|
|
2019-06-21 18:43:25 +02:00
|
|
|
// HandlerFileLine returns the handler's file and line information.
|
|
|
|
// See `context.HandlerFileLine` to get the file, line of the current running handler in the chain.
|
2020-04-25 01:30:19 +02:00
|
|
|
func HandlerFileLine(h interface{}) (file string, line int) {
|
|
|
|
pc := valueOf(h).Pointer()
|
2019-06-21 18:43:25 +02:00
|
|
|
return runtime.FuncForPC(pc).FileLine(pc)
|
|
|
|
}
|
|
|
|
|
2020-04-28 00:58:56 +02:00
|
|
|
// HandlerFileLineRel same as `HandlerFileLine` but it returns the path
|
|
|
|
// corresponding to its relative based on the package-level "WorkingDir" variable.
|
|
|
|
func HandlerFileLineRel(h interface{}) (file string, line int) {
|
2020-04-26 05:21:20 +02:00
|
|
|
file, line = HandlerFileLine(h)
|
2020-04-28 00:58:56 +02:00
|
|
|
if relFile, err := filepath.Rel(WorkingDir, file); err == nil {
|
2020-04-26 05:21:20 +02:00
|
|
|
if !strings.HasPrefix(relFile, "..") {
|
|
|
|
// Only if it's relative to this path, not parent.
|
|
|
|
file = "./" + relFile
|
|
|
|
}
|
2020-04-25 01:30:19 +02:00
|
|
|
}
|
|
|
|
|
2020-04-26 05:21:20 +02:00
|
|
|
return
|
2020-04-25 01:30:19 +02:00
|
|
|
}
|
|
|
|
|
2020-04-26 05:21:20 +02:00
|
|
|
// MainHandlerName tries to find the main handler that end-developer
|
2019-06-21 18:43:25 +02:00
|
|
|
// registered on the provided chain of handlers and returns its function name.
|
2020-04-28 21:34:36 +02:00
|
|
|
func MainHandlerName(handlers ...interface{}) (name string, index int) {
|
|
|
|
if len(handlers) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if hs, ok := handlers[0].(Handlers); ok {
|
|
|
|
tmp := make([]interface{}, 0, len(hs))
|
|
|
|
for _, h := range hs {
|
|
|
|
tmp = append(tmp, h)
|
|
|
|
}
|
|
|
|
|
|
|
|
return MainHandlerName(tmp...)
|
|
|
|
}
|
|
|
|
|
2019-06-21 18:43:25 +02:00
|
|
|
for i := 0; i < len(handlers); i++ {
|
|
|
|
name = HandlerName(handlers[i])
|
2020-04-28 21:34:36 +02:00
|
|
|
if name == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-04-26 05:21:20 +02:00
|
|
|
index = i
|
2020-04-28 21:34:36 +02:00
|
|
|
if !ingoreMainHandlerName(name) {
|
2019-06-21 18:43:25 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-04-28 21:34:36 +02:00
|
|
|
func trimHandlerName(name string) string {
|
|
|
|
// trim the path for Iris' internal middlewares, e.g.
|
|
|
|
// irs/mvc.GRPC.Apply.func1
|
|
|
|
if internalName := PackageName; strings.HasPrefix(name, internalName) {
|
|
|
|
name = strings.Replace(name, internalName, "iris", 1)
|
|
|
|
}
|
|
|
|
|
2020-05-07 07:06:06 +02:00
|
|
|
if internalName := "github.com/iris-contrib"; strings.HasPrefix(name, internalName) {
|
2020-04-28 21:34:36 +02:00
|
|
|
name = strings.Replace(name, internalName, "iris-contrib", 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
name = strings.TrimSuffix(name, ".func1")
|
|
|
|
return name
|
|
|
|
}
|
|
|
|
|
|
|
|
var ignoreHandlerNames = [...]string{
|
|
|
|
"iris/macro/handler.MakeHandler",
|
|
|
|
"iris/hero.makeHandler.func2",
|
|
|
|
"iris/core/router.ExecutionOptions.buildHandler",
|
|
|
|
"iris/core/router.(*APIBuilder).Favicon",
|
|
|
|
"iris/core/router.StripPrefix",
|
|
|
|
}
|
|
|
|
|
|
|
|
// IgnoreHandlerName compares a static slice of Iris builtin
|
|
|
|
// internal methods that should be ignored from trace.
|
|
|
|
// Some internal methods are kept out of this list for actual debugging.
|
|
|
|
func IgnoreHandlerName(name string) bool {
|
|
|
|
for _, ignore := range ignoreHandlerNames {
|
|
|
|
if name == ignore {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
var ignoreMainHandlerNames = [...]string{
|
|
|
|
"iris.cache",
|
|
|
|
"iris.basicauth",
|
|
|
|
"iris.hCaptcha",
|
|
|
|
"iris.reCAPTCHA",
|
|
|
|
"iris.profiling",
|
|
|
|
"iris.recover",
|
2021-01-06 00:52:39 +01:00
|
|
|
"iris.accesslog",
|
|
|
|
"iris.grpc",
|
|
|
|
"iris.requestid",
|
|
|
|
"iris.rewrite",
|
2020-04-28 21:34:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// ingoreMainHandlerName reports whether a main handler of "name" should
|
|
|
|
// be ignored and continue to match the next.
|
|
|
|
// The ignored main handler names are literals and respects the `ignoreNameHandlers` too.
|
|
|
|
func ingoreMainHandlerName(name string) bool {
|
|
|
|
if IgnoreHandlerName(name) {
|
|
|
|
// If ignored at all, it can't be the main.
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, ignore := range ignoreMainHandlerNames {
|
|
|
|
if name == ignore {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-08-27 06:23:56 +02:00
|
|
|
// Filter is just a type of func(Context) bool which reports whether an action must be performed
|
2019-01-19 22:33:33 +01:00
|
|
|
// based on the incoming request.
|
|
|
|
//
|
|
|
|
// See `NewConditionalHandler` for more.
|
2020-07-10 22:21:09 +02:00
|
|
|
type Filter func(*Context) bool
|
2019-01-19 22:33:33 +01:00
|
|
|
|
|
|
|
// NewConditionalHandler returns a single Handler which can be registered
|
|
|
|
// as a middleware.
|
|
|
|
// Filter is just a type of Handler which returns a boolean.
|
|
|
|
// Handlers here should act like middleware, they should contain `ctx.Next` to proceed
|
2019-01-19 22:34:41 +01:00
|
|
|
// to the next handler of the chain. Those "handlers" are registered to the per-request context.
|
2019-01-19 22:33:33 +01:00
|
|
|
//
|
|
|
|
//
|
|
|
|
// It checks the "filter" and if passed then
|
|
|
|
// it, correctly, executes the "handlers".
|
|
|
|
//
|
|
|
|
// If passed, this function makes sure that the Context's information
|
|
|
|
// about its per-request handler chain based on the new "handlers" is always updated.
|
|
|
|
//
|
|
|
|
// If not passed, then simply the Next handler(if any) is executed and "handlers" are ignored.
|
|
|
|
//
|
|
|
|
// Example can be found at: _examples/routing/conditional-chain.
|
|
|
|
func NewConditionalHandler(filter Filter, handlers ...Handler) Handler {
|
2020-07-10 22:21:09 +02:00
|
|
|
return func(ctx *Context) {
|
2019-01-19 22:33:33 +01:00
|
|
|
if filter(ctx) {
|
|
|
|
// Note that we don't want just to fire the incoming handlers, we must make sure
|
|
|
|
// that it won't break any further handler chain
|
|
|
|
// information that may be required for the next handlers.
|
|
|
|
//
|
|
|
|
// The below code makes sure that this conditional handler does not break
|
|
|
|
// the ability that iris provides to its end-devs
|
|
|
|
// to check and modify the per-request handlers chain at runtime.
|
|
|
|
currIdx := ctx.HandlerIndex(-1)
|
|
|
|
currHandlers := ctx.Handlers()
|
2020-02-10 19:35:19 +01:00
|
|
|
|
2019-01-19 22:33:33 +01:00
|
|
|
if currIdx == len(currHandlers)-1 {
|
|
|
|
// if this is the last handler of the chain
|
|
|
|
// just add to the last the new handlers and call Next to fire those.
|
|
|
|
ctx.AddHandler(handlers...)
|
|
|
|
ctx.Next()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// otherwise insert the new handlers in the middle of the current executed chain and the next chain.
|
2020-02-10 19:35:19 +01:00
|
|
|
newHandlers := append(currHandlers[:currIdx+1], append(handlers, currHandlers[currIdx+1:]...)...)
|
2019-01-19 22:33:33 +01:00
|
|
|
ctx.SetHandlers(newHandlers)
|
|
|
|
ctx.Next()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// if not pass, then just execute the next.
|
|
|
|
ctx.Next()
|
|
|
|
}
|
|
|
|
}
|
2020-06-19 19:58:24 +02:00
|
|
|
|
|
|
|
// JoinHandlers returns a copy of "h1" and "h2" Handlers slice joined as one slice of Handlers.
|
|
|
|
func JoinHandlers(h1 Handlers, h2 Handlers) Handlers {
|
|
|
|
if len(h1) == 0 {
|
|
|
|
return h2
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(h2) == 0 {
|
|
|
|
return h1
|
|
|
|
}
|
|
|
|
|
|
|
|
nowLen := len(h1)
|
|
|
|
totalLen := nowLen + len(h2)
|
|
|
|
// create a new slice of Handlers in order to merge the "h1" and "h2"
|
|
|
|
newHandlers := make(Handlers, totalLen)
|
|
|
|
// copy the already Handlers to the just created
|
|
|
|
copy(newHandlers, h1)
|
|
|
|
// start from there we finish, and store the new Handlers too
|
|
|
|
copy(newHandlers[nowLen:], h2)
|
|
|
|
return newHandlers
|
|
|
|
}
|
2020-08-12 06:20:07 +02:00
|
|
|
|
|
|
|
// UpsertHandlers like `JoinHandlers` but it does
|
|
|
|
// NOT copies the handlers entries and it does remove duplicates.
|
|
|
|
func UpsertHandlers(h1 Handlers, h2 Handlers) Handlers {
|
|
|
|
reg:
|
|
|
|
for _, handler := range h2 {
|
|
|
|
name := HandlerName(handler)
|
|
|
|
for i, registeredHandler := range h1 {
|
|
|
|
registeredName := HandlerName(registeredHandler)
|
|
|
|
if name == registeredName {
|
|
|
|
h1[i] = handler // replace this handler with the new one.
|
|
|
|
continue reg // break and continue to the next handler.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
h1 = append(h1, handler) // or just insert it.
|
|
|
|
}
|
|
|
|
|
|
|
|
return h1
|
|
|
|
}
|