LoginSignup
3
2

More than 5 years have passed since last update.

Go製WAFのuconを触ってみた その2

Last updated at Posted at 2016-05-13

Swagger Plugin

ここを見ながら頑張ります。
https://github.com/favclip/ucon/tree/master/sample/swagger

まとめ

  • 難しい!!
  • [serverName]/api/swagger.jsonでJSONファイルを取得できる
  • swagger.NewHandlerInfoでhandlerをwrapしよう
  • swagger.NewPlugin(*Options)で作成
  • ucon.DefaultMux.Prepare()でPlugin起動
  • swagger.NewHandlerInfoでwrapしたものはucon.HandlerFuncではなくucon.Handleで登録する!
  • swagger-uiで見るにはすべてのAPIでJSONを返す必要がある?(HTTP Statusのみでも)

Swagger Pluginを作成する

plugin.go
// NewPlugin returns new swagger plugin configured with the options.
func NewPlugin(opts *Options) *Plugin {
    if opts == nil {
        opts = &Options{}
    }
    plugin := &Plugin{
        options:          opts,
        swagger:          opts.Object,
        typeSchemaMapper: make(map[reflect.Type]*TypeSchema),
    }
    for k, v := range DefaultTypeSchemaMapper {
        plugin.typeSchemaMapper[k] = v
    }
    var _ ucon.HandlersScannerPlugin = plugin

    return plugin
}

&swagger.Optionを引数にして新しいPluginを返しています。
DefaultTypeSchemaMapperから定義済のschemaを登録しています。
var _ ucon.HandlersScannerPlugin = pluginはGoでよく見る、右辺が左辺のInterfaceを実装しているかを確認する処理です。

swagger.Option.Object

swagger.Option.Object
type Options struct {
    Object                 *Object
    DefinitionNameModifier func(refT reflect.Type, defName string) string
}

// Object is https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object
type Object struct {
    Swagger             string                 `json:"swagger" swagger:",req,d=2.0"`
    Info                *Info                  `json:"info" swagger:",req"`
    Host                string                 `json:"host,omitempty"`
    BasePath            string                 `json:"basePath,omitempty"`
    Schemes             []string               `json:"schemes,omitempty" swagger:",d=https"`
    Consumes            []string               `json:"consumes,omitempty" swagger:",d=application/json"`
    Produces            []string               `json:"produces,omitempty" swagger:",d=application/json"`
    Paths               Paths                  `json:"paths" swagger:",req"`
    Definitions         Definitions            `json:"definitions,omitempty"`
    Parameters          ParametersDefinitions  `json:"parameters,omitempty"`
    Responses           ResponsesDefinitions   `json:"responses,omitempty"`
    SecurityDefinitions SecurityDefinitions    `json:"securityDefinitions,omitempty"`
    Security            []SecurityRequirement  `json:"security,omitempty"`
    Tags                []*Tag                 `json:"tags,omitempty"`
    ExternalDocs        *ExternalDocumentation `json:"externalDocs,omitempty"`
}

swaggerで定義されているSchemaのObjectでしょうかね。。

Pluginを登録

register_plugin
///// core.go /////
// Plugin can append Plugin to ServeMux.
func Plugin(plugin interface{}) {
    DefaultMux.Plugin(plugin)
}

// Plugin can append Plugin to ServeMux.
func (m *ServeMux) Plugin(plugin interface{}) {
    p, ok := plugin.(*pluginContainer)
    if !ok {
        p = &pluginContainer{base: plugin}
    }
    p.check()
    m.plugins = append(m.plugins, p)
}

///// plugin.go /////
type pluginContainer struct {
    base interface{}
}

func (p *pluginContainer) check() {
    if p.HandlersScanner() != nil {
        return
    }

    panic(fmt.Sprintf("unused plugin: %#v", p.base))
}

// HandlersScanner returns itself if it implements HandlersScannerPlugin.
func (p *pluginContainer) HandlersScanner() HandlersScannerPlugin {
    if v, ok := p.base.(HandlersScannerPlugin); ok {
        return v
    }

    return nil
}

// HandlersScannerPlugin is an interface to make a plugin for scanning request handlers.
type HandlersScannerPlugin interface {
    HandlersScannerProcess(m *ServeMux, rds []*RouteDefinition) error
}

全体的に登録したいPluginがHandlersScannerPluginを実装していればPluginに登録可能のようです。

Pluginを使う

ucon.DefautlMux.Prepare()を実行します

core.go

// Prepare the ServeMux.
// Plugin is not show affect to anything.
// This method is enabled plugins.
func (m *ServeMux) Prepare() {
    for _, plugin := range m.plugins {
        used := false
        if sc := plugin.HandlersScanner(); sc != nil {
            err := sc.HandlersScannerProcess(m, m.router.handlers)
            if err != nil {
                panic(err)
            }
            used = true
        }
        if !used {
            panic(fmt.Sprintf("unused plugin: %#v", plugin))
        }
    }
}

先ほど登録したpluginsを実行しています。
plugin.HandlersScanner() == nilの場合はused == falseのままなのでpanicするようです。

Swagger PluginのHandlersScannerProcess(m, m.router.handlers)を見る

Pluginが実行されるのは、HandlersScannerProcess(m, m.router.handlers)なので、
Swagger Pluginで実装されている箇所を見ていきます。

