diff --git a/g/g.go b/g/g.go index e3a659f2f..bb86eea25 100644 --- a/g/g.go +++ b/g/g.go @@ -6,8 +6,18 @@ package g +import ( + "html/template" +) + // 常用map数据结构 type Map map[string]interface{} // 常用list数据结构 type List []Map + + +// 输出到模板页面时保留HTML标签原意,不做自动escape处理 +func HTML(content string) template.HTML { + return template.HTML(content) +} diff --git a/g/net/ghttp/http_request.go b/g/net/ghttp/http_request.go index fa7209fcf..be13f696f 100644 --- a/g/net/ghttp/http_request.go +++ b/g/net/ghttp/http_request.go @@ -27,6 +27,7 @@ type Request struct { Cookie *Cookie // 与当前请求绑定的Cookie对象(并发安全) Session *Session // 与当前请求绑定的Session对象(并发安全) Response *Response // 对应请求的返回数据操作对象 + Router *Router // 匹配到的路由对象 } // 创建一个Request对象 diff --git a/g/net/ghttp/http_server.go b/g/net/ghttp/http_server.go index 043081c05..7f2b5ffad 100644 --- a/g/net/ghttp/http_server.go +++ b/g/net/ghttp/http_server.go @@ -65,15 +65,20 @@ type Server struct { // 域名、URI与回调函数的绑定记录表 type HandlerMap map[string]*HandlerItem +// 路由对象 +type Router struct { + Uri string // 注册时的pattern - uri + Method string // 注册时的pattern - method + Domain string // 注册时的pattern - domain + Priority int // 优先级,用于链表排序,值越大优先级越高 +} + // http回调函数注册信息 type HandlerItem struct { ctype reflect.Type // 控制器类型 fname string // 回调方法名称 faddr HandlerFunc // 准确的执行方法内存地址(与以上两个参数二选一) - uri string // 注册时的pattern - uri - method string // 注册时的pattern - method - domain string // 注册时的pattern - domain - priority int // 优先级,用于链表排序,值越大优先级越高 + router *Router // 注册时绑定的路由对象 } // http注册函数 diff --git a/g/net/ghttp/http_server_hooks.go b/g/net/ghttp/http_server_hooks.go index 683feaddc..102a0072f 100644 --- a/g/net/ghttp/http_server_hooks.go +++ b/g/net/ghttp/http_server_hooks.go @@ -27,9 +27,11 @@ func (s *Server) setHookHandler(pattern string, hook string, item *HandlerItem) if err != nil { return errors.New("invalid pattern") } - item.uri = uri - item.domain = domain - item.method = method + item.router = &Router { + Uri : uri, + Domain : domain, + Method : method, + } s.hhmu.Lock() defer s.hhmu.Unlock() @@ -44,8 +46,8 @@ func (s *Server) setHookHandler(pattern string, hook string, item *HandlerItem) } p = p.(map[string]interface{})[hook] - array := strings.Split(uri[1:], "/") - item.priority = len(array) + array := strings.Split(uri[1:], "/") + item.router.Priority = len(array) for _, v := range array { if len(v) == 0 { continue @@ -164,8 +166,8 @@ func (s *Server) searchHookHandler(r *Request, hook string) []*hookCacheItem { for i := len(lists) - 1; i >= 0; i-- { for e := lists[i].Front(); e != nil; e = e.Next() { item := e.Value.(*HandlerItem) - if strings.EqualFold(item.method, gDEFAULT_METHOD) || strings.EqualFold(item.method, r.Method) { - regrule, names := s.patternToRegRule(item.uri) + if strings.EqualFold(item.router.Method, gDEFAULT_METHOD) || strings.EqualFold(item.router.Method, r.Method) { + regrule, names := s.patternToRegRule(item.router.Uri) if gregx.IsMatchString(regrule, r.URL.Path) { hookItem := &hookCacheItem {item.faddr, nil} // 如果需要query匹配,那么需要重新解析URL diff --git a/g/net/ghttp/http_server_router.go b/g/net/ghttp/http_server_router.go index f8b493305..8d661a190 100644 --- a/g/net/ghttp/http_server_router.go +++ b/g/net/ghttp/http_server_router.go @@ -40,6 +40,7 @@ func (s *Server) getHandler(r *Request) *HandlerItem { for k, v := range handlerItem.values { r.values[k] = v } + r.Router = handlerItem.item.router return handlerItem.item } return nil @@ -74,10 +75,11 @@ func (s *Server) setHandler(pattern string, item *HandlerItem) error { if err != nil { return errors.New("invalid pattern") } - item.uri = uri - item.domain = domain - item.method = method - + item.router = &Router { + Uri : uri, + Domain : domain, + Method : method, + } s.hmmu.Lock() defer s.hmmu.Unlock() defer s.clearHandlerCache() @@ -88,10 +90,10 @@ func (s *Server) setHandler(pattern string, item *HandlerItem) error { if _, ok := s.handlerTree[domain]; !ok { s.handlerTree[domain] = make(map[string]interface{}) } - p := s.handlerTree[domain] - lists := make([]*list.List, 0) - array := strings.Split(uri[1:], "/") - item.priority = len(array) + p := s.handlerTree[domain] + lists := make([]*list.List, 0) + array := strings.Split(uri[1:], "/") + item.router.Priority = len(array) for k, v := range array { if len(v) == 0 { continue @@ -149,13 +151,13 @@ func (s *Server) setHandler(pattern string, item *HandlerItem) error { // 对比两个HandlerItem的优先级,需要非常注意的是,注意新老对比项的参数先后顺序 func (s *Server) compareHandlerItemPriority(newItem, oldItem *HandlerItem) bool { - if newItem.priority > oldItem.priority { + if newItem.router.Priority > oldItem.router.Priority { return true } - if newItem.priority < oldItem.priority { + if newItem.router.Priority < oldItem.router.Priority { return false } - if strings.Count(newItem.uri, "/:") > strings.Count(oldItem.uri, "/:") { + if strings.Count(newItem.router.Uri, "/:") > strings.Count(oldItem.router.Uri, "/:") { return true } return false @@ -224,8 +226,8 @@ func (s *Server) searchHandlerDynamic(r *Request) *handlerCacheItem { for i := len(lists) - 1; i >= 0; i-- { for e := lists[i].Front(); e != nil; e = e.Next() { item := e.Value.(*HandlerItem) - if strings.EqualFold(item.method, gDEFAULT_METHOD) || strings.EqualFold(item.method, r.Method) { - regrule, names := s.patternToRegRule(item.uri) + if strings.EqualFold(item.router.Method, gDEFAULT_METHOD) || strings.EqualFold(item.router.Method, r.Method) { + regrule, names := s.patternToRegRule(item.router.Uri) if gregx.IsMatchString(regrule, r.URL.Path) { handlerItem := &handlerCacheItem{item, nil} // 如果需要query匹配,那么需要重新解析URL diff --git a/g/os/gview/gview.go b/g/os/gview/gview.go index 25c20e24e..4f894efc8 100644 --- a/g/os/gview/gview.go +++ b/g/os/gview/gview.go @@ -32,6 +32,19 @@ type View struct { // 视图表 var viewMap = gmap.NewStringInterfaceMap() +// 默认的视图对象 +var viewObj = Get(".") + +// 输出到模板页面时保留HTML标签原意,不做自动escape处理 +func HTML(content string) template.HTML { + return template.HTML(content) +} + +// 直接解析模板内容,返回解析后的内容 +func ParseContent(content string, params map[string]interface{}) ([]byte, error) { + return viewObj.ParseContent(content, params) +} + // 获取或者创建一个视图对象 func Get(path string) *View { if r := viewMap.Get(path); r != nil { diff --git a/g/util/gpage/gpage.go b/g/util/gpage/gpage.go index 9a8dafed9..a022d7400 100644 --- a/g/util/gpage/gpage.go +++ b/g/util/gpage/gpage.go @@ -17,55 +17,53 @@ import ( // 分页对象 type Page struct { - pageName string // 分页参数名称 - nextPageTag string // 下一页标签 - prevPageTag string // 上一页标签 - firstPageTag string // 首页标签 - lastPageTag string // 尾页标签 - prevBar string // 上一分页条 - nextBar string // 下一分页条 - totalSize int // 总共条数 - pageBarNum int // 控制记录条的个数 - totalPage int // 总页数 - currentPage int // 当前页 - offset int // 分页的offset条数 - url *url2.URL // URL对象 - route string // 路由规则 - ajaxActionName string // AJAX动作名,当该属性有值时,表示使用AJAX分页 + Url *url2.URL // 当前页面的URL对象 + Route string // 当前页面的路由规则(在静态分页下有效) + TotalSize int // 总共数据条数 + TotalPage int // 总页数 + CurrentPage int // 当前页码 + PageName string // 分页参数名称(GET参数) + NextPageTag string // 下一页标签 + PrevPageTag string // 上一页标签 + FirstPageTag string // 首页标签 + LastPageTag string // 尾页标签 + PrevBar string // 上一分页条 + NextBar string // 下一分页条 + PageBarNum int // 控制分页条的数量 + AjaxActionName string // AJAX方法名,当该属性有值时,表示使用AJAX分页 } // 创建一个分页对象,输入参数分别为: // 总数量、每页数量、当前页码、当前的URL(可以只是URI+QUERY)、(可选)路由规则(例如: /user/list/:page、/order/list/*page) -func New(totalSize, perPage, currentPage int, url string, route...string) *Page { +func New(TotalSize, perPage int, CurrentPage interface{}, url string, route...string) *Page { u, _ := url2.Parse(url) page := &Page { - pageName : "page", - prevPageTag : "<", - nextPageTag : ">", - firstPageTag : "|<", - lastPageTag : ">|", - prevBar : "<<", - nextBar : ">>", - totalSize : totalSize, - totalPage : int(math.Ceil(float64(totalSize/perPage))), - currentPage : currentPage, - offset : (currentPage - 1)*perPage, - pageBarNum : 10, - url : u, + PageName : "page", + PrevPageTag : "<", + NextPageTag : ">", + FirstPageTag : "|<", + LastPageTag : ">|", + PrevBar : "<<", + NextBar : ">>", + TotalSize : TotalSize, + TotalPage : int(math.Ceil(float64(TotalSize/perPage))), + CurrentPage : gconv.Int(CurrentPage), + PageBarNum : 10, + Url : u, } if len(route) > 0 { - page.route = route[0] + page.Route = route[0] } return page } // 启用AJAX分页 -func (page *Page)EnableAjax(actionName string) { - page.ajaxActionName = actionName +func (page *Page) EnableAjax(actionName string) { + page.AjaxActionName = actionName } // 获取显示"下一页"的内容. -func (page *Page) nextPage(styles ... string) string { +func (page *Page) NextPage(styles ... string) string { var curStyle, style string if len(styles) > 0 { curStyle = styles[0] @@ -73,14 +71,14 @@ func (page *Page) nextPage(styles ... string) string { if len(styles) > 1 { style = styles[0] } - if page.currentPage < page.totalPage { - return page.getLink(page.getUrl(page.currentPage + 1), page.nextPageTag, "下一页", style) + if page.CurrentPage < page.TotalPage { + return page.GetLink(page.GetUrl(page.CurrentPage + 1), page.NextPageTag, "下一页", style) } - return fmt.Sprintf(`%s`, curStyle, page.nextPageTag) + return fmt.Sprintf(`%s`, curStyle, page.NextPageTag) } /// 获取显示“上一页”的内容 -func (page *Page) prevPage(styles ... string) string { +func (page *Page) PrevPage(styles ... string) string { var curStyle, style string if len(styles) > 0 { curStyle = styles[0] @@ -88,10 +86,10 @@ func (page *Page) prevPage(styles ... string) string { if len(styles) > 1 { style = styles[0] } - if page.currentPage > 1 { - return page.getLink(page.getUrl(page.currentPage - 1), page.prevPageTag, "上一页", style) + if page.CurrentPage > 1 { + return page.GetLink(page.GetUrl(page.CurrentPage - 1), page.PrevPageTag, "上一页", style) } - return fmt.Sprintf(`%s`, curStyle, page.prevPageTag) + return fmt.Sprintf(`%s`, curStyle, page.PrevPageTag) } /** @@ -99,7 +97,7 @@ func (page *Page) prevPage(styles ... string) string { * * @return string */ -func (page *Page)firstPage(styles ... string) string { +func (page *Page) FirstPage(styles ... string) string { var curStyle, style string if len(styles) > 0 { curStyle = styles[0] @@ -107,14 +105,14 @@ func (page *Page)firstPage(styles ... string) string { if len(styles) > 1 { style = styles[0] } - if page.currentPage == 1 { - return fmt.Sprintf(`%s`, curStyle, page.firstPageTag) + if page.CurrentPage == 1 { + return fmt.Sprintf(`%s`, curStyle, page.FirstPageTag) } - return page.getLink(page.getUrl(1), page.firstPageTag, "第一页", style) + return page.GetLink(page.GetUrl(1), page.FirstPageTag, "第一页", style) } // 获取显示“尾页”的内容 -func (page *Page)lastPage(styles ... string) string { +func (page *Page) LastPage(styles ... string) string { var curStyle, style string if len(styles) > 0 { curStyle = styles[0] @@ -122,14 +120,14 @@ func (page *Page)lastPage(styles ... string) string { if len(styles) > 1 { style = styles[0] } - if page.currentPage == page.totalPage { - return fmt.Sprintf(`%s`, curStyle, page.lastPageTag) + if page.CurrentPage == page.TotalPage { + return fmt.Sprintf(`%s`, curStyle, page.LastPageTag) } - return page.getLink(page.getUrl(page.totalPage), page.lastPageTag, "最后页", style) + return page.GetLink(page.GetUrl(page.TotalPage), page.LastPageTag, "最后页", style) } -// 获得分页条。 -func (page *Page) nowBar(styles ... string) string { +// 获得分页条列表内容 +func (page *Page) PageBar(styles ... string) string { var curStyle, style string if len(styles) > 0 { curStyle = styles[0] @@ -137,43 +135,36 @@ func (page *Page) nowBar(styles ... string) string { if len(styles) > 1 { style = styles[0] } - plus := int(math.Ceil(float64(page.pageBarNum / 2))) - if page.pageBarNum - plus + page.currentPage > page.totalPage { - plus = page.pageBarNum - page.totalPage + page.currentPage + plus := int(math.Ceil(float64(page.PageBarNum / 2))) + if page.PageBarNum - plus + page.CurrentPage > page.TotalPage { + plus = page.PageBarNum - page.TotalPage + page.CurrentPage } - begin := page.currentPage - plus + 1 + begin := page.CurrentPage - plus + 1 if begin < 1 { begin = 1 } ret := "" - for i := begin; i < begin + page.pageBarNum; i++ { - if i <= page.totalPage { - if i != page.currentPage { - ret += page.getLink(page.getUrl(i), gconv.String(i), style, "") + for i := begin; i < begin + page.PageBarNum; i++ { + if i <= page.TotalPage { + if i != page.CurrentPage { + ret += page.GetLink(page.GetUrl(i), gconv.String(i), style, "") } else { ret += fmt.Sprintf(`%d`, curStyle, i) } } else { break } - if i != begin + page.pageBarNum - 1 { - ret += "\n" - } } return ret } -/** -* 获取显示跳转按钮的代码 -* -* @return string -*/ -func (page *Page) selectBar() string { - ret := fmt.Sprintf(`` + for i := 1; i <= page.TotalPage; i++ { + if i == page.CurrentPage { + ret += fmt.Sprintf(``, page.GetUrl(i), i) } else { - ret += fmt.Sprintf(``, i, i) + ret += fmt.Sprintf(``, page.GetUrl(i), i) } } ret += "" @@ -182,85 +173,99 @@ func (page *Page) selectBar() string { // 预定义的分页显示风格内容 func (page *Page) GetContent(mode int) string { - switch (mode) { + switch mode { case 1: - page.nextPageTag = "下一页" - page.prevPageTag = "上一页" - return fmt.Sprintf(`%s %d %s`, page.prevPage(), page.currentPage, page.nextPage()) - + page.NextPageTag = "下一页" + page.PrevPageTag = "上一页" + return fmt.Sprintf( + `%s %d %s`, + page.PrevPage(), + page.CurrentPage, + page.NextPage(), + ) case 2: - page.nextPageTag = "下一页>>" - page.prevPageTag = "<<上一页" - page.firstPageTag = "首页" - page.lastPageTag = "尾页" - return fmt.Sprintf(`%s%s [第%d页] %s%s 第%s页`, - page.firstPage(), page.prevPage(), page.currentPage, page.nextPage(), page.lastPage(), page.selectBar()) - + page.NextPageTag = "下一页>>" + page.PrevPageTag = "<<上一页" + page.FirstPageTag = "首页" + page.LastPageTag = "尾页" + return fmt.Sprintf( + `%s%s[第%d页]%s%s第%s页`, + page.FirstPage(), + page.PrevPage(), + page.CurrentPage, + page.NextPage(), + page.LastPage(), + page.SelectBar(), + ) case 3: - page.nextPageTag = "下一页" - page.prevPageTag = "上一页" - page.firstPageTag = "首页" - page.lastPageTag = "尾页" - pageStr := page.firstPage() + "\n" - pageStr += page.prevPage() + "\n" - pageStr += page.nowBar("current") + "\n" - pageStr += page.nextPage() + "\n" - pageStr += page.lastPage() + "\n" - pageStr += fmt.Sprintf(`当前页%d/%d 共%d条`, page.currentPage, page.totalPage, page.totalSize) + page.NextPageTag = "下一页" + page.PrevPageTag = "上一页" + page.FirstPageTag = "首页" + page.LastPageTag = "尾页" + pageStr := page.FirstPage() + pageStr += page.PrevPage() + pageStr += page.PageBar("current") + pageStr += page.NextPage() + pageStr += page.LastPage() + pageStr += fmt.Sprintf( + `当前页%d/%d 共%d条`, + page.CurrentPage, + page.TotalPage, + page.TotalSize, + ) return pageStr - case 4: - page.nextPageTag = "下一页" - page.prevPageTag = "上一页" - page.firstPageTag = "首页" - page.lastPageTag = "尾页" - pageStr := page.firstPage() + "\n" - pageStr += page.prevPage() + "\n" - pageStr += page.nowBar("current") + "\n" - pageStr += page.nextPage() + "\n" - pageStr += page.lastPage() + "\n" + page.NextPageTag = "下一页" + page.PrevPageTag = "上一页" + page.FirstPageTag = "首页" + page.LastPageTag = "尾页" + pageStr := page.FirstPage() + pageStr += page.PrevPage() + pageStr += page.PageBar("current") + pageStr += page.NextPage() + pageStr += page.LastPage() return pageStr } return "" } // 为指定的页面返回地址值 -func (page *Page) getUrl(pageNo int) string { - url := *page.url - if len(page.url.RawQuery) > 0 && len(page.url.Query().Get(page.pageName)) > 0 { - values := page.url.Query() - values.Set(page.pageName, gconv.String(pageNo)) - url.RawQuery = values.Encode() - } else { +func (page *Page) GetUrl(pageNo int) string { + url := *page.Url + if len(page.Route) > 0 { // 这里基于路由匹配的URL页码替换比较简单,但能满足绝大多数场景 index := -1 - array := strings.Split(page.route, "/") + array := strings.Split(page.Route, "/") for k, v := range array { - if strings.EqualFold(v, ":" + page.pageName) || strings.EqualFold(v, "*" + page.pageName) { + if strings.EqualFold(v, ":" + page.PageName) || strings.EqualFold(v, "*" + page.PageName) { index = k break } } // 替换url.Path中的分页码 if index != -1 { - array := strings.Split(page.url.Path, "/") + array := strings.Split(page.Url.Path, "/") array[index] = gconv.String(pageNo) url.Path = strings.Join(array, "/") + return url.String() } } + values := page.Url.Query() + values.Set(page.PageName, gconv.String(pageNo)) + url.RawQuery = values.Encode() return url.String() } // 获取链接地址 -func (page *Page) getLink(url, text, title, style string) string { +func (page *Page) GetLink(url, text, title, style string) string { if len(style) > 0 { style = fmt.Sprintf(`class="%s" `, style) } - if len(page.ajaxActionName) > 0 { - return fmt.Sprintf(`%s`, style, page.ajaxActionName, url, text) + if len(page.AjaxActionName) > 0 { + return fmt.Sprintf(`%s`, style, page.AjaxActionName, url, text) } else { return fmt.Sprintf(`%s`, style, url, title, text) } diff --git a/geg/util/gpage.go b/geg/util/gpage.go deleted file mode 100644 index d06875634..000000000 --- a/geg/util/gpage.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - "fmt" - "gitee.com/johng/gf/g/util/gpage" -) - -func main() { - // 基本分页示例 - page1 := gpage.New(100, 10, 1, "http://xxx.xxx.xxx/user/list?page=1&type=10#anchor") - fmt.Println(page1.GetContent(3)) - - // 基于静态链接的分页示例 - page2 := gpage.New(100, 10, 1, "http://xxx.xxx.xxx/user/list/1?type=10#anchor", "/user/list/:page") - fmt.Println(page2.GetContent(3)) -} \ No newline at end of file diff --git a/geg/util/gpage/gpage.go b/geg/util/gpage/gpage.go new file mode 100644 index 000000000..a9c96fd2d --- /dev/null +++ b/geg/util/gpage/gpage.go @@ -0,0 +1,39 @@ +package main + +import ( + "gitee.com/johng/gf/g" + "gitee.com/johng/gf/g/os/gview" + "gitee.com/johng/gf/g/net/ghttp" + "gitee.com/johng/gf/g/util/gpage" +) + +func main() { + s := ghttp.GetServer() + s.BindHandler("/page/demo", func(r *ghttp.Request){ + page := gpage.New(100, 10, r.Get("page"), r.URL.String()) + buffer, _ := gview.ParseContent(` + + + + + +
{{.page1}}
+
{{.page2}}
+
{{.page3}}
+
{{.page4}}
+ + + `, g.Map{ + "page1" : gview.HTML(page.GetContent(1)), + "page2" : gview.HTML(page.GetContent(2)), + "page3" : gview.HTML(page.GetContent(3)), + "page4" : gview.HTML(page.GetContent(4)), + }) + r.Response.Write(buffer) + }) + s.SetPort(8199) + s.Run() +} \ No newline at end of file diff --git a/geg/util/gpage/gpage_custom.go b/geg/util/gpage/gpage_custom.go new file mode 100644 index 000000000..cb493fcc5 --- /dev/null +++ b/geg/util/gpage/gpage_custom.go @@ -0,0 +1,47 @@ +package main + +import ( + "gitee.com/johng/gf/g" + "gitee.com/johng/gf/g/os/gview" + "gitee.com/johng/gf/g/net/ghttp" + "gitee.com/johng/gf/g/util/gpage" +) + +// 自定义分页内容 +func pageContent(page *gpage.Page) string { + page.NextPageTag = "NextPage" + page.PrevPageTag = "PrevPage" + page.FirstPageTag = "HomePage" + page.LastPageTag = "LastPage" + pageStr := page.FirstPage() + pageStr += page.PrevPage() + pageStr += page.PageBar("current-page") + pageStr += page.NextPage() + pageStr += page.LastPage() + return pageStr +} + +func main() { + s := ghttp.GetServer() + s.BindHandler("/page/custom/*page", func(r *ghttp.Request){ + page := gpage.New(100, 10, r.Get("page"), r.URL.String(), r.Router.Uri) + buffer, _ := gview.ParseContent(` + + + + + +
{{.page}}
+ + + `, g.Map{ + "page" : gview.HTML(pageContent(page)), + }) + r.Response.Write(buffer) + }) + s.SetPort(8199) + s.Run() +} \ No newline at end of file diff --git a/geg/util/gpage/gpage_static.go b/geg/util/gpage/gpage_static.go new file mode 100644 index 000000000..324c23261 --- /dev/null +++ b/geg/util/gpage/gpage_static.go @@ -0,0 +1,39 @@ +package main + +import ( + "gitee.com/johng/gf/g" + "gitee.com/johng/gf/g/os/gview" + "gitee.com/johng/gf/g/net/ghttp" + "gitee.com/johng/gf/g/util/gpage" +) + +func main() { + s := ghttp.GetServer() + s.BindHandler("/page/static/*page", func(r *ghttp.Request){ + page := gpage.New(100, 10, r.Get("page"), r.URL.String(), r.Router.Uri) + buffer, _ := gview.ParseContent(` + + + + + +
{{.page1}}
+
{{.page2}}
+
{{.page3}}
+
{{.page4}}
+ + + `, g.Map{ + "page1" : gview.HTML(page.GetContent(1)), + "page2" : gview.HTML(page.GetContent(2)), + "page3" : gview.HTML(page.GetContent(3)), + "page4" : gview.HTML(page.GetContent(4)), + }) + r.Response.Write(buffer) + }) + s.SetPort(8199) + s.Run() +} \ No newline at end of file