Möglichkeiten zur Implementierung des Golang-API-Servers mit automatisch generiertem Code und Dokumentation

In diesem Artikel möchte ich Ihnen erklären, wie Sie schnell und einfach einen Webserver in der Sprache Golang mit Dokumentation dafür erstellen können. Und was sind die Ansätze und Werkzeuge für ihre Umsetzung


Heute werden wir diese vorgefertigten Werkzeuge analysieren:



Swagger Codegen


Beginnen wir mit swagger-api / swagger-codegen von swagger.io . Swagger Codegen vereinfacht den Erstellungsprozess, indem Serverstubs und Client-SDKs für jede in der OpenAPI-Spezifikation (früher als Swagger bezeichnet) definierte API erstellt werden. Sie müssen lediglich Ihre API implementieren.


Unten finden Sie ein Beispiel für unsere Swagger-Datei und ihr visuelles Erscheinungsbild im Swagger-Editor, in dem wir unsere Implementierungen für eine Sache generieren können.


swagger.json
{
  "swagger" : "2.0",
  "info" : {
    "version" : "1.0.0",
    "title" : "Swagger Petstore"
  },
  "host" : "localhost",
  "basePath" : "/v1",
  "tags" : [ {
    "name" : "pet"
  } ],
  "schemes" : [ "https", "http" ],
  "paths" : {
    "/pet" : {
      "post" : {
        "tags" : [ "pet" ],
        "summary" : "Add a new pet to the store",
        "operationId" : "addPet",
        "consumes" : [ "application/json" ],
        "produces" : [ "application/json" ],
        "parameters" : [ {
          "in" : "body",
          "name" : "body",
          "description" : "Pet object that needs to be added to the store",
          "required" : true,
          "schema" : {
            "$ref" : "#/definitions/Pet"
          }
        } ],
        "responses" : {
          "405" : {
            "description" : "Invalid input"
          }
        }
      }
    }
  },
  "definitions" : {
    "Category" : {
      "type" : "object",
      "properties" : {
        "id" : {
          "type" : "integer",
          "format" : "int64"
        },
        "name" : {
          "type" : "string"
        }
      }
    },
    "Tag" : {
      "type" : "object",
      "properties" : {
        "id" : {
          "type" : "integer",
          "format" : "int64"
        },
        "name" : {
          "type" : "string"
        }
      }
    },
    "Pet" : {
      "type" : "object",
      "required" : [ "name", "photoUrls" ],
      "properties" : {
        "id" : {
          "type" : "integer",
          "format" : "int64"
        },
        "category" : {
          "$ref" : "#/definitions/Category"
        },
        "name" : {
          "type" : "string",
          "example" : "doggie"
        },
        "photoUrls" : {
          "type" : "array",
          "items" : {
            "type" : "string"
          }
        },
        "tags" : {
          "type" : "array",
          "items" : {
            "$ref" : "#/definitions/Tag"
          }
        },
        "status" : {
          "type" : "string",
          "description" : "pet status in the store",
          "enum" : [ "available", "pending", "sold" ]
        }
      }
    }
  }
}

Visuelle Ansicht in der Swagger-Benutzeroberfläche

Swagger UI


Export aus dem Swagger Editor

Swagger Editor


Der Client und der Server haben identische Strukturobjekte. Beispieldatei go-client-generated/model_pet.gound go-server-server-generated/go/model_pet.go.


Haustierstrukturmodell in Client und Server
/*
 * Swagger Petstore
 *
 * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
 *
 * API version: 1.0.0
 * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
 */

package swagger

type Pet struct {
    Id int64           `json:"id,omitempty"`
    Category *Category `json:"category,omitempty"`
    Name string        `json:"name"`
    PhotoUrls []string `json:"photoUrls"`
    Tags []Tag         `json:"tags,omitempty"`
    // pet status in the store
    Status string `json:"status,omitempty"`
}

Der Client enthält auch Methoden zum Senden von Anforderungen sowie Server-Endpionten und -Stubs zum Verarbeiten dieser Anforderungen.


Kundenarchitektur


Serverarchitektur


, . , .