plugin.go

// HandlersScannerProcess executes scanning all registered handlers to serve swagger.json.
func (p *Plugin) HandlersScannerProcess(m *ucon.ServeMux, rds []*ucon.RouteDefinition) error {
    // construct swagger.json
    for _, rd := range rds {
        err := p.processHandler(rd)
        if err != nil {
            return err
        }
    }

    err := p.swagger.finish()
    if err != nil {
        return err
    }

    // supply swagger.json endpoint
    m.HandleFunc("GET", "/api/swagger.json", func(w http.ResponseWriter, r *http.Request) *Object {
        return p.swagger
    })

    return nil
}

p.processHandler(*ucon.RouteDefinition)p.swagger.finish()m.HandlerFuncの順に実行されています。
順番に見ていきます。

p.processHandler(*ucon.RouteDfinition)
func (p *Plugin) processHandler(rd *ucon.RouteDefinition) error {
    if p.swagger == nil {
        p.swagger = &Object{}
    }
    if p.swagger.Paths == nil {
        p.swagger.Paths = make(Paths, 0)
    }
    if p.swagger.Definitions == nil {
        p.swagger.Definitions = make(Definitions, 0)
    }

    item := p.swagger.Paths[rd.PathTemplate.PathTemplate]
    if item == nil {
        item = &PathItem{}
    }

    var putOperation func(op *Operation)
    // putOperationの設定は省略

    op, err := p.extractHandlerInfo(rd)
    if err != nil {
        return err
    }
    if op != nil {
        p.swagger.Paths[rd.PathTemplate.PathTemplate] = item

        putOperation(op)

        for _, tsc := range p.typeSchemaMapper {
            if !tsc.AllowRef {
                continue
            }
            if tsc.RefName == "" {
                return errors.New("Name is required")
            }

            _, ok := p.swagger.Definitions[tsc.RefName]
            if !ok {
                p.swagger.Definitions[tsc.RefName] = tsc.Schema
            }
        }
    }

    return nil
}

p.swaggerに各種設定を登録していることがわかります。
p.extractHandlerInfo(*ucon.RouteDfinition)を見てみます。

func (p *Plugin) extractHandlerInfo(rd *ucon.RouteDefinition) (*Operation, error) {
    // とても長いので省略・・・
}

まとめると、HandleFuncで登録したものがゴニョゴニョ処理されているようでした・・・(多分)

p.swagger.finish()関係は長いのでコード内にインラインで記述していきます。

p.swagger.finish()
func (o *Object) finish() error {
    return checkObject(reflect.ValueOf(o))
}

func checkObject(refV reflect.Value) error {
    if refV.Kind() == reflect.Ptr {
        refV = refV.Elem()
    }

    var checkNext func(fV reflect.Value) error
    // checkNextの設定は省略

    for i, numField := 0, refV.NumField(); i < numField; i++ {
        sf := refV.Type().Field(i)
        fV := refV.Field(i)
        swaggerTag := NewTagSwagger(sf.Tag)

        // `swagger:",d=hogehoge`のhogehogeがObjectのFieldに設定される
        if d := swaggerTag.Default(); d != "" && ucon.IsEmpty(fV) {
            err := ucon.SetValueFromString(fV, d)
            if err != nil {
                return err
            }
        }
        // `swagger:"req"`のようにreqがあり、かつそのFieldが空の場合にエラー
        // reqがついたもののうち、Pathsはucon.HandleFuncしていれば自動で登録されている
        if swaggerTag.Required() && ucon.IsEmpty(fV) {
            return fmt.Errorf("%s is required", sf.Name)
        }
        // `swagger:",enum=a|b|c"のようにa,b,cがstringであれば処理が走る
        if enum := swaggerTag.Enum(); len(enum) != 0 && !ucon.IsEmpty(fV) {
            if sf.Type.Kind() != reflect.String {
                return fmt.Errorf("unsupported kind: %s", sf.Type.Kind().String())
            }

            str := fV.String()
            found := false
            for _, c := range enum {
                if c == str {
                    found = true
                    break
                }
            }

            if !found {
                return fmt.Errorf("invalid value: %s in %s", str, sf.Name)
            }
        }

        // checkNextはarray,slice,struct,mapなど入れ子の構造に使用される
        // 特にstructの場合はcheckObject自体が再帰的に実行される
        err := checkNext(fV)
        if err != nil {
            return err
        }
    }

    return nil
}

最終的にHandlerFuncでp.swaggerを返しています。
なお、ucon.Middleware(ResponseMapper())ucon.Orthodox()しないと多分エラーとなるので注意。

さて、ここまで読んで、p.swagger.Swaggerに値が入っていないことに気がついた。
これはどこで定義されているかとsampleに戻ると、swagger.NewHandlerInfoが怪しそうだと気づく。
それでは、見てみましょう・・

plugin.go
// NewHandlerInfo returns new HandlerInfo containing given handler function.
func NewHandlerInfo(handler interface{}) *HandlerInfo {
    ucon.CheckFunction(handler)
    return &HandlerInfo{
        HandlerFunc: handler,
    }
}

特に何もなかった。まあいいかw
NewHandlerInfoをしないと、おそらくp.extractHandlerInfo(*ucon.RouteDfinition)が機能しないと思われます。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2