mirror of
https://github.com/niusmallnan/steve.git
synced 2025-09-08 16:39:36 +00:00
Refactor
This commit is contained in:
18
pkg/schemaserver/parse/browser.go
Normal file
18
pkg/schemaserver/parse/browser.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func IsBrowser(req *http.Request, checkAccepts bool) bool {
|
||||
accepts := strings.ToLower(req.Header.Get("Accept"))
|
||||
userAgent := strings.ToLower(req.Header.Get("User-Agent"))
|
||||
|
||||
if accepts == "" || !checkAccepts {
|
||||
accepts = "*/*"
|
||||
}
|
||||
|
||||
// User agent has Mozilla and browser accepts */*
|
||||
return strings.Contains(userAgent, "mozilla") && strings.Contains(accepts, "*/*")
|
||||
}
|
23
pkg/schemaserver/parse/mux.go
Normal file
23
pkg/schemaserver/parse/mux.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
)
|
||||
|
||||
func MuxURLParser(rw http.ResponseWriter, req *http.Request, schemas *types.APISchemas) (ParsedURL, error) {
|
||||
vars := mux.Vars(req)
|
||||
url := ParsedURL{
|
||||
Type: vars["type"],
|
||||
Name: vars["name"],
|
||||
Link: vars["link"],
|
||||
Prefix: vars["prefix"],
|
||||
Method: req.Method,
|
||||
Action: vars["action"],
|
||||
Query: req.URL.Query(),
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
168
pkg/schemaserver/parse/parse.go
Normal file
168
pkg/schemaserver/parse/parse.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/schemaserver/urlbuilder"
|
||||
)
|
||||
|
||||
const (
|
||||
maxFormSize = 2 * 1 << 20
|
||||
)
|
||||
|
||||
var (
|
||||
allowedFormats = map[string]bool{
|
||||
"html": true,
|
||||
"json": true,
|
||||
"yaml": true,
|
||||
}
|
||||
)
|
||||
|
||||
type ParsedURL struct {
|
||||
Type string
|
||||
Name string
|
||||
Link string
|
||||
Method string
|
||||
Action string
|
||||
Prefix string
|
||||
SubContext map[string]string
|
||||
Query url.Values
|
||||
}
|
||||
|
||||
type URLParser func(rw http.ResponseWriter, req *http.Request, schemas *types.APISchemas) (ParsedURL, error)
|
||||
|
||||
type Parser func(apiOp *types.APIRequest, urlParser URLParser) error
|
||||
|
||||
func Parse(apiOp *types.APIRequest, urlParser URLParser) error {
|
||||
var err error
|
||||
|
||||
if apiOp.Request == nil {
|
||||
apiOp.Request, err = http.NewRequest("GET", "/", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
apiOp = types.StoreAPIContext(apiOp)
|
||||
|
||||
if apiOp.Method == "" {
|
||||
apiOp.Method = parseMethod(apiOp.Request)
|
||||
}
|
||||
if apiOp.ResponseFormat == "" {
|
||||
apiOp.ResponseFormat = parseResponseFormat(apiOp.Request)
|
||||
}
|
||||
|
||||
// The response format is guaranteed to be set even in the event of an error
|
||||
parsedURL, err := urlParser(apiOp.Response, apiOp.Request, apiOp.Schemas)
|
||||
// wait to check error, want to set as much as possible
|
||||
|
||||
if apiOp.Type == "" {
|
||||
apiOp.Type = parsedURL.Type
|
||||
}
|
||||
if apiOp.Name == "" {
|
||||
apiOp.Name = parsedURL.Name
|
||||
}
|
||||
if apiOp.Link == "" {
|
||||
apiOp.Link = parsedURL.Link
|
||||
}
|
||||
if apiOp.Action == "" {
|
||||
apiOp.Action = parsedURL.Action
|
||||
}
|
||||
if apiOp.Query == nil {
|
||||
apiOp.Query = parsedURL.Query
|
||||
}
|
||||
if apiOp.Method == "" && parsedURL.Method != "" {
|
||||
apiOp.Method = parsedURL.Method
|
||||
}
|
||||
if apiOp.URLPrefix == "" {
|
||||
apiOp.URLPrefix = parsedURL.Prefix
|
||||
}
|
||||
|
||||
if apiOp.URLBuilder == nil {
|
||||
// make error local to not override the outer error we have yet to check
|
||||
var err error
|
||||
apiOp.URLBuilder, err = urlbuilder.New(apiOp.Request, &urlbuilder.DefaultPathResolver{
|
||||
Prefix: apiOp.URLPrefix,
|
||||
}, apiOp.Schemas)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if apiOp.Schema == nil && apiOp.Schemas != nil {
|
||||
apiOp.Schema = apiOp.Schemas.LookupSchema(apiOp.Type)
|
||||
}
|
||||
|
||||
if apiOp.Schema != nil && apiOp.Type == "" {
|
||||
apiOp.Type = apiOp.Schema.ID
|
||||
}
|
||||
|
||||
if err := ValidateMethod(apiOp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseResponseFormat(req *http.Request) string {
|
||||
format := req.URL.Query().Get("_format")
|
||||
|
||||
if format != "" {
|
||||
format = strings.TrimSpace(strings.ToLower(format))
|
||||
}
|
||||
|
||||
/* Format specified */
|
||||
if allowedFormats[format] {
|
||||
return format
|
||||
}
|
||||
|
||||
// User agent has Mozilla and browser accepts */*
|
||||
if IsBrowser(req, true) {
|
||||
return "html"
|
||||
}
|
||||
|
||||
if isYaml(req) {
|
||||
return "yaml"
|
||||
}
|
||||
return "json"
|
||||
}
|
||||
|
||||
func isYaml(req *http.Request) bool {
|
||||
return strings.Contains(req.Header.Get("Accept"), "application/yaml")
|
||||
}
|
||||
|
||||
func parseMethod(req *http.Request) string {
|
||||
method := req.URL.Query().Get("_method")
|
||||
if method == "" {
|
||||
method = req.Method
|
||||
}
|
||||
return method
|
||||
}
|
||||
|
||||
func Body(req *http.Request) (types.APIObject, error) {
|
||||
req.ParseMultipartForm(maxFormSize)
|
||||
if req.MultipartForm != nil {
|
||||
return valuesToBody(req.MultipartForm.Value), nil
|
||||
}
|
||||
|
||||
if req.PostForm != nil && len(req.PostForm) > 0 {
|
||||
return valuesToBody(map[string][]string(req.Form)), nil
|
||||
}
|
||||
|
||||
return ReadBody(req)
|
||||
}
|
||||
|
||||
func valuesToBody(input map[string][]string) types.APIObject {
|
||||
result := map[string]interface{}{}
|
||||
for k, v := range input {
|
||||
result[k] = v
|
||||
}
|
||||
return toAPI(result)
|
||||
}
|
56
pkg/schemaserver/parse/read_input.go
Normal file
56
pkg/schemaserver/parse/read_input.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/httperror"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/data/convert"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
)
|
||||
|
||||
const reqMaxSize = (2 * 1 << 20) + 1
|
||||
|
||||
var bodyMethods = map[string]bool{
|
||||
http.MethodPut: true,
|
||||
http.MethodPost: true,
|
||||
}
|
||||
|
||||
type Decode func(interface{}) error
|
||||
|
||||
func ReadBody(req *http.Request) (types.APIObject, error) {
|
||||
if !bodyMethods[req.Method] {
|
||||
return types.APIObject{}, nil
|
||||
}
|
||||
|
||||
decode := getDecoder(req, io.LimitReader(req.Body, maxFormSize))
|
||||
|
||||
data := map[string]interface{}{}
|
||||
if err := decode(&data); err != nil {
|
||||
return types.APIObject{}, httperror.NewAPIError(validation.InvalidBodyContent,
|
||||
fmt.Sprintf("Failed to parse body: %v", err))
|
||||
}
|
||||
|
||||
return toAPI(data), nil
|
||||
}
|
||||
|
||||
func toAPI(data map[string]interface{}) types.APIObject {
|
||||
return types.APIObject{
|
||||
Type: convert.ToString(data["type"]),
|
||||
ID: convert.ToString(data["id"]),
|
||||
Object: data,
|
||||
}
|
||||
}
|
||||
|
||||
func getDecoder(req *http.Request, reader io.Reader) Decode {
|
||||
if req.Header.Get("Content-type") == "application/yaml" {
|
||||
return yaml.NewYAMLToJSONDecoder(reader).Decode
|
||||
}
|
||||
decoder := json.NewDecoder(reader)
|
||||
decoder.UseNumber()
|
||||
return decoder.Decode
|
||||
}
|
47
pkg/schemaserver/parse/validate.go
Normal file
47
pkg/schemaserver/parse/validate.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/httperror"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
)
|
||||
|
||||
var (
|
||||
supportedMethods = map[string]bool{
|
||||
http.MethodPost: true,
|
||||
http.MethodGet: true,
|
||||
http.MethodPut: true,
|
||||
http.MethodPatch: true,
|
||||
http.MethodDelete: true,
|
||||
}
|
||||
)
|
||||
|
||||
func ValidateMethod(request *types.APIRequest) error {
|
||||
if request.Action != "" && request.Method == http.MethodPost {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !supportedMethods[request.Method] {
|
||||
return httperror.NewAPIError(validation.MethodNotAllowed, fmt.Sprintf("Invalid method %s not supported", request.Method))
|
||||
}
|
||||
|
||||
if request.Type == "" || request.Schema == nil || request.Link != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
allowed := request.Schema.ResourceMethods
|
||||
if request.Name == "" {
|
||||
allowed = request.Schema.CollectionMethods
|
||||
}
|
||||
|
||||
for _, method := range allowed {
|
||||
if method == request.Method {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return httperror.NewAPIError(validation.MethodNotAllowed, fmt.Sprintf("Method %s not supported", request.Method))
|
||||
}
|
Reference in New Issue
Block a user