96 Star 343 Fork 80

andeyalee / faygo

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
apidoc.go 9.86 KB
一键复制 编辑 原始数据 按行查看 历史
andeyalee 提交于 2022-06-27 01:07 . update Copyright
// Copyright 2022 AndeyaLee. All Rights Reserved.
//
// 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.
// API automation documentation.
package faygo
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"path"
"regexp"
"strings"
"github.com/andeya/faygo/swagger"
)
type swaggerFS struct {
jsonPath []byte
http.FileSystem
}
func (s *swaggerFS) Open(name string) (http.File, error) {
f, err := s.FileSystem.Open(name)
if err == nil && name == "/index.html" {
b, err := ioutil.ReadAll(f)
f.Close()
if err != nil {
return f, err
}
b = bytes.Replace(b, []byte(`"/swagger.json"`), s.jsonPath, -1)
info, err := swagger.AssetInfo("swagger-ui/index.html")
if err != nil {
return nil, err
}
return NewFile(b, &FileInfo{
name: "apidoc/index.html",
size: int64(len(b)),
mode: info.Mode(),
modTime: info.ModTime(),
isDir: info.IsDir(),
sys: info.Sys(),
}), err
}
return f, err
}
// register the API doc router.
func (frame *Framework) regAPIdoc() {
swaggerPath := frame.swaggerPath()
fs := FS(&swaggerFS{jsonPath: []byte("\"" + swaggerPath + "\""), FileSystem: swagger.AssetFS()})
if frame.config.APIdoc.NoLimit {
frame.MuxAPI.NamedStaticFS("APIdoc-Swagger", frame.config.APIdoc.Path, fs)
frame.MuxAPI.NamedGET("APIdoc-Swagger-JSON", swaggerPath, newAPIdocJSONHandler())
} else {
allowApidoc := newIPFilter(frame.config.APIdoc.Whitelist, frame.config.APIdoc.RealIP)
frame.MuxAPI.NamedStaticFS("APIdoc-Swagger", frame.config.APIdoc.Path, fs).Use(allowApidoc)
frame.MuxAPI.NamedGET("APIdoc-Swagger-JSON", swaggerPath, newAPIdocJSONHandler(), allowApidoc)
}
tip := `APIdoc's URL path is '` + frame.config.APIdoc.Path
if frame.config.APIdoc.NoLimit {
frame.syslog.Criticalf(tip + `' [free access]`)
} else if len(frame.config.APIdoc.Whitelist) == 0 {
frame.syslog.Criticalf(tip + `' [no access]`)
} else if frame.config.APIdoc.RealIP {
frame.syslog.Criticalf(tip + `' [check real ip for filter]`)
} else {
frame.syslog.Criticalf(tip + `' [check direct ip for filter]`)
}
}
func newAPIdocJSONHandler() HandlerFunc {
return func(ctx *Context) error {
if ctx.frame.apidoc == nil {
ctx.frame.initAPIdoc(ctx.R.Host)
}
ctx.frame.apidoc.Schemes = []string{ctx.Scheme()}
ctx.frame.apidoc.Host = ctx.R.Host
return ctx.JSON(200, ctx.frame.apidoc, true)
}
}
func (frame *Framework) swaggerPath() string {
return strings.TrimRight(frame.config.APIdoc.Path, "/") + "_swagger.json"
}
func (frame *Framework) initAPIdoc(host string) {
rootMuxAPI := frame.MuxAPI
rootTag := &swagger.Tag{
Name: rootMuxAPI.Path(),
Description: apiTagDesc(rootMuxAPI.Name()),
}
frame.apidoc = &swagger.Swagger{
Version: swagger.Version,
Info: &swagger.Info{
Title: strings.Title(frame.Name()) + " API",
ApiVersion: frame.Version(),
Description: frame.config.APIdoc.Desc,
Contact: &swagger.Contact{Email: frame.config.APIdoc.Email},
TermsOfService: frame.config.APIdoc.TermsURL,
License: &swagger.License{
Name: frame.config.APIdoc.License,
Url: frame.config.APIdoc.LicenseURL,
},
},
Host: host,
BasePath: "/",
Tags: []*swagger.Tag{rootTag},
Schemes: []string{"http", "https"},
Paths: map[string]map[string]*swagger.Opera{},
// SecurityDefinitions: map[string]map[string]interface{}{},
// Definitions: map[string]Definition{},
// ExternalDocs: map[string]string{},
}
jsonPattern := frame.swaggerPath()
for _, child := range rootMuxAPI.Children() {
// filter useless API
if (child.pattern == jsonPattern || strings.HasPrefix(child.pattern, frame.config.APIdoc.Path)) && child.HasMethod("GET") {
continue
}
if !child.IsGroup() {
addpath(child, rootTag)
continue
}
childTag := &swagger.Tag{
Name: child.Path(),
Description: apiTagDesc(child.Name()),
}
frame.apidoc.Tags = append(frame.apidoc.Tags, childTag)
for _, grandson := range child.Children() {
if !grandson.IsGroup() {
addpath(grandson, childTag)
continue
}
grandsonTag := &swagger.Tag{
Name: grandson.Path(),
Description: apiTagDesc(grandson.Name()),
}
frame.apidoc.Tags = append(frame.apidoc.Tags, grandsonTag)
for _, progeny := range grandson.Progeny() {
if !progeny.IsGroup() {
addpath(progeny, grandsonTag)
continue
}
}
}
}
}
// 添加API操作项
func addpath(mux *MuxAPI, tag *swagger.Tag) {
operas := map[string]*swagger.Opera{}
pid := apiCreatePath(mux.Path())
summary := apiSummary(mux.Name())
desc := apiDesc(mux.Notes())
for _, method := range mux.Methods() {
if method == "CONNECT" || method == "TRACE" {
continue
}
// if method == "WS" {
// method = "GET"
// }
o := &swagger.Opera{
Tags: []string{tag.Name},
Summary: summary,
Description: desc,
OperationId: pid + "-" + method,
Consumes: swagger.CommonMIMETypes,
Produces: swagger.CommonMIMETypes,
Responses: make(map[string]*swagger.Resp, 1),
// Security: []map[string][]string{},
}
for _, param := range mux.ParamInfos() {
p := &swagger.Parameter{
In: param.In,
Name: param.Name,
Description: param.Desc,
Required: param.Required,
// Items: &Items{},
// Schema: &Schema{},
}
typ := swagger.ParamType(param.Model)
switch p.In {
case "cookie":
continue
default:
switch typ {
case "file":
o.Consumes = []string{"multipart/form-data"}
p.Type = typ
case "array":
subtyp, first, count := swagger.SliceInfo(param.Model)
switch subtyp {
case "object":
ref := apiDefinitions(mux, p.Name, method, param.Model)
p.Schema = &swagger.Schema{
Type: typ,
Items: &swagger.Items{
Ref: "#/definitions/" + ref,
},
}
default:
p.Type = typ
p.Items = &swagger.Items{
Type: subtyp,
Default: first,
}
if count > 0 {
p.Items.Enum = param.Model
}
p.CollectionFormat = "multi"
}
case "object":
ref := apiDefinitions(mux, p.Name, method, param.Model)
p.Schema = &swagger.Schema{
Type: typ,
Ref: "#/definitions/" + ref,
}
default:
p.Type = typ
p.Format = fmt.Sprintf("%T", param.Model)
p.Default = param.Model
}
}
o.Parameters = append(o.Parameters, p)
}
// static file
if strings.HasSuffix(pid, "/{filepath}") {
o.Parameters = append(o.Parameters, &swagger.Parameter{
In: "path",
Name: "filepath",
Type: swagger.ParamType("*"),
Description: "any static path or file",
Required: true,
Format: fmt.Sprintf("%T", "*"),
Default: "",
})
}
operas[strings.ToLower(method)] = o
}
if _operas, ok := mux.frame.apidoc.Paths[pid]; ok {
for k, v := range operas {
_operas[k] = v
}
} else {
mux.frame.apidoc.Paths[pid] = operas
}
}
func apiDefinitions(mux *MuxAPI, pname, method string, format interface{}) (ref string) {
upath := mux.Path()
ref = strings.Replace(path.Join(upath[1:], pname, method), "/", "@", -1)
def := &swagger.Definition{
Type: "object",
Xml: &swagger.Xml{Name: ref},
}
def.Properties = swagger.CreateProperties(format)
if mux.frame.apidoc.Definitions == nil {
mux.frame.apidoc.Definitions = map[string]*swagger.Definition{}
}
mux.frame.apidoc.Definitions[ref] = def
return
}
var (
pathWildcardRegexp = regexp.MustCompile(`/\*[^/]*`)
pathColonRegexp = regexp.MustCompile(`/:[^/]*`)
)
func apiCreatePath(u string) string {
for _, wildcard := range pathWildcardRegexp.FindAllString(u, -1) {
u = strings.Replace(u, wildcard, "/{"+wildcard[2:]+"}", -1)
}
for _, colon := range pathColonRegexp.FindAllString(u, -1) {
u = strings.Replace(u, colon, "/{"+colon[2:]+"}", -1)
}
return u
}
func apiTagDesc(desc string) string {
return strings.TrimSpace(desc)
}
func apiSummary(desc string) string {
return strings.TrimSpace(strings.Split(strings.TrimSpace(desc), "\n")[0])
}
func apiDesc(notes []Notes) string {
var desc string
count := len(notes)
for i, n := range notes {
if count > 1 {
desc += fmt.Sprintf("\n\n======================= Handler %d =======================", i)
}
if n.Note != "" {
desc += fmt.Sprintf("\nNote: %s", strings.TrimSpace(n.Note))
} else {
desc += "\nNote:"
}
if n.Return != nil {
b, _ := json.MarshalIndent(n.Return, "", " ")
desc += fmt.Sprintf("\nReturn: %s", b)
} else {
desc += "\nReturn:"
}
}
if desc != "" {
return "<pre style=\"line-height:18px;\">" + strings.TrimSpace(desc) + "</pre>"
}
return ""
}
// newIPFilter creates middleware that intercepts the specified IP prefix.
func newIPFilter(whitelist []string, realIP bool) HandlerFunc {
var noAccess bool
var match []string
var prefix []string
if len(whitelist) == 0 {
noAccess = true
} else {
for _, s := range whitelist {
if strings.HasSuffix(s, "*") {
prefix = append(prefix, s[:len(s)-1])
} else {
match = append(match, s)
}
}
}
return func(ctx *Context) error {
if noAccess {
ctx.Error(http.StatusForbidden, "no access")
return nil
}
var ip string
if realIP {
ip = ctx.RealIP()
} else {
ip = ctx.IP()
}
for _, ipMatch := range match {
if ipMatch == ip {
ctx.Next()
return nil
}
}
for _, ipPrefix := range prefix {
if strings.HasPrefix(ip, ipPrefix) {
ctx.Next()
return nil
}
}
ctx.Error(http.StatusForbidden, "not allow to access: "+ip)
return nil
}
}
Go
1
https://gitee.com/andeyalee/faygo.git
git@gitee.com:andeyalee/faygo.git
andeyalee
faygo
faygo
master

搜索帮助