go-swagger


  —  go-swagger/go-swagger. go API- Swagger API: , .


  • Swagger
  • Swagger
  • , jsonschema swagger,
  • swagger go
  • swagger
  • ,


:


$ swagger generate server -f ./swagger.json

 

 


, , . .


go-swagger-service/restapi/configure_swagger_petstore.go. : handler', middleware, , , .


// This file is safe to edit. Once it exists it will not be overwritten

package restapi

import (
    "crypto/tls"
    "net/http"

    errors "github.com/go-openapi/errors"
    runtime "github.com/go-openapi/runtime"
    middleware "github.com/go-openapi/runtime/middleware"

    "go-generator/go-swagger-service/restapi/operations"
    "go-generator/go-swagger-service/restapi/operations/pet"
)

//go:generate swagger generate server --target ../../go-swagger --name SwaggerPetstore --spec ../../swagger.json

func configureFlags(api *operations.SwaggerPetstoreAPI) {
    // api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{ ... }
}

func configureAPI(api *operations.SwaggerPetstoreAPI) http.Handler {
    // configure the api here
    api.ServeError = errors.ServeError

    // Set your custom logger if needed. Default one is log.Printf
    // Expected interface func(string, ...interface{})
    //
    // Example:
    // api.Logger = log.Printf

    api.JSONConsumer = runtime.JSONConsumer()

    api.JSONProducer = runtime.JSONProducer()

    if api.PetAddPetHandler == nil {
        api.PetAddPetHandler = pet.AddPetHandlerFunc(func(params pet.AddPetParams) middleware.Responder {
            return middleware.NotImplemented("operation pet.AddPet has not yet been implemented")
        })
    }

    api.ServerShutdown = func() {}

    return setupGlobalMiddleware(api.Serve(setupMiddlewares))
}

// The TLS configuration before HTTPS server starts.
func configureTLS(tlsConfig *tls.Config) {
    // Make all necessary changes to the TLS configuration here.
}

// As soon as server is initialized but not run yet, this function will be called.
// If you need to modify a config, store server instance to stop it individually later, this is the place.
// This function can be called multiple times, depending on the number of serving schemes.
// scheme value will be set accordingly: "http", "https" or "unix"
func configureServer(s *http.Server, scheme, addr string) {
}

// The middleware configuration is for the handler executors. These do not apply to the swagger.json document.
// The middleware executes after routing but before authentication, binding and validation
func setupMiddlewares(handler http.Handler) http.Handler {
    return handler
}

// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document.
// So this is a good place to plug in a panic handling middleware, logging and metrics
func setupGlobalMiddleware(handler http.Handler) http.Handler {
    return handler
}

. 422 .


swagger . enum, required, type, maximum .


Pet
// Code generated by go-swagger; DO NOT EDIT.

package models

// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

import (
    "encoding/json"
    "strconv"

    strfmt "github.com/go-openapi/strfmt"

    "github.com/go-openapi/errors"
    "github.com/go-openapi/swag"
    "github.com/go-openapi/validate"
)

// Pet pet
// swagger:model Pet
type Pet struct {

    // category
    Category *Category `json:"category,omitempty"`

    // id
    ID int64 `json:"id,omitempty"`

    // name
    // Required: true
    Name *string `json:"name"`

    // photo urls
    // Required: true
    PhotoUrls []string `json:"photoUrls"`

    // pet status in the store
    // Enum: [available pending sold]
    Status string `json:"status,omitempty"`

    // tags
    Tags []*Tag `json:"tags"`
}

// Validate validates this pet
func (m *Pet) Validate(formats strfmt.Registry) error {
    var res []error

    if err := m.validateCategory(formats); err != nil {
        res = append(res, err)
    }

    if err := m.validateName(formats); err != nil {
        res = append(res, err)
    }

    if err := m.validatePhotoUrls(formats); err != nil {
        res = append(res, err)
    }

    if err := m.validateStatus(formats); err != nil {
        res = append(res, err)
    }

    if err := m.validateTags(formats); err != nil {
        res = append(res, err)
    }

    if len(res) > 0 {
        return errors.CompositeValidationError(res...)
    }
    return nil
}

