blob: e121d9b505e9d949785f119d607af4e31d457d44 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package v1
import (
"context"
"encoding/json"
"errors"
"github.com/apache/servicecomb-kie/server/pubsub"
"github.com/apache/servicecomb-kie/server/service"
uuid "github.com/satori/go.uuid"
"net/http"
"strconv"
"strings"
"time"
"github.com/apache/servicecomb-kie/pkg/common"
goRestful "github.com/emicklei/go-restful"
"github.com/go-chassis/go-chassis/server/restful"
"github.com/go-mesh/openlogging"
"gopkg.in/yaml.v2"
)
//const of server
//err
var (
ErrInvalidRev = errors.New(common.MsgInvalidRev)
)
//ReadDomain get domain info from attribute
func ReadDomain(context *restful.Context) interface{} {
return context.ReadRestfulRequest().Attribute("domain")
}
//ReadFindDepth get find depth
func ReadFindDepth(context *restful.Context) (int, error) {
d := context.ReadRestfulRequest().HeaderParameter(common.HeaderDepth)
if d == "" {
return 1, nil
}
depth, err := strconv.Atoi(d)
if err != nil {
return 0, err
}
return depth, nil
}
//ReadLabelCombinations get query combination from url
//q=app:default+service:payment&q=app:default
func ReadLabelCombinations(req *goRestful.Request) ([]map[string]string, error) {
queryCombinations := req.QueryParameters(common.QueryParamQ)
labelCombinations := make([]map[string]string, 0)
for _, queryStr := range queryCombinations {
labelStr := strings.Split(queryStr, " ")
labels := make(map[string]string, len(labelStr))
for _, label := range labelStr {
l := strings.Split(label, ":")
if len(l) != 2 {
return nil, errors.New("wrong query syntax:" + label)
}
labels[l[0]] = l[1]
}
if len(labels) == 0 {
continue
}
labelCombinations = append(labelCombinations, labels)
}
if len(labelCombinations) == 0 {
return []map[string]string{{"default": "default"}}, nil
}
return labelCombinations, nil
}
//WriteErrResponse write error message to client
func WriteErrResponse(context *restful.Context, status int, msg, contentType string) {
context.WriteHeader(status)
b, _ := json.MarshalIndent(&ErrorMsg{Msg: msg}, "", " ")
context.ReadRestfulResponse().AddHeader(goRestful.HEADER_ContentType, contentType)
context.Write(b)
}
func readRequest(ctx *restful.Context, v interface{}) error {
if ctx.ReadHeader(common.HeaderContentType) == common.ContentTypeYaml {
return yaml.NewDecoder(ctx.ReadRequest().Body).Decode(v)
}
return json.NewDecoder(ctx.ReadRequest().Body).Decode(v) // json is default
}
func writeYaml(resp *goRestful.Response, v interface{}) error {
if v == nil {
resp.WriteHeader(http.StatusOK)
return nil
}
resp.Header().Set(common.HeaderContentType, common.ContentTypeYaml)
resp.WriteHeader(http.StatusOK)
return yaml.NewEncoder(resp).Encode(v)
}
func writeResponse(ctx *restful.Context, v interface{}) error {
if ctx.ReadHeader(common.HeaderAccept) == common.ContentTypeYaml {
return writeYaml(ctx.Resp, v)
}
return ctx.WriteJSON(v, goRestful.MIME_JSON) // json is default
}
func getLabels(rctx *restful.Context) (map[string]string, error) {
labelSlice := rctx.Req.QueryParameters("label")
if len(labelSlice) == 0 {
return nil, nil
}
labels := make(map[string]string, len(labelSlice))
for _, v := range labelSlice {
v := strings.Split(v, ":")
if len(v) != 2 {
return nil, errors.New(common.MsgIllegalLabels)
}
labels[v[0]] = v[1]
}
return labels, nil
}
func isRevised(ctx context.Context, revStr, domain string) (bool, error) {
rev, err := strconv.ParseInt(revStr, 10, 64)
if err != nil {
return false, ErrInvalidRev
}
latest, err := service.RevisionService.GetRevision(ctx, domain)
if err != nil {
return false, err
}
if latest > rev {
return true, nil
}
return false, nil
}
func getMatchPattern(rctx *restful.Context) string {
m := rctx.ReadQueryParameter(common.QueryParamMatch)
if m != "" && m != common.PatternExact {
return ""
}
return m
}
func eventHappened(rctx *restful.Context, waitStr string, topic *pubsub.Topic) (bool, error) {
d, err := time.ParseDuration(waitStr)
if err != nil || d > common.MaxWait {
return false, errors.New(common.MsgInvalidWait)
}
happened := true
o := &pubsub.Observer{
UUID: uuid.NewV4().String(),
RemoteIP: rctx.ReadRequest().RemoteAddr, //TODO x forward ip
UserAgent: rctx.ReadHeader("User-Agent"),
Event: make(chan *pubsub.KVChangeEvent, 1),
}
pubsub.ObserveOnce(o, topic)
select {
case <-time.After(d):
happened = false
case <-o.Event:
}
return happened, nil
}
// size from 1 to start
func checkPagination(pageNum, pageSize string) (int64, int64, error) {
var err error
var num, size int64
if pageNum != "" {
num, err = strconv.ParseInt(pageNum, 10, 64)
if err != nil {
return 0, 0, err
}
if num < 1 {
return 0, 0, errors.New("invalid pageNum number")
}
}
if pageSize != "" {
size, err = strconv.ParseInt(pageSize, 10, 64)
if err != nil {
return 0, 0, errors.New("invalid pageSize number")
}
if size < 1 || size > 100 {
return 0, 0, errors.New("invalid pageSize number")
}
}
return num, size, err
}
func checkStatus(status string) (string, error) {
if status != "" {
if status != common.StatusEnabled && status != common.StatusDisabled {
return "", errors.New("invalid status string")
}
}
return status, nil
}
func queryAndResponse(rctx *restful.Context,
domain interface{}, project string, key string, labels map[string]string, pageNum, pageSize int64, status string) {
m := getMatchPattern(rctx)
opts := []service.FindOption{
service.WithKey(key),
service.WithLabels(labels),
service.WithPageNum(pageNum),
service.WithPageSize(pageSize),
}
if m == common.PatternExact {
opts = append(opts, service.WithExactLabels())
}
if status != "" {
opts = append(opts, service.WithStatus(status))
}
kv, err := service.KVService.List(rctx.Ctx, domain.(string), project, opts...)
if err != nil {
if err == service.ErrKeyNotExists {
WriteErrResponse(rctx, http.StatusNotFound, err.Error(), common.ContentTypeText)
return
}
WriteErrResponse(rctx, http.StatusInternalServerError, err.Error(), common.ContentTypeText)
return
}
rev, err := service.RevisionService.GetRevision(rctx.Ctx, domain.(string))
if err != nil {
WriteErrResponse(rctx, http.StatusInternalServerError, err.Error(), common.ContentTypeText)
return
}
rctx.ReadResponseWriter().Header().Set(common.HeaderRevision, strconv.FormatInt(rev, 10))
err = writeResponse(rctx, kv)
rctx.Ctx = context.WithValue(rctx.Ctx, common.RespBodyContextKey, kv)
if err != nil {
openlogging.Error(err.Error())
}
}