github.com/thetreep/go-swagger@v0.0.0-20240223100711-35af64f14f01/cmd/swagger/commands/serve.go (about)

     1  package commands
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"log"
     8  	"net"
     9  	"net/http"
    10  	"path"
    11  	"strconv"
    12  
    13  	"github.com/go-openapi/loads"
    14  	"github.com/go-openapi/runtime/middleware"
    15  	"github.com/go-openapi/spec"
    16  	"github.com/go-openapi/swag"
    17  	"github.com/gorilla/handlers"
    18  	"github.com/toqueteos/webbrowser"
    19  )
    20  
    21  // ServeCmd to serve a swagger spec with docs ui
    22  type ServeCmd struct {
    23  	BasePath string `long:"base-path" description:"the base path to serve the spec and UI at"`
    24  	Flavor   string `short:"F" long:"flavor" description:"the flavor of docs, can be swagger or redoc" default:"redoc" choice:"redoc" choice:"swagger"`
    25  	DocURL   string `long:"doc-url" description:"override the url which takes a url query param to render the doc ui"`
    26  	NoOpen   bool   `long:"no-open" description:"when present won't open the the browser to show the url"`
    27  	NoUI     bool   `long:"no-ui" description:"when present, only the swagger spec will be served"`
    28  	Flatten  bool   `long:"flatten" description:"when present, flatten the swagger spec before serving it"`
    29  	Port     int    `long:"port" short:"p" description:"the port to serve this site" env:"PORT"`
    30  	Host     string `long:"host" description:"the interface to serve this site, defaults to 0.0.0.0" default:"0.0.0.0" env:"HOST"`
    31  	Path     string `long:"path" description:"the uri path at which the docs will be served" default:"docs"`
    32  }
    33  
    34  // Execute the serve command
    35  func (s *ServeCmd) Execute(args []string) error {
    36  	if len(args) == 0 {
    37  		return errors.New("specify the spec to serve as argument to the serve command")
    38  	}
    39  
    40  	specDoc, err := loads.Spec(args[0])
    41  	if err != nil {
    42  		return err
    43  	}
    44  
    45  	if s.Flatten {
    46  		specDoc, err = specDoc.Expanded(&spec.ExpandOptions{
    47  			SkipSchemas:         false,
    48  			ContinueOnError:     true,
    49  			AbsoluteCircularRef: true,
    50  		})
    51  
    52  		if err != nil {
    53  			return err
    54  		}
    55  	}
    56  
    57  	b, err := json.MarshalIndent(specDoc.Spec(), "", "  ")
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	basePath := s.BasePath
    63  	if basePath == "" {
    64  		basePath = "/"
    65  	}
    66  
    67  	listener, err := net.Listen("tcp4", net.JoinHostPort(s.Host, strconv.Itoa(s.Port)))
    68  	if err != nil {
    69  		return err
    70  	}
    71  	sh, sp, err := swag.SplitHostPort(listener.Addr().String())
    72  	if err != nil {
    73  		return err
    74  	}
    75  	if sh == "0.0.0.0" {
    76  		sh = "localhost"
    77  	}
    78  
    79  	visit := s.DocURL
    80  	handler := http.NotFoundHandler()
    81  	if !s.NoUI {
    82  		if s.Flavor == "redoc" {
    83  			handler = middleware.Redoc(middleware.RedocOpts{
    84  				BasePath: basePath,
    85  				SpecURL:  path.Join(basePath, "swagger.json"),
    86  				Path:     s.Path,
    87  			}, handler)
    88  			visit = fmt.Sprintf("http://%s:%d%s", sh, sp, path.Join(basePath, "docs"))
    89  		} else if visit != "" || s.Flavor == "swagger" {
    90  			handler = middleware.SwaggerUI(middleware.SwaggerUIOpts{
    91  				BasePath: basePath,
    92  				SpecURL:  path.Join(basePath, "swagger.json"),
    93  				Path:     s.Path,
    94  			}, handler)
    95  			visit = fmt.Sprintf("http://%s:%d%s", sh, sp, path.Join(basePath, s.Path))
    96  		}
    97  	}
    98  
    99  	handler = handlers.CORS()(middleware.Spec(basePath, b, handler))
   100  	errFuture := make(chan error)
   101  	go func() {
   102  		docServer := new(http.Server)
   103  		docServer.SetKeepAlivesEnabled(true)
   104  		docServer.Handler = handler
   105  
   106  		errFuture <- docServer.Serve(listener)
   107  	}()
   108  
   109  	if !s.NoOpen && !s.NoUI {
   110  		err := webbrowser.Open(visit)
   111  		if err != nil {
   112  			return err
   113  		}
   114  	}
   115  	log.Println("serving docs at", visit)
   116  	return <-errFuture
   117  }