package restlet
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
|
|
"github.com/archsh/go.xql"
|
|
//"cygnux.net/kepler/utils"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
var _debugInfo bool = false
|
|
|
|
func Build_QueryControl(queries Parameters, table *xql.Table, skips ...bool) (*QueryController, error) {
|
|
log.Debugln("_process_request_ctrl>>", table.TableName, queries)
|
|
qc := &QueryController{}
|
|
var include_fields = make(map[string]bool)
|
|
qc.Offset, _ = queries.GetInt64(QCTRL_OFFSET, QCTRL_OFFSET_DEFAULT)
|
|
delete(queries, QCTRL_OFFSET)
|
|
qc.Limit, _ = queries.GetInt64(QCTRL_LIMIT, QCTRL_LIMIT_DEFAULT)
|
|
delete(queries, QCTRL_LIMIT)
|
|
if ss, ok := queries.GetString(QCTRL_ORDERBY); ok {
|
|
kss := strings.Split(ss, ",")
|
|
for _, x := range kss {
|
|
qc.OrderBy = append(qc.OrderBy, x)
|
|
}
|
|
delete(queries, QCTRL_ORDERBY)
|
|
}
|
|
if ss, ok := queries.GetString(QCTRL_GROUPBY); ok {
|
|
kss := strings.Split(ss, ",")
|
|
for _, x := range kss {
|
|
qc.GroupBy = append(qc.GroupBy, x)
|
|
}
|
|
delete(queries, QCTRL_GROUPBY)
|
|
}
|
|
if ss, ok := queries.GetString(QCTRL_DEBUG); ok {
|
|
switch strings.ToLower(ss) {
|
|
case "true", "yes", "ok", "1", "t", "y":
|
|
qc.Debug = true
|
|
}
|
|
delete(queries, QCTRL_DEBUG)
|
|
}
|
|
|
|
if ss, ok := queries.GetString(QCTRL_INCLUDES); ok {
|
|
for _, x := range strings.Split(ss, ",") {
|
|
include_fields[x] = true
|
|
}
|
|
delete(queries, QCTRL_INCLUDES)
|
|
} else {
|
|
for _, c := range table.GetColumns() {
|
|
include_fields[c.FieldName] = true
|
|
}
|
|
}
|
|
if ss, ok := queries.GetString(QCTRL_EXCLUDES); ok {
|
|
for _, x := range strings.Split(ss, ",") {
|
|
include_fields[x] = false
|
|
}
|
|
delete(queries, QCTRL_EXCLUDES)
|
|
}
|
|
for k, v := range include_fields {
|
|
if ! v {
|
|
continue
|
|
}
|
|
if c, ok := table.GetColumn(k); ! ok {
|
|
return nil, errors.New("Invalid column:" + k)
|
|
} else {
|
|
qc.Includes = append(qc.Includes, c.FieldName)
|
|
}
|
|
|
|
}
|
|
if len(skips) > 0 && skips[0] == true {
|
|
return qc, nil
|
|
}
|
|
for k, v := range queries {
|
|
keys := strings.Split(k, "__")
|
|
if len(keys) > 3 {
|
|
return nil, fmt.Errorf("invalid expression: %s", k)
|
|
}
|
|
c, ok := table.GetColumn(keys[0])
|
|
if ! ok {
|
|
return nil, fmt.Errorf("unknow column: %s", keys[0])
|
|
}
|
|
f := xql.QueryFilter{Field: c.FieldName}
|
|
if len(keys) == 1 {
|
|
f.Operator = "="
|
|
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v)
|
|
} else if len(keys) == 2 {
|
|
switch strings.ToUpper(keys[1]) {
|
|
case "=":
|
|
f.Operator = "="
|
|
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v)
|
|
case "IN":
|
|
f.Operator = "IN"
|
|
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v)
|
|
//fmt.Println(">IN>>>>>>>>>>>",f.Value)
|
|
if reflect.TypeOf(f.Value).Kind() == reflect.Slice {
|
|
if reflect.ValueOf(f.Value).Len() < 1 {
|
|
continue
|
|
}
|
|
}
|
|
case "HAS":
|
|
f.Operator = "@>"
|
|
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v)
|
|
//fmt.Println(">HAS>>>>>>>>>>>",f.Value)
|
|
if reflect.TypeOf(f.Value).Kind() == reflect.Slice {
|
|
if reflect.ValueOf(f.Value).Len() < 1 {
|
|
continue
|
|
}
|
|
}
|
|
case "STARTSWITH":
|
|
f.Operator = "LIKE"
|
|
f.Value = v + "%"
|
|
case "ENDSWITH":
|
|
f.Operator = "LIKE"
|
|
f.Value = "%" + v
|
|
case "CONTAINS":
|
|
f.Operator = "LIKE"
|
|
f.Value = "%" + v + "%"
|
|
case "GT":
|
|
f.Operator = ">"
|
|
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v)
|
|
case "GTE":
|
|
f.Operator = ">="
|
|
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v)
|
|
case "LT":
|
|
f.Operator = "<"
|
|
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v)
|
|
case "LTE":
|
|
f.Operator = "<="
|
|
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v)
|
|
case "NOT":
|
|
f.Operator = "<>"
|
|
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v)
|
|
default:
|
|
return nil, fmt.Errorf("unknow operator: %s", keys[1])
|
|
}
|
|
}
|
|
if len(keys) == 3 {
|
|
switch strings.ToUpper(keys[2]) {
|
|
case "AND":
|
|
f.Condition = xql.CONDITION_AND
|
|
case "OR":
|
|
f.Condition = xql.CONDITION_OR
|
|
default:
|
|
return nil, fmt.Errorf("unknow condition: %s", keys[2])
|
|
}
|
|
}
|
|
//f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v)
|
|
qc.Filters = append(qc.Filters, f)
|
|
}
|
|
|
|
return qc, nil
|
|
}
|
|
|
|
func MatchMethod(m string, methods []string) bool {
|
|
if len(methods) == 0 || methods[0] == "*" {
|
|
return true
|
|
}
|
|
for _, s := range methods {
|
|
if s == m {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func HashKey(s ...string) string {
|
|
h := sha1.New()
|
|
ns := strings.Join(s, "")
|
|
h.Write([]byte(ns))
|
|
bs := h.Sum(nil)
|
|
return fmt.Sprintf("%x", bs)
|
|
}
|
|
|
|
func SetDebug(d bool) {
|
|
_debugInfo = d
|
|
}
|
|
|
|
func Failure_Response(code uint, message string) (*RestletResult, error) {
|
|
return &RestletResult{
|
|
Code: code,
|
|
Message: message,
|
|
}, nil //errors.New(message)
|
|
}
|
|
|
|
func MakeRestletHandler(h RestletHandler, predictor RequestPredictor, ctx_provider RequestContextProvider, methods []string, cache *CacheController) http.Handler {
|
|
var f = func(response http.ResponseWriter, request *http.Request) {
|
|
var t1 int64
|
|
if _debugInfo {
|
|
t1 = time.Now().Unix()
|
|
}
|
|
log.Infoln(">", request.Method, request.URL.String())
|
|
if msg, ret := predictor.Predicate(request); !ret {
|
|
response.Header().Set("Content-Type", "text/plain")
|
|
response.WriteHeader(403)
|
|
response.Write([]byte(msg))
|
|
return
|
|
}
|
|
if ! MatchMethod(request.Method, methods) {
|
|
response.Header().Set("Content-Type", "text/plain")
|
|
response.WriteHeader(405)
|
|
response.Write([]byte(fmt.Sprintf("Method '%s' not allowed!", request.Method)))
|
|
return
|
|
}
|
|
//var ctx *RequestContext
|
|
var url_params Parameters
|
|
var queries Parameters = Parameters(make(map[string]string))
|
|
var post_data []byte
|
|
var c_key string
|
|
ctx := ctx_provider.NewContext(request)
|
|
url_params = Parameters(mux.Vars(request))
|
|
log.Debugln("makeRestletHandler:>>>", request.URL.Query())
|
|
for k, v := range request.URL.Query() {
|
|
log.Debugln("makeRestletHandler:>>>", k, v)
|
|
queries[k] = v[0]
|
|
}
|
|
if nil != cache && MatchMethod(request.Method, cache.CacheMethods) {
|
|
c_key = cache.KeyPrefix + HashKey(request.Method, request.URL.String())
|
|
if d, t := ctx.Cache().Get(c_key); t {
|
|
log.Debugln("Restlet:> Hit cache for :> ", c_key, request.Method, request.URL.String())
|
|
content_type := strings.TrimRightFunc(string(d[:CONTENT_HEADER_SIZE]), func(i rune) bool {
|
|
return i == 0
|
|
}) // http.DetectContentType(d)
|
|
log.Debugln("Restlet:> detect content-type:", content_type)
|
|
response.Header().Set("Content-Type", content_type)
|
|
response.WriteHeader(200)
|
|
response.Write(d[CONTENT_HEADER_SIZE:])
|
|
if _debugInfo {
|
|
t2 := time.Now().Unix()
|
|
log.Debugln("Request Time:", t1, t2, t2-t1, request.URL.String())
|
|
}
|
|
return
|
|
}
|
|
}
|
|
switch request.Method {
|
|
case "POST", "PUT", "PATCH":
|
|
if buf, e := ioutil.ReadAll(request.Body); e == nil {
|
|
post_data = buf
|
|
} else {
|
|
log.Errorln("Restlet:> Read Body error:>", e)
|
|
response.Header().Set("Content-Type", "text/plain")
|
|
response.WriteHeader(FATAL_INTERNAL_SERVER_ERROR)
|
|
response.Write([]byte(fmt.Sprintf("Read Body error:> %s", e)))
|
|
return
|
|
}
|
|
}
|
|
if r, e := h.Handle(ctx, url_params, queries, post_data); nil != e {
|
|
if nil == r {
|
|
response.Header().Set("Content-Type", "text/plain")
|
|
response.WriteHeader(FATAL_INTERNAL_SERVER_ERROR)
|
|
response.Write([]byte(fmt.Sprintf("Failed:> %s", e)))
|
|
} else {
|
|
response.Header().Set("Content-Type", "text/plain")
|
|
response.WriteHeader(int(r.Code / 100))
|
|
response.Write([]byte(r.Message))
|
|
}
|
|
|
|
} else {
|
|
r.ETag = fmt.Sprintf("%016x", time.Now().Unix())
|
|
if _debugInfo {
|
|
t2 := time.Now().Unix()
|
|
r.Debug = &DebugResult{Start: t1, Finish: t2}
|
|
}
|
|
|
|
var output interface{}
|
|
var err error
|
|
var pbytes []byte
|
|
var ok bool
|
|
if r.DataOnly {
|
|
output = r.Data
|
|
} else {
|
|
output = r
|
|
}
|
|
if r.RawBytes {
|
|
err = nil
|
|
pbytes, ok = output.([]byte)
|
|
if !ok {
|
|
panic("Restlet:> Data not row bytes!")
|
|
}
|
|
} else {
|
|
r.ContentType = "application/json"
|
|
pbytes, err = json.Marshal(output)
|
|
}
|
|
if nil != err {
|
|
response.Header().Set("Content-Type", "text/plain")
|
|
response.WriteHeader(FATAL_INTERNAL_SERVER_ERROR / 100)
|
|
response.Write([]byte(fmt.Sprintf("Failed:> %s", e)))
|
|
} else {
|
|
statusCode := int(r.Code / 100)
|
|
if statusCode/100 < 1 || statusCode/100 > 5 {
|
|
statusCode = 200
|
|
}
|
|
for _, x := range r.Cookies {
|
|
//x.Domain = ctx.Config().GetString("service.cookie_domain", "*.cygnux-tv.top")
|
|
http.SetCookie(response, x)
|
|
}
|
|
response.Header().Set("Content-Type", r.ContentType)
|
|
response.WriteHeader(statusCode)
|
|
response.Write(pbytes)
|
|
if nil != cache && MatchMethod(request.Method, cache.CacheMethods) /* && r.Cacheable */ {
|
|
var ct_bytes [CONTENT_HEADER_SIZE]byte
|
|
copy(ct_bytes[:], []byte(r.ContentType)[:CONTENT_HEADER_SIZE])
|
|
ctx.Cache().Set(c_key, append(ct_bytes[:], pbytes...), cache.Expires)
|
|
log.Debugln("Restlet:> Cached Content for:> ", c_key, request.Method, request.URL.String())
|
|
}
|
|
}
|
|
if _debugInfo {
|
|
t2 := time.Now().Unix()
|
|
log.Debugln("Request Time:", t1, t2, t2-t1, request.URL.String())
|
|
}
|
|
}
|
|
}
|
|
return http.HandlerFunc(f)
|
|
}
|
|
|
|
func MakeRawletHandler(h RawletHandler, predictor RequestPredictor, ctx_provider RequestContextProvider, methods []string) http.Handler {
|
|
var f = func(response http.ResponseWriter, request *http.Request) {
|
|
log.Infoln(">", request.Method, request.URL.String())
|
|
if msg, ret := predictor.Predicate(request); !ret {
|
|
response.Header().Set("Content-Type", "text/plain")
|
|
response.WriteHeader(403)
|
|
response.Write([]byte(msg))
|
|
return
|
|
}
|
|
if ! MatchMethod(request.Method, methods) {
|
|
response.Header().Set("Content-Type", "text/plain")
|
|
response.WriteHeader(405)
|
|
response.Write([]byte(fmt.Sprintf("Method '%s' not allowed!", request.Method)))
|
|
return
|
|
}
|
|
var params Parameters
|
|
ctx := ctx_provider.NewContext(request)
|
|
params = Parameters(mux.Vars(request))
|
|
log.Debugln("MakeRawletHandler:>>>", request.URL.Query())
|
|
h.Handle(ctx, params, response, request)
|
|
}
|
|
return http.HandlerFunc(f)
|
|
}
|