func (m *Pet) validateCategory(formats strfmt.Registry) error {

    if swag.IsZero(m.Category) { // not required
        return nil
    }

    if m.Category != nil {
        if err := m.Category.Validate(formats); err != nil {
            if ve, ok := err.(*errors.Validation); ok {
                return ve.ValidateName("category")
            }
            return err
        }
    }

    return nil
}

func (m *Pet) validateName(formats strfmt.Registry) error {

    if err := validate.Required("name", "body", m.Name); err != nil {
        return err
    }

    return nil
}

func (m *Pet) validatePhotoUrls(formats strfmt.Registry) error {

    if err := validate.Required("photoUrls", "body", m.PhotoUrls); err != nil {
        return err
    }

    return nil
}

var petTypeStatusPropEnum []interface{}

func init() {
    var res []string
    if err := json.Unmarshal([]byte(`["available","pending","sold"]`), &res); err != nil {
        panic(err)
    }
    for _, v := range res {
        petTypeStatusPropEnum = append(petTypeStatusPropEnum, v)
    }
}

const (

    // PetStatusAvailable captures enum value "available"
    PetStatusAvailable string = "available"

    // PetStatusPending captures enum value "pending"
    PetStatusPending string = "pending"

    // PetStatusSold captures enum value "sold"
    PetStatusSold string = "sold"
)

// prop value enum
func (m *Pet) validateStatusEnum(path, location string, value string) error {
    if err := validate.Enum(path, location, value, petTypeStatusPropEnum); err != nil {
        return err
    }
    return nil
}

func (m *Pet) validateStatus(formats strfmt.Registry) error {

    if swag.IsZero(m.Status) { // not required
        return nil
    }

    // value enum
    if err := m.validateStatusEnum("status", "body", m.Status); err != nil {
        return err
    }

    return nil
}

func (m *Pet) validateTags(formats strfmt.Registry) error {

    if swag.IsZero(m.Tags) { // not required
        return nil
    }

    for i := 0; i < len(m.Tags); i++ {
        if swag.IsZero(m.Tags[i]) { // not required
            continue
        }

        if m.Tags[i] != nil {
            if err := m.Tags[i].Validate(formats); err != nil {
                if ve, ok := err.(*errors.Validation); ok {
                    return ve.ValidateName("tags" + "." + strconv.Itoa(i))
                }
                return err
            }
        }

    }

    return nil
}

// MarshalBinary interface implementation
func (m *Pet) MarshalBinary() ([]byte, error) {
    if m == nil {
        return nil, nil
    }
    return swag.WriteJSON(m)
}

// UnmarshalBinary interface implementation
func (m *Pet) UnmarshalBinary(b []byte) error {
    var res Pet
    if err := swag.ReadJSON(b, &res); err != nil {
        return err
    }
    *m = res
    return nil
}


$ run ./go-swagger-service/cmd/swagger-petstore-server/main.go --port 8090


:


$ swagger generate client -f ./swagger.json

 

 


, api-sdk. , , .


// Code generated by go-swagger; DO NOT EDIT.

package pet

// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

import (
    "github.com/go-openapi/runtime"

    strfmt "github.com/go-openapi/strfmt"
)

// New creates a new pet API client.
func New(transport runtime.ClientTransport, formats strfmt.Registry) *Client {
    return &Client{transport: transport, formats: formats}
}

/*
Client for pet API
*/
type Client struct {
    transport runtime.ClientTransport
    formats   strfmt.Registry
}

/*
AddPet adds a new pet to the store
*/
func (a *Client) AddPet(params *AddPetParams) error {
    // TODO: Validate the params before sending
    if params == nil {
        params = NewAddPetParams()
    }

    _, err := a.transport.Submit(&runtime.ClientOperation{
        ID:                 "addPet",
        Method:             "POST",
        PathPattern:        "/pet",
        ProducesMediaTypes: []string{"application/json"},
        ConsumesMediaTypes: []string{"application/json"},
        Schemes:            []string{"http", "https"},
        Params:             params,
        Reader:             &AddPetReader{formats: a.formats},
        Context:            params.Context,
        Client:             params.HTTPClient,
    })
    if err != nil {
        return err
    }
    return nil
}

// SetTransport changes the transport on the client
func (a *Client) SetTransport(transport runtime.ClientTransport) {
    a.transport = transport
}

. Generate a spec from source code.


. , swagger .


grpc-gateway


swagger . grpc-ecosystem/grpc-gateway —   protoc. gRPC -, RESTful JSON API gRPC. gRPC.


HTTP + JSON gRPC. API gRPC RESTful .


grpc-gateway reverse-proxy

grpc-gateway   reverse-proxy


swagger.json protoc-gen-swagger.


. .


$ go install \
    github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway \
    github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger \
    github.com/golang/protobuf/protoc-gen-go

 .proto


protobuf
syntax = "proto3";

import "google/api/annotations.proto";
import "google/protobuf/empty.proto";

package pet;

message AddPetRequest {
    int64 id = 1;
    message Category {
        int64 id = 1;
        string name = 2;
    }
    Category category = 2;
    string name = 3;
    repeated string photo_urls = 4;
    message Tag {
        int64 id = 1;
        string name = 2;
    }
    repeated Tag tags = 5;
    //pet status in the store
    enum Status {
        available = 0;
        pending = 1;
        sold = 2;
    }
    Status status = 6;
}

service PetService {
    rpc AddPet (AddPetRequest) returns (google.protobuf.Empty) {
        option (google.api.http) = {
            post: "/pet"
            body: "*"
        };
    }
}

gRPC stub:


$ protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --go_out=plugins=grpc:. \
  pet.proto

pet.pb.go. reverse-proxy protoc-gen-grpc-gateway:


$ protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --grpc-gateway_out=logtostderr=true:. \
  pet.proto

pet.pb.gw.go . gateway. pet.swagger.json protoc-gen-swagger:


$ protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --swagger_out=logtostderr=true:. \
  pet.proto

PetServiceServer.


// PetServiceServer is the server API for PetService service.
type PetServiceServer interface {
   AddPet(context.Context, *AddPetRequest) (*empty.Empty, error)
}

:


main.go
package pet

import (
    "context"
    "flag"
    "net/http"

    "github.com/golang/glog"
    "github.com/grpc-ecosystem/grpc-gateway/runtime"
    gw "grpc-gateway/pet"
    "grpc-gateway/service"
)

func run() error {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    mux := runtime.NewServeMux()
    err := gw.RegisterPetServiceHandlerServer(ctx, mux, &service.PetService{})
    if err != nil {
        return err
    }

    return http.ListenAndServe(":8081", mux)
}

func main() {
    flag.Parse()
    defer glog.Flush()

    if err := run(); err != nil {
        glog.Fatal(err)
    }
}

, . , c grpc-gateway


Swag


Swag Go Swagger 2.0. - Go. swaggo/swag Go ( Swagger UI).


:



Swagger , endpoint'. .


API
// @title Swagger Example API
// @version 1.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/

// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io

// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html

// @host localhost:8080
// @BasePath /api/v1
// @query.collection.format multi

// @securityDefinitions.basic BasicAuth

// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization

// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information

// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information

// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information

// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information

// @x-extension-openapi {"example": "value on a json format"}

func main() {
    // ... 
}

API-
// ShowAccount godoc
// @Summary Show a account
// @Description get string by ID
// @ID get-string-by-int
// @Accept  json
// @Produce  json
// @Param id path int true "Account ID"
// @Success 200 {object} model.Account
// @Header 200 {string} Token "qwerty"
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts/{id} [get]

func (c *ExampleController) ShowAccount(ctx *contex.Context) {
    // ...
}

github.


, , .



Jetzt haben wir gerade unser Verständnis möglicher Interaktionen mit der Prahlerdokumentation erweitert. Wir haben Möglichkeiten untersucht, den Code zu entwerfen und die Dokumentation dafür zu kompilieren.


Und ich denke, dass einige bereits die geeignete Methode zur Implementierung der API ihres neuen Projekts ausgewählt oder die analysierten Beispiele mit den aktuellen verglichen haben.


All Articles