@ -0,0 +1,3 @@ | |||
glide.lock | |||
.idea/ | |||
vendor/ |
@ -0,0 +1,26 @@ | |||
package restlet | |||
import ( | |||
"net/http" | |||
"fmt" | |||
"context" | |||
) | |||
type AuthFunc func(r *http.Request) (interface{}, error) | |||
type Authenticator struct { | |||
next http.Handler | |||
authenticate AuthFunc | |||
} | |||
func (a *Authenticator) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||
var ctx context.Context | |||
if a.authenticate != nil { | |||
if u, e := a.authenticate(r); nil != e { | |||
http.Error(w, fmt.Sprint(e), http.StatusUnauthorized) | |||
} else { | |||
ctx = context.WithValue(r.Context(),"sessUserInfo",u) | |||
} | |||
} | |||
a.next.ServeHTTP(w, r.WithContext(ctx)) | |||
} |
@ -0,0 +1,95 @@ | |||
package restlet | |||
const CONTENT_HEADER_SIZE = 32 | |||
const ( | |||
SUCCESS_OK = 20000 | |||
SUCCESS_CREATED = 20100 | |||
SUCCESS_DELETED = 20400 | |||
) | |||
const ( | |||
ERROR_BAD_REQUEST = 40000 + iota | |||
ERROR_INVALID_REQUEST | |||
ERROR_INVALID_PATH | |||
ERROR_INVALID_PARAMS | |||
ERROR_INVALID_DATA | |||
) | |||
const ( | |||
ERROR_UNAUTHORIZED = 40100 + iota | |||
ERROR_LOGGIN_REQUIRED | |||
ERROR_SECURET_REQUIRED | |||
ERROR_SESSION_EXPIRED | |||
ERROR_CLIENT_LOCKED | |||
ERROR_CLIENT_UNREGISTERED | |||
) | |||
const ( | |||
ERROR_PAYMENT_REQUIRED = 40200 + iota | |||
ERROR_PAYMENT_NOTFOUND | |||
ERROR_PAYMENT_EXPIRED | |||
) | |||
const ( | |||
ERROR_FORBIDDEN = 40300 + iota | |||
ERROR_PERMISSION_DENNIED | |||
ERROR_RESOURCE_LOCKED | |||
) | |||
const ( | |||
ERROR_NOT_FOUND = 40400 + iota | |||
ERROR_OBJECT_NOT_FOUND | |||
ERROR_ASSETS_NOT_FOUND | |||
) | |||
const ( | |||
ERROR_METHOD_NOT_ALLOWED = 40500 + iota | |||
) | |||
const ( | |||
ERROR_NOT_ACCEPTABLE = 40600 + iota | |||
) | |||
const ( | |||
ERROR_REQUEST_TIMEOUT = 40800 + iota | |||
) | |||
const ( | |||
ERROR_CONFLICT = 40900 + iota | |||
ERROR_RESOURCE_EXISTS | |||
ERROR_RESOURCE_NOT_COMPATIBLE | |||
) | |||
const ( | |||
ERROR_GONE = 41000 + iota | |||
) | |||
const ( | |||
FATAL_INTERNAL_SERVER_ERROR = 50000 + iota | |||
FATAL_DB_READ_FAILED | |||
FATAL_DB_WRITE_FAILED | |||
FATAL_KV_READ_FAILED | |||
FATAL_KV_WRITE_FAILED | |||
FATAL_DATA_ENCODE_FAILED | |||
FATAL_DATA_DECODE_FAILED | |||
) | |||
const ( | |||
FATAL_NOT_IMPLEMENTED = 50100 + iota | |||
) | |||
const ( | |||
FATAL_BAD_GATEWAY = 50200 + iota | |||
FATAL_BAD_CONFIG | |||
FATAL_CANNOT_CONNECT | |||
) | |||
const ( | |||
FATAL_SERVICE_UNAVAILABLE = 50300 | |||
) | |||
const ( | |||
FATAL_GATEWAY_TIMEOUT = 50400 + iota | |||
FATAL_CONNECT_TIMEOUT | |||
FATAL_RESPOSE_TIMEOUT | |||
) | |||
const ( | |||
QCTRL_OFFSET = "__offset" | |||
QCTRL_LIMIT = "__limit" | |||
QCTRL_INCLUDES = "__includes" | |||
QCTRL_EXCLUDES = "__excludes" | |||
QCTRL_ORDERBY = "__order_by" | |||
QCTRL_GROUPBY = "__group_by" | |||
QCTRL_DEBUG = "__debug" | |||
QCTRL_OFFSET_DEFAULT int64 = 0 | |||
QCTRL_LIMIT_DEFAULT int64 = 50 | |||
) |
@ -1,50 +1,79 @@ | |||
package restlet | |||
import ( | |||
"net/http" | |||
"database/sql" | |||
) | |||
type Context interface { | |||
// GetConfig: Return a param value from configurations. | |||
GetConfig(string, ...interface{}) (interface{}, bool) | |||
"fmt" | |||
// | |||
"cygnux.net/kepler/cache" | |||
"cygnux.net/kepler/config" | |||
"cygnux.net/kepler/database" | |||
"cygnux.net/kepler/kv" | |||
"cygnux.net/kepler/msq" | |||
log "github.com/Sirupsen/logrus" | |||
) | |||
type dumyContext struct { | |||
_dbi database.DBI | |||
_kvi kv.KV | |||
_cache cache.Cache | |||
_pub msq.Publisher | |||
_sub msq.Subscriber | |||
_cfg config.Config | |||
} | |||
func GetViaContext(k string, v interface{}) error { | |||
return nil | |||
func (d dumyContext) Schema(name ...string) string { | |||
return d._dbi.Schema() | |||
} | |||
func AttachSQL(next http.Handler, d *sql.DB) http.Handler { | |||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||
next.ServeHTTP(w, r) | |||
}) | |||
func (d dumyContext) SQL(name ...string) *sql.DB { | |||
return d._dbi.DB() | |||
} | |||
func AttachMongoDB(next http.Handler, d *sql.DB) http.Handler { | |||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||
next.ServeHTTP(w, r) | |||
}) | |||
func (d dumyContext) DBI(name ...string) database.DBI { | |||
return d._dbi | |||
} | |||
func (d dumyContext) Chan(string, ...interface{}) error { | |||
func AttachCache(next http.Handler, d *sql.DB) http.Handler { | |||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||
next.ServeHTTP(w, r) | |||
}) | |||
return fmt.Errorf("not implemented") | |||
} | |||
func AttachKV(next http.Handler, d *sql.DB) http.Handler { | |||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||
next.ServeHTTP(w, r) | |||
}) | |||
func (d dumyContext) Config() config.Config { | |||
return d._cfg | |||
} | |||
func (d dumyContext) Cache(name ...string) cache.Cache { | |||
return d._cache | |||
} | |||
func (d dumyContext) KV(name ...string) kv.KV { | |||
return d._kvi | |||
} | |||
func (d dumyContext) Publish(topic string, bss ...[]byte) error { | |||
for _, bs := range bss { | |||
if e := d._pub.Publish(topic, bs); nil != e { | |||
return e | |||
} | |||
} | |||
return nil | |||
} | |||
func AttachConfig(next http.Handler, d interface{}) http.Handler { | |||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||
next.ServeHTTP(w, r) | |||
}) | |||
} | |||
func NewDummyContext(cfg config.Config) (RestletContext, error) { | |||
d := dumyContext{_cfg: cfg} | |||
var e error | |||
if d._dbi, e = database.SetupDBI(cfg.Sub("database")); nil != e { | |||
log.Errorln("Open database failed:> ", e) | |||
return nil, e | |||
} | |||
if d._cache, e = cache.SetupCache(cfg.Sub("cache")); nil != e { | |||
log.Errorln("Make Cache failed:> ", e) | |||
return nil, e | |||
} | |||
if d._kvi, e = kv.SetupKV(cfg.Sub("kv")); nil != e { | |||
log.Errorln("Make KVStore failed:> ", e) | |||
return nil, e | |||
} | |||
if d._pub, e = msq.SetupPublisher(cfg.Sub("publish")); nil != e { | |||
log.Errorln("Make MessagePub failed:> ", e) | |||
return nil, e | |||
} | |||
if d._sub, e = msq.SetupSubscriber(cfg.Sub("subscribe")); nil != e { | |||
log.Errorln("Make MessageSub failed:> ", e) | |||
return nil, e | |||
} | |||
return d, nil | |||
} |
@ -0,0 +1,672 @@ | |||
package restlet | |||
import ( | |||
//"errors" | |||
"reflect" | |||
"strconv" | |||
"time" | |||
"github.com/archsh/go.xql" | |||
_ "github.com/archsh/go.xql/dialects/postgres" | |||
//"cygnux.net/kepler/utils" | |||
"fmt" | |||
log "github.com/Sirupsen/logrus" | |||
//"strings" | |||
"encoding/json" | |||
"strings" | |||
) | |||
type CURDKind interface { | |||
Entity() xql.TableIdentified | |||
} | |||
type CURDTable interface { | |||
Table() *xql.Table | |||
} | |||
type CURDPreRead interface { | |||
PreRead(ctx RequestContext, session *xql.Session, url_params Parameters, queries Parameters) (*QueryController, error) | |||
} | |||
type CURDPreCreate interface { | |||
PreCreate(ctx RequestContext, session *xql.Session, url_params Parameters, queries Parameters, entity interface{}) error | |||
} | |||
type CURDPreUpdate interface { | |||
PreUpdate(ctx RequestContext, session *xql.Session, url_params Parameters, queries Parameters, columns []xql.UpdateColumn) (*QueryController, []xql.UpdateColumn, error) | |||
} | |||
type CURDPreDelete interface { | |||
PreDelete(ctx RequestContext, session *xql.Session, url_params Parameters, queries Parameters) (*QueryController, error) | |||
} | |||
type CURDPostRead interface { | |||
PostRead(ctx RequestContext, session *xql.Session, qc *QueryController, entity interface{}) (interface{}, error) | |||
} | |||
type CURDPostCreate interface { | |||
PostCreate(ctx RequestContext, session *xql.Session, entity interface{}) (interface{}, error) | |||
} | |||
type CURDPostUpdate interface { | |||
PostUpdate(ctx RequestContext, session *xql.Session, qc *QueryController, columns []xql.UpdateColumn) ([]xql.UpdateColumn, error) | |||
} | |||
type CURDPostDelete interface { | |||
PostDelete(ctx RequestContext, session *xql.Session, qc *QueryController) error | |||
} | |||
type DefaultCURDHandler struct { | |||
dbiName string | |||
table *xql.Table | |||
entity xql.TableIdentified | |||
pks []string | |||
notAllowed []string | |||
preRead CURDPreRead | |||
preCreate CURDPreCreate | |||
preUpdate CURDPreUpdate | |||
preDelete CURDPreDelete | |||
postRead CURDPostRead | |||
postCreate CURDPostCreate | |||
postUpdate CURDPostUpdate | |||
postDelete CURDPostDelete | |||
} | |||
func (h DefaultCURDHandler) Handle(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) { | |||
for _, x := range h.notAllowed { | |||
if strings.ToUpper(x) == ctx.Request().Method { | |||
return Failure_Response(ERROR_METHOD_NOT_ALLOWED, "Method Not Allowed!") | |||
} | |||
} | |||
switch ctx.Request().Method { | |||
case "GET": | |||
return h.read(ctx, url_params, queries, post_data) | |||
case "PATCH": | |||
return h.update(ctx, url_params, queries, post_data) | |||
case "PUT": | |||
return h.update(ctx, url_params, queries, post_data) | |||
case "POST": | |||
return h.create(ctx, url_params, queries, post_data) | |||
case "DELETE": | |||
return h.delete(ctx, url_params, queries, post_data) | |||
case "OPTIONS": | |||
return h.options(ctx, url_params, queries, post_data) | |||
default: | |||
return Failure_Response(ERROR_METHOD_NOT_ALLOWED, "Method Not Allowed!") | |||
} | |||
} | |||
func (h DefaultCURDHandler) options(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) { | |||
result := &RestletResult{} | |||
result.Model = reflect.TypeOf(h.entity).Name() | |||
result.Data = h.entity | |||
return result, nil | |||
} | |||
func (h DefaultCURDHandler) read(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) { | |||
session := xql.MakeSession(ctx.SQL(h.dbiName), "postgres", true) | |||
defer session.Close() | |||
result := &RestletResult{} | |||
entityType := reflect.TypeOf(h.entity) | |||
if val := reflect.ValueOf(h.entity); val.Kind() == reflect.Ptr { | |||
entityType = val.Elem().Type() //reflect.TypeOf() | |||
} | |||
log.Debugln("DefaultCURDHandler.read> ", h.table.TableName, entityType) | |||
var queryControl *QueryController | |||
if h.preRead != nil { | |||
qc, e := h.preRead.PreRead(ctx, session, url_params, queries) | |||
if nil != e { | |||
return Failure_Response(ERROR_BAD_REQUEST, fmt.Sprintf("%s", e)) | |||
} | |||
queryControl = qc | |||
} else if qc, err := Build_QueryControl(queries, h.table); nil != err { | |||
log.Errorln("DefaultCURDHandler.read:> failure:", err) | |||
return Failure_Response(ERROR_BAD_REQUEST, fmt.Sprintf("%s", err)) | |||
} else { | |||
queryControl = qc | |||
} | |||
var pk_mapping = _build_params_map(h.table, url_params, h.pks...) | |||
if nil != pk_mapping { | |||
obj := reflect.New(entityType) | |||
row := session.Query(h.table, queryControl.Includes...).Filter(pk_mapping).One() | |||
err := row.Scan(obj.Elem().Addr().Interface()) | |||
if nil != err { | |||
//if e, ok := err.(*pq.Error); ok { | |||
// log.Errorln("DefaultCURDHandler.read:1> failure:", e) | |||
//} | |||
log.Errorln("_make_get_handle:2> failure:", reflect.TypeOf(err), err) | |||
//return Failure_Response(FATAL_DB_READ_FAILED, fmt.Sprintf("%s", err)) | |||
return Failure_Response(ERROR_NOT_FOUND, "Record Not Found!") | |||
} | |||
result.Data = obj.Elem().Addr().Interface() | |||
//return result, nil | |||
} else { | |||
for k, v := range url_params { | |||
f := xql.QueryFilter{Field: k, Operator: "="} | |||
f.Value = _build_column_query_value(h.table, k, f.Operator, v) | |||
queryControl.Filters = append(queryControl.Filters, f) | |||
} | |||
qs := session.Query(h.table, queryControl.Includes...).Filter(queryControl.Filters...) | |||
total, err := qs.Count() | |||
if nil != err { | |||
log.Errorln("DefaultCURDHandler.read:> failure:", err) | |||
return Failure_Response(FATAL_DB_READ_FAILED, fmt.Sprintf("%s", err)) | |||
} | |||
result.Control = &ControlResult{} | |||
result.Control.Total = total | |||
result.Control.Offset = queryControl.Offset | |||
result.Control.Limit = queryControl.Limit | |||
qs = qs.Offset(queryControl.Offset).Limit(queryControl.Limit) | |||
qs = qs.OrderBy(queryControl.OrderBy...) | |||
rows, err := qs.All() | |||
if nil != err { | |||
log.Errorln("DefaultCURDHandler.read:> failure:", err) | |||
return Failure_Response(FATAL_DB_READ_FAILED, fmt.Sprintf("%s", err)) | |||
} | |||
defer rows.Close() | |||
objects := reflect.MakeSlice(reflect.SliceOf(reflect.PtrTo(entityType)), 0, 0) | |||
for rows.Next() { | |||
obj := reflect.New(entityType) | |||
err = rows.Scan(obj.Elem().Addr().Interface()) | |||
if nil != err { | |||
log.Errorln("DefaultCURDHandler.read:> failure:", err) | |||
return Failure_Response(FATAL_DB_READ_FAILED, fmt.Sprintf("%s", err)) | |||
} | |||
objects = reflect.Append(objects, obj) | |||
result.Control.Count += 1 | |||
} | |||
result.Model = reflect.TypeOf(h.entity).Name() | |||
result.Data = objects.Interface() | |||
} | |||
if h.postRead != nil { | |||
if ret, e := h.postRead.PostRead(ctx, session, queryControl, result.Data); nil != e { | |||
log.Errorln("DefaultCURDHandler.read:> failure:", e) | |||
return Failure_Response(FATAL_DB_READ_FAILED, fmt.Sprintf("%s", e)) | |||
} else { | |||
result.Data = ret | |||
} | |||
} | |||
return result, nil | |||
} | |||
func (h DefaultCURDHandler) create(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) { | |||
entityType := reflect.TypeOf(h.entity) | |||
if val := reflect.ValueOf(h.entity); val.Kind() == reflect.Ptr { | |||
entityType = val.Elem().Type() //reflect.TypeOf() | |||
} | |||
log.Debugln("DefaultCURDHandler.create> ", h.table.TableName, entityType) | |||
if nil == post_data || len(post_data) < 1 { | |||
return Failure_Response(ERROR_INVALID_DATA, "Empty Data!") | |||
} | |||
session := xql.MakeSession(ctx.SQL(h.dbiName), "postgres", true) | |||
defer session.Close() | |||
entityObjs := reflect.MakeSlice(reflect.SliceOf(entityType), 1, 2) | |||
p := reflect.New(reflect.SliceOf(entityType)) | |||
reflect.Indirect(p).Set(entityObjs) | |||
if err := json.Unmarshal(post_data, entityObjs.Index(0).Addr().Interface()); nil == err { | |||
log.Debugln("DefaultCURDHandler.create>>>: Single Object") | |||
} else if err = json.Unmarshal(post_data, p.Interface()); nil == err { | |||
log.Debugln("DefaultCURDHandler.create>>>: Slice Objects") | |||
} else { | |||
log.Warnln("DefaultCURDHandler.create> Invalid Data:", err) | |||
return Failure_Response(ERROR_INVALID_DATA, "Invalid Data!") | |||
} | |||
var pk_mapping = _build_params_map(h.table, url_params, h.pks...) | |||
for k, v := range url_params { | |||
if nil == pk_mapping { | |||
pk_mapping = make(map[string]interface{}) | |||
pk_mapping[k] = _build_column_query_value(h.table, k, "=", v) | |||
continue | |||
} | |||
if _, ok := pk_mapping[k]; ! ok { | |||
pk_mapping[k] = _build_column_query_value(h.table, k, "=", v) | |||
} | |||
} | |||
result := &RestletResult{} | |||
err := session.Begin() | |||
if nil != err { | |||
log.Errorln("DefaultCURDHandler.create:> failure:", err) | |||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprintf("%s", err)) | |||
} | |||
//n := 0 | |||
for i := 0; i < entityObjs.Len(); i++ { | |||
obj := entityObjs.Index(i) | |||
if nil != pk_mapping { | |||
_assign_entity_from_map(obj.Addr().Interface(), pk_mapping, false) | |||
} | |||
if h.preCreate != nil { | |||
if e := h.preCreate.PreCreate(ctx, session, url_params, queries, obj.Addr().Interface()); nil != e { | |||
log.Errorln("DefaultCURDHandler.create> preCreate failed:", e) | |||
return Failure_Response(ERROR_INVALID_DATA, fmt.Sprintf("%s", e)) | |||
} | |||
} | |||
log.Debugln("DefaultCURDHandler.create:> Inserting :", obj.Addr().Interface()) | |||
_, err = session.Query(h.table).Insert(obj.Addr().Interface()) | |||
if nil != err { | |||
log.Errorln("DefaultCURDHandler.create:> failure:", err) | |||
session.Rollback() | |||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprintf("%s", err)) | |||
} | |||
} | |||
result.Data = entityObjs.Interface() | |||
if h.postCreate != nil { | |||
if ret, e := h.postCreate.PostCreate(ctx, session, result.Data); nil != e { | |||
log.Errorln("DefaultCURDHandler.create:> postCreate failed:", e) | |||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprintf("%s", e)) | |||
} else { | |||
result.Data = ret | |||
} | |||
} | |||
err = session.Commit() | |||
if nil != err { | |||
log.Errorln("DefaultCURDHandler.create:> failure:", err) | |||
session.Rollback() | |||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprintf("%s", err)) | |||
} | |||
result.Code = SUCCESS_CREATED | |||
return result, nil | |||
} | |||
func (h DefaultCURDHandler) update(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) { | |||
entityType := reflect.TypeOf(h.entity) | |||
if val := reflect.ValueOf(h.entity); val.Kind() == reflect.Ptr { | |||
entityType = val.Elem().Type() //reflect.TypeOf() | |||
} | |||
log.Debugln("DefaultCURDHandler.update> ", h.table.TableName, entityType) | |||
if nil == post_data || len(post_data) < 1 { | |||
log.Warnln("DefaultCURDHandler.update:> Empty DATA.") | |||
return Failure_Response(ERROR_INVALID_DATA, "Empty Data!") | |||
} | |||
entityObj := reflect.New(entityType) | |||
var entityMap = make(map[string]interface{}) | |||
err := json.Unmarshal(post_data, entityObj.Elem().Addr().Interface()) | |||
e1 := json.Unmarshal(post_data, &entityMap) | |||
if nil != err || nil != e1 { | |||
log.Warnln("DefaultCURDHandler.update:> Invalid DATA:", err, e1) | |||
return Failure_Response(ERROR_INVALID_DATA, "Invalid Data!") | |||
} | |||
for k, _ := range entityMap { | |||
if _, ok := h.table.GetColumn(k); ok { | |||
continue | |||
} else { | |||
log.Warnln("DefaultCURDHandler.update:> Invalid Field:", k) | |||
return Failure_Response(ERROR_INVALID_DATA, "Invalid Field:"+k) | |||
} | |||
} | |||
if len(entityMap) == 0 { | |||
log.Warnln("DefaultCURDHandler.update:> Empty DATA:", err, e1) | |||
return Failure_Response(ERROR_INVALID_DATA, "Empty Data!") | |||
} | |||
session := xql.MakeSession(ctx.SQL(h.dbiName), "postgres", true) | |||
defer session.Close() | |||
var updateCols []xql.UpdateColumn | |||
for _, c := range h.table.GetColumns() { | |||
if _, ok := entityMap[c.FieldName]; ok { | |||
} else if _, ok := entityMap[c.Jtag]; ok { | |||
} else if _, ok := entityMap[c.ElemName]; ok { | |||
} else { | |||
continue | |||
} | |||
if c.PrimaryKey { | |||
return Failure_Response(ERROR_FORBIDDEN, "Not Allowed to Change Primary Key(s).") | |||
} | |||
uc := xql.UpdateColumn{Field: c.FieldName, Operator: "="} | |||
val := entityObj.Elem().FieldByName(c.ElemName) | |||
switch val.Kind() { | |||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |||
uc.Value = val.Int() | |||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |||
uc.Value = val.Uint() | |||
case reflect.String: | |||
uc.Value = val.String() | |||
case reflect.Bool: | |||
uc.Value = val.Bool() | |||
case reflect.Float32, reflect.Float64: | |||
uc.Value = val.Float() | |||
default: | |||
uc.Value = val.Interface() | |||
} | |||
updateCols = append(updateCols, uc) | |||
} | |||
var queryControl *QueryController | |||
if h.preUpdate != nil { | |||
if qc, cols, e := h.preUpdate.PreUpdate(ctx, session, url_params, queries, updateCols); nil != e { | |||
return Failure_Response(ERROR_BAD_REQUEST, fmt.Sprintf("%s", e)) | |||
} else { | |||
queryControl = qc | |||
updateCols = cols | |||
} | |||
} else { | |||
if qc, err := Build_QueryControl(queries, h.table); nil != err { | |||
log.Errorln("DefaultCURDHandler.update:> failure:", err) | |||
return Failure_Response(ERROR_BAD_REQUEST, fmt.Sprintf("%s", err)) | |||
} else { | |||
queryControl = qc | |||
} | |||
} | |||
log.Debugln("DefaultCURDHandler.update> QueryControl:", queryControl) | |||
var pk_mapping = _build_params_map(h.table, url_params, h.pks...) | |||
if nil != pk_mapping { | |||
queryControl.Filters = append(queryControl.Filters, pk_mapping) | |||
} | |||
n, e := session.Query(h.table).Filter(queryControl.Filters...).Update(updateCols) | |||
if nil != e { | |||
log.Errorln("DefaultCURDHandler.update:> failure:", e) | |||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprintf("%s", e)) | |||
} | |||
if h.postUpdate != nil { | |||
updateCols, e = h.postUpdate.PostUpdate(ctx, session, queryControl, updateCols) | |||
if nil != e { | |||
log.Errorln("DefaultCURDHandler.update:> post update failure:", e) | |||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprintf("%s", e)) | |||
} | |||
} | |||
var updates = make(map[string]interface{}) | |||
for _, x := range updateCols { | |||
updates[x.Field] = x.Value | |||
} | |||
result := &RestletResult{ | |||
Code: SUCCESS_OK, | |||
Model: entityType.Name(), | |||
Data: map[string]interface{}{ | |||
"count": n, | |||
"updates": updates, | |||
}, | |||
} | |||
return result, nil | |||
} | |||
func (h DefaultCURDHandler) delete(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) { | |||
session := xql.MakeSession(ctx.SQL(h.dbiName), "postgres", true) | |||
defer session.Close() | |||
result := &RestletResult{} | |||
entityType := reflect.TypeOf(h.entity) | |||
if val := reflect.ValueOf(h.entity); val.Kind() == reflect.Ptr { | |||
entityType = val.Elem().Type() //reflect.TypeOf() | |||
} | |||
result.Model = reflect.TypeOf(h.entity).Name() | |||
log.Debugln("DefaultCURDHandler.delete> ", h.table.TableName, entityType) | |||
var queryControl *QueryController | |||
if h.preDelete != nil { | |||
if qc, e := h.preDelete.PreDelete(ctx, session, url_params, queries); nil != e { | |||
log.Errorln("DefaultCURDHandler.delete:> pre delete failure:", e) | |||
return Failure_Response(ERROR_BAD_REQUEST, fmt.Sprintf("%s", e)) | |||
} else { | |||
queryControl = qc | |||
} | |||
} else { | |||
if qc, err := Build_QueryControl(queries, h.table); nil != err { | |||
log.Errorln("DefaultCURDHandler.delete:> failure:", err) | |||
return Failure_Response(ERROR_BAD_REQUEST, fmt.Sprintf("%s", err)) | |||
} else { | |||
queryControl = qc | |||
} | |||
} | |||
var pk_mapping = _build_params_map(h.table, url_params, h.pks...) | |||
if nil != pk_mapping { | |||
n, err := session.Query(h.table).Filter(pk_mapping).Delete() | |||
if nil != err { | |||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprint(err)) | |||
} | |||
result.Code = SUCCESS_DELETED | |||
result.Data = map[string]interface{}{"Deleted": n} | |||
return result, nil | |||
} | |||
for k, v := range url_params { | |||
f := xql.QueryFilter{Field: k, Operator: "="} | |||
f.Value = _build_column_query_value(h.table, k, f.Operator, v) | |||
queryControl.Filters = append(queryControl.Filters, f) | |||
} | |||
if len(queryControl.Filters) < 1 { | |||
return Failure_Response(ERROR_FORBIDDEN, "Not allowed to delete without conditions!") | |||
} | |||
n, err := session.Query(h.table).Filter(queryControl.Filters...).Delete() | |||
if nil != err { | |||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprint(err)) | |||
} | |||
if h.postDelete != nil { | |||
if e := h.postDelete.PostDelete(ctx, session, queryControl); nil != e { | |||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprint(e)) | |||
} | |||
} | |||
result.Code = SUCCESS_OK | |||
result.Data = map[string]interface{}{"deleted": n} | |||
return result, nil | |||
} | |||
func NewCURDHandler(dbiname string, kind interface{}, notAllowedMethods ...string) RestletHandler { | |||
var h = DefaultCURDHandler{dbiName: dbiname, notAllowed: notAllowedMethods} | |||
if h.dbiName == "" { | |||
h.dbiName = "default" | |||
} | |||
if k, b := kind.(xql.TableIdentified); b { | |||
h.entity = k | |||
} else if k, b := kind.(CURDKind); b { | |||
h.entity = k.Entity() | |||
} else { | |||
panic("kind param should at least implement CURDKind or xql.TableIdentified interface!") | |||
} | |||
if k, b := kind.(CURDTable); b { | |||
h.table = k.Table() | |||
} else { | |||
h.table = xql.DeclareTable(h.entity) | |||
} | |||
for _, x := range h.table.GetPrimaryKeys() { | |||
h.pks = append(h.pks, x.FieldName) | |||
} | |||
if k, b := kind.(CURDPreRead); b { | |||
h.preRead = k | |||
} | |||
if k, b := kind.(CURDPreCreate); b { | |||
h.preCreate = k | |||
} | |||
if k, b := kind.(CURDPreUpdate); b { | |||
h.preUpdate = k | |||
} | |||
if k, b := kind.(CURDPreDelete); b { | |||
h.preDelete = k | |||
} | |||
if k, b := kind.(CURDPostRead); b { | |||
h.postRead = k | |||
} | |||
if k, b := kind.(CURDPostCreate); b { | |||
h.postCreate = k | |||
} | |||
if k, b := kind.(CURDPostUpdate); b { | |||
h.postUpdate = k | |||
} | |||
if k, b := kind.(CURDPostDelete); b { | |||
h.postDelete = k | |||
} | |||
return h | |||
} | |||
func _build_params_map(table *xql.Table, params Parameters, pks ...string) map[string]interface{} { | |||
var pk_mapping map[string]interface{} | |||
for i, pk := range pks { | |||
if v, ok := params.GetString(pk); ok { | |||
log.Debugln("_make_get_handle:> Pk", i, pk, v) | |||
if nil == pk_mapping { | |||
pk_mapping = make(map[string]interface{}) | |||
} | |||
pk_mapping[table.GetPrimaryKeys()[i].FieldName] = | |||
_build_column_query_value(table, table.GetPrimaryKeys()[i].FieldName, "=", v) | |||
delete(params, pk) | |||
} | |||
} | |||
if len(pks) > 0 { | |||
return pk_mapping | |||
} | |||
for k, v := range params { | |||
if nil == pk_mapping { | |||
pk_mapping = make(map[string]interface{}) | |||
} | |||
pk_mapping[k] = _build_column_query_value(table, k, "=", v) | |||
} | |||
return pk_mapping | |||
} | |||
func _assign_entity_from_map(entity interface{}, params map[string]interface{}, recursive bool) error { | |||
log.Debugln("_assign_entity_from_map:>>>", entity, params) | |||
if nil != entity { | |||
et := reflect.TypeOf(entity) | |||
ev := reflect.ValueOf(entity) | |||
for i := 0; i < et.Elem().NumField(); i++ { | |||
f := et.Elem().Field(i) | |||
x_tag := strings.Split(f.Tag.Get("xql"), ",")[0] | |||
if x_tag == "-" { | |||
continue | |||
} | |||
if f.Anonymous && !recursive { | |||
_assign_entity_from_map(ev.Elem().Field(i).Addr().Interface(), params, true) | |||
continue | |||
} | |||
json_tag := strings.Split(f.Tag.Get("json"), ",")[0] | |||
var val interface{} | |||
if v, ok := params[x_tag]; ok { | |||
log.Debugln("_assign_entity_from_map:>>>", 1, v) | |||
val = v | |||
} else if v, ok := params[json_tag]; ok { | |||
log.Debugln("_assign_entity_from_map:>>>", 2, v) | |||
val = v | |||
} else if v, ok := params[f.Name]; ok { | |||
log.Debugln("_assign_entity_from_map:>>>", 3, v) | |||
val = v | |||
} else if v, ok := params[xql.Camel2Underscore(f.Name)]; ok { | |||
log.Debugln("_assign_entity_from_map:>>>", 4, v) | |||
val = v | |||
} else { | |||
continue | |||
} | |||
fv := ev.Elem().Field(i) | |||
if fv.IsValid() && fv.CanSet() { | |||
fv.Set(reflect.ValueOf(val)) | |||
} | |||
} | |||
} else { | |||
panic("Empty pointer of entity!") | |||
} | |||
return nil | |||
} | |||
func translate_datetime(s string) time.Time { | |||
if t, e := time.Parse(time.RFC3339Nano, s); nil == e { | |||
return t | |||
} else if t, e := time.Parse(time.RFC3339, s); nil == e { | |||
return t | |||
} else if t, e := time.Parse("2006-01-02T15:04:05", s); nil == e { | |||
return t | |||
} else if t, e := time.Parse("2006-01-02T15:04", s); nil == e { | |||
return t | |||
} else if t, e := time.Parse("2006-01-02", s); nil == e { | |||
return t | |||
} else if t, e := time.Parse("2006", s); nil == e { | |||
return t | |||
} else { | |||
return time.Time{} | |||
} | |||
} | |||
func translate_single_value(k reflect.Kind, val string) interface{} { | |||
switch k { | |||
case reflect.Bool: | |||
switch strings.ToLower(val) { | |||
case "yes", "true", "y", "t", "ok": | |||
return true | |||
default: | |||
return false | |||
} | |||
case reflect.Int: | |||
n, _ := strconv.ParseInt(val, 10, 32) | |||
return int(n) | |||
case reflect.Int8: | |||
n, _ := strconv.ParseInt(val, 10, 8) | |||
return int8(n) | |||
case reflect.Int16: | |||
n, _ := strconv.ParseInt(val, 10, 16) | |||
return int16(n) | |||
case reflect.Int64: | |||
n, _ := strconv.ParseInt(val, 10, 64) | |||
return int64(n) | |||
case reflect.Uint: | |||
n, _ := strconv.ParseUint(val, 10, 32) | |||
return uint(n) | |||
case reflect.Uint8: | |||
n, _ := strconv.ParseUint(val, 10, 8) | |||
return uint8(n) | |||
case reflect.Uint16: | |||
n, _ := strconv.ParseUint(val, 10, 16) | |||
return uint16(n) | |||
case reflect.Uint64: | |||
n, _ := strconv.ParseUint(val, 10, 64) | |||
return uint64(n) | |||
case reflect.String: | |||
return val | |||
case reflect.Float32: | |||
f, _ := strconv.ParseFloat(val, 32) | |||
return float32(f) | |||
case reflect.Float64: | |||
f, _ := strconv.ParseFloat(val, 64) | |||
return float64(f) | |||
case reflect.TypeOf(time.Time{}).Kind(): | |||
t := translate_datetime(val) | |||
return t | |||
case reflect.TypeOf(&time.Time{}).Kind(): | |||
t := translate_datetime(val) | |||
return &t | |||
default: | |||
return val | |||
} | |||
return nil | |||
} | |||
func _build_column_query_value(table *xql.Table, field string, operator string, val string) interface{} { | |||
if col, b := table.GetColumn(field); !b { | |||
return val | |||
} else { | |||
if col.Type.Kind() == reflect.Slice { | |||
et := col.Type.Elem() | |||
vv := reflect.MakeSlice(col.Type, 0, 0) | |||
for _, x := range strings.Split(val, ",") { | |||
if x == "" { | |||
continue | |||
} | |||
v := translate_single_value(et.Kind(), x) | |||
//fmt.Println(">>>>", v,reflect.ValueOf(v)) | |||
vv = reflect.Append(vv, reflect.ValueOf(v)) | |||
//fmt.Println(">>>>",vv) | |||
} | |||
return vv.Interface() | |||
} else if operator == "IN" { | |||
vv := reflect.MakeSlice(reflect.SliceOf(col.Type), 0, 0) | |||
for _, x := range strings.Split(val, ",") { | |||
if x == "" { | |||
continue | |||
} | |||
v := translate_single_value(col.Type.Kind(), x) | |||
//fmt.Println(">>>>", v,reflect.ValueOf(v)) | |||
vv = reflect.Append(vv, reflect.ValueOf(v)) | |||
//fmt.Println(">>>>",vv) | |||
} | |||
return vv.Interface() | |||
} else { | |||
return translate_single_value(col.Type.Kind(), val) | |||
} | |||
} | |||
return val | |||
} |
@ -0,0 +1,42 @@ | |||
package restlet | |||
import ( | |||
"errors" | |||
"github.com/dgrijalva/jwt-go" | |||
"fmt" | |||
) | |||
const ( | |||
RESTLET_JWT_KEYNAME = "jwt_secret" | |||
RESTLET_JWT_DEFAULT_KEY = "thisisanotherrun" | |||
) | |||
func ParseToken(ctx RequestContext, claims jwt.Claims, tkstring string) error { | |||
if token, err := jwt.ParseWithClaims(tkstring, claims, func(tk *jwt.Token)(interface{}, error){ | |||
if _, ok := tk.Method.(*jwt.SigningMethodHMAC); !ok { | |||
return nil, fmt.Errorf("Unexpected signing method: %v", tk.Header["alg"]) | |||
} | |||
secret := ctx.Config().GetString(RESTLET_JWT_KEYNAME, RESTLET_JWT_DEFAULT_KEY) | |||
return []byte(secret), nil | |||
}); nil != err { | |||
return err | |||
}else if !token.Valid { | |||
return errors.New("Invalid token!") | |||
}else{ | |||
return nil | |||
} | |||
} | |||
func SignToken(ctx RequestContext, claims jwt.Claims) (string, error) { | |||
tk := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | |||
secret := ctx.Config().GetString(RESTLET_JWT_KEYNAME, RESTLET_JWT_DEFAULT_KEY) | |||
if s, e := tk.SignedString([]byte(secret));nil != e { | |||
fmt.Println("Error:1>", e) | |||
return "", e | |||
}else { | |||
return s, nil | |||
} | |||
} |
@ -0,0 +1,53 @@ | |||
package restlet | |||
import ( | |||
"fmt" | |||
log "github.com/Sirupsen/logrus" | |||
"github.com/dgrijalva/jwt-go" | |||
"github.com/dgrijalva/jwt-go/request" | |||
"net/http" | |||
) | |||
func GetSession(ctx RequestContext, secret, cookiename string, claims jwt.Claims) error { | |||
if e := ExtractClaimsViaHeader(ctx.Request(), secret, claims); nil == e { | |||
log.Debugln("> GetSession via Header:>", claims) | |||
return nil | |||
} else { | |||
log.Debugln("> GetSession via Header failed:>", e) | |||
} | |||
if e := ExtractClaimsViaCookie(ctx.Request(), cookiename, secret, claims); nil == e { | |||
log.Debugln("> GetSession via Cookie:>", claims) | |||
return nil | |||
} else { | |||
log.Debugln("> GetSession via Cookie failed:>", e) | |||
} | |||
return fmt.Errorf("can not read session token") | |||
} | |||
func ExtractClaimsViaHeader(r *http.Request, secret string, o jwt.Claims) error { | |||
if token, err := request.ParseFromRequestWithClaims(r, request.AuthorizationHeaderExtractor, o, | |||
func(token *jwt.Token) (interface{}, error) { | |||
return []byte(secret), nil | |||
}); nil != err { | |||
return err | |||
} else if ! token.Valid { | |||
return fmt.Errorf("token invalid") | |||
} | |||
return nil | |||
} | |||
func ExtractClaimsViaCookie(r *http.Request, ckname string, secret string, o jwt.Claims) error { | |||
for _, x := range r.Cookies() { | |||
if x.Name == ckname { | |||
if token, e := jwt.ParseWithClaims(x.Value, o, func(token *jwt.Token) (interface{}, error) { | |||
return []byte(secret), nil | |||
}); nil != e { | |||
return e | |||
} else if token.Valid { | |||
return nil | |||
} | |||
break | |||
} | |||
} | |||
return fmt.Errorf("not found") | |||
} |
@ -1,11 +1,347 @@ | |||
package restlet | |||
import ( | |||
"github.com/archsh/go.xql" | |||
"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" | |||
) | |||
func MakeRestletHandler(entity xql.TableIdentified, schema string) http.Handler { | |||
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) | |||
} | |||
return nil | |||
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) | |||
} |
@ -0,0 +1,112 @@ | |||
package restlet | |||
import ( | |||
"strings" | |||
"strconv" | |||
"github.com/archsh/go.uuid" | |||
) | |||
func (h Parameters) HasKey(k string) bool { | |||
if nil == h { | |||
return false | |||
} | |||
_, ok := h[k] | |||
return ok | |||
} | |||
func (h Parameters) GetInt(k string, defaults ... int) (int, bool) { | |||
d := 0 | |||
if len(defaults) > 0 { | |||
d = defaults[0] | |||
} | |||
if nil == h { | |||
return d, false | |||
} | |||
if ns, ok := h[k]; ok { | |||
if n, e := strconv.Atoi(ns); e == nil { | |||
return n, true | |||
} | |||
} | |||
return d, false | |||
} | |||
func (h Parameters) GetInt64(k string, defaults ... int64) (int64, bool) { | |||
var d int64 = 0 | |||
if len(defaults) > 0 { | |||
d = defaults[0] | |||
} | |||
if nil == h { | |||
return d, false | |||
} | |||
if ns, ok := h[k]; ok { | |||
if n, e := strconv.ParseInt(ns, 10, 64); e == nil { | |||
return n, true | |||
} | |||
} | |||
return d, false | |||
} | |||
func (h Parameters) GetUInt(k string, defaults ... uint) (uint, bool) { | |||
var d uint = 0 | |||
if len(defaults) > 0 { | |||
d = defaults[0] | |||
} | |||
if nil == h { | |||
return d, false | |||
} | |||
if ns, ok := h[k]; ok { | |||
if n, e := strconv.ParseUint(ns, 10, 32); e == nil { | |||
return uint(n), true | |||
} | |||
} | |||
return d, false | |||
} | |||
func (h Parameters) GetString(k string, defaults ... string) (string, bool) { | |||
d := "" | |||
if len(defaults) > 0 { | |||
d = defaults[0] | |||
} | |||
if nil == h { | |||
return d, false | |||
} | |||
if ns, ok := h[k]; ok { | |||
return ns, true | |||
} | |||
return d, false | |||
} | |||
func (h Parameters) GetBool(k string, defaults ... bool) (bool, bool) { | |||
d := false | |||
if len(defaults) > 0 { | |||
d = defaults[0] | |||
} | |||
if nil == h { | |||
return d, false | |||
} | |||
if ns, ok := h[k]; ok { | |||
switch strings.ToLower(ns) { | |||
case "t", "true", "yes", "ok": | |||
return true, true | |||
case "f", "false", "no": | |||
return false, true | |||
} | |||
} | |||
return d, false | |||
} | |||
func (h Parameters) GetUUID(k string, defaults ... uuid.UUID) (uuid.UUID, bool) { | |||
d := uuid.UUID{} | |||
if len(defaults) > 0 { | |||
d = defaults[0] | |||
} | |||
if nil == h { | |||
return d, false | |||
} | |||
if ns, ok := h[k]; ok { | |||
if uid, e := uuid.FromString(ns); nil == e { | |||
return uid, true | |||
} | |||
} | |||
return d, false | |||
} |
@ -1,15 +1,162 @@ | |||
package restlet | |||
type RestletControl struct { | |||
Offset int64 `json:"offset" xml:"offset,attr"` | |||
Limit int64 `json:"limit" xml:"limit,attr"` | |||
Count int64 `json:"count" xml:"count,attr"` | |||
Total int64 `json:"total" xml:"total,attr"` | |||
} | |||
type RestletResponse struct { | |||
Code int `json:"Code" xml:"Code"` | |||
Message string `json:"message" xml:"Message"` | |||
Control *RestletControl `json:"ctrl,omitempty" xml:"Control,omitempty"` | |||
Data interface{} `json:"data" xml:"Data"` | |||
} | |||
import ( | |||
"database/sql" | |||
"net/http" | |||
"cygnux.net/kepler/cache" | |||
"cygnux.net/kepler/config" | |||
"cygnux.net/kepler/database" | |||
"cygnux.net/kepler/kv" | |||
) | |||
type Parameters map[string]string | |||
type ControlResult struct { | |||
Total int64 `json:"total"` | |||
Count int64 `json:"count"` | |||
Offset int64 `json:"offset"` | |||
Limit int64 `json:"limit"` | |||
} | |||
type DebugResult struct { | |||
Start int64 | |||
Finish int64 | |||
} | |||
type RestletResult struct { | |||
Code uint `json:"code"` | |||
Message string `json:"message,omitempty"` | |||
ContentType string `json:"-"` | |||
DataOnly bool `json:"-"` | |||
RawBytes bool `json:"-"` | |||
Cacheable bool | |||
Headers http.Header `json:"-"` | |||
Cookies []*http.Cookie `json:"-"` | |||
ETag string `json:"__eTag,omitempty"` | |||
Debug *DebugResult `json:"__debug,omitempty"` | |||
Model string `json:"__model,omitempty"` | |||
IsList bool `json:"__is_list,omitempty"` | |||
Control *ControlResult `json:"__control,omitempty"` | |||
Data interface{} `json:"data"` | |||
} | |||
type RawletResult struct { | |||
Code uint | |||
Message string | |||
ContentType string | |||
Data []byte | |||
} | |||
type QueryController struct { | |||
Offset int64 | |||
Limit int64 | |||
Debug bool | |||
Includes []interface{} | |||
Excludes []interface{} | |||
OrderBy []interface{} | |||
GroupBy []interface{} | |||
Filters []interface{} | |||
Params map[string]interface{} | |||
} | |||
type RestletError struct { | |||
msg string | |||
code uint | |||
} | |||
func (e RestletError) Error() string { | |||
return e.msg | |||
} | |||
func (e RestletError) Code() uint { | |||
return e.code | |||
} | |||
func MakeError(code uint, msg string) *RestletError { | |||
return &RestletError{ | |||
code: code, | |||
msg: msg, | |||
} | |||
} | |||
//type MessageHandleFunc func ([]byte) error | |||
// | |||
//type MessageQueue interface { | |||
// Chan(channel string, body []byte) error | |||
// Subscribe(channel string, queue string, handle MessageHandleFunc) error | |||
//} | |||
type RestletContext interface { | |||
Schema(name ...string) string | |||
SQL(name ...string) *sql.DB | |||
DBI(name ...string) database.DBI | |||
Chan(string, ...interface{}) error | |||
Config() config.Config | |||
Cache(name ...string) cache.Cache | |||
KV(name ...string) kv.KV | |||
Publish(string, ...[]byte) error | |||
} | |||
type RequestContext interface { | |||
RestletContext | |||
Request() *http.Request | |||
} | |||
type RequestContextProvider interface { | |||
NewContext(*http.Request) RequestContext | |||
} | |||
type RequestPredictor interface { | |||
Predicate(*http.Request) (string, bool) | |||
} | |||
type CacheController struct { | |||
KeyPrefix string | |||
CacheMethods []string | |||
CacheStatus []int | |||
Expires int64 | |||
} | |||
type RestletHandler interface { | |||
Handle(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) | |||
} | |||
type RawletHandler interface { | |||
Handle(ctx RequestContext, params Parameters, w http.ResponseWriter, r *http.Request) | |||
} | |||
type RawletHandleFunc func(ctx RequestContext, params Parameters, w http.ResponseWriter, r *http.Request) | |||
func (f RawletHandleFunc) Handle(ctx RequestContext, params Parameters, w http.ResponseWriter, r *http.Request) { | |||
f(ctx, params, w, r) | |||
} | |||
type TaskContext interface { | |||
RestletContext | |||
} | |||
type TaskContextProvider interface { | |||
NewContext() TaskContext | |||
} | |||
type TaskObject struct { | |||
Queue string | |||
Params []interface{} | |||
} | |||
type TaskletHandler interface { | |||
Handle(TaskContext, ...interface{}) error | |||
} | |||
type RestletHandleFunc func(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) | |||
type TaskletHandlerFunc func(TaskContext, ...interface{}) error | |||
func (f RestletHandleFunc) Handle(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) { | |||
return f(ctx, url_params, queries, post_data) | |||
} | |||
func (f TaskletHandlerFunc) Handle(ctx TaskContext, params ...interface{}) error { | |||
return f(ctx, params...) | |||
} |