From 650fde66aad7d22556d92481f9483e36889df6bb Mon Sep 17 00:00:00 2001 From: Ming Deng Date: Thu, 26 Nov 2020 17:48:29 +0800 Subject: [PATCH 1/2] Revert "Merge pull request #4254 from astaxie/develop-2.0" This reverts commit e284b0ddae072311617a6fdd8393eb04aba873d2, reversing changes made to 8ef8fd26068a7b70865acbabee89f6691ae553a9. --- .github/workflows/stale.yml | 19 - .gitignore | 6 - .travis.yml | 62 +- CONTRIBUTING.md | 45 +- README.md | 218 +---- adapter/admin.go | 45 -- adapter/app.go | 262 ------ adapter/beego.go | 77 -- adapter/build_info.go | 27 - adapter/cache/cache_adapter.go | 117 --- adapter/cache/conv.go | 44 - adapter/cache/file.go | 30 - adapter/cache/memcache/memcache.go | 44 - adapter/cache/memory.go | 28 - adapter/cache/redis/redis.go | 49 -- adapter/cache/ssdb/ssdb.go | 15 - adapter/config.go | 177 ----- adapter/config/adapter.go | 191 ----- adapter/config/config.go | 151 ---- adapter/config/env/env.go | 50 -- adapter/config/fake.go | 25 - adapter/config/xml/xml.go | 34 - adapter/config/yaml/yaml.go | 34 - adapter/context/acceptencoder.go | 45 -- adapter/context/context.go | 146 ---- adapter/context/input.go | 282 ------- adapter/context/output.go | 154 ---- adapter/context/renderer.go | 8 - adapter/controller.go | 399 ---------- adapter/doc.go | 16 - adapter/error.go | 202 ----- adapter/flash.go | 63 -- adapter/fs.go | 35 - adapter/grace/grace.go | 94 --- adapter/grace/server.go | 48 -- adapter/httplib/httplib.go | 300 ------- adapter/logs/accesslog.go | 27 - adapter/logs/alils/alils.go | 5 - adapter/logs/es/es.go | 5 - adapter/logs/log.go | 347 -------- adapter/logs/log_adapter.go | 69 -- adapter/logs/logger.go | 38 - adapter/logs/logger_test.go | 24 - adapter/migration/ddl.go | 198 ----- adapter/migration/migration.go | 111 --- adapter/namespace.go | 378 --------- adapter/orm/cmd.go | 28 - adapter/orm/db.go | 24 - adapter/orm/db_alias.go | 121 --- adapter/orm/models.go | 25 - adapter/orm/models_boot.go | 40 - adapter/orm/models_fields.go | 625 --------------- adapter/orm/orm.go | 314 -------- adapter/orm/orm_conds.go | 83 -- adapter/orm/orm_log.go | 32 - adapter/orm/orm_queryset.go | 32 - adapter/orm/qb.go | 27 - adapter/orm/qb_mysql.go | 150 ---- adapter/orm/query_setter_adapter.go | 34 - adapter/orm/types.go | 150 ---- adapter/orm/utils.go | 286 ------- adapter/plugins/apiauth/apiauth.go | 94 --- adapter/plugins/auth/basic.go | 81 -- adapter/plugins/authz/authz.go | 80 -- adapter/plugins/cors/cors.go | 71 -- adapter/policy.go | 57 -- adapter/router.go | 282 ------- adapter/session/couchbase/sess_couchbase.go | 118 --- adapter/session/ledis/ledis_session.go | 86 -- adapter/session/memcache/sess_memcache.go | 118 --- adapter/session/mysql/sess_mysql.go | 135 ---- adapter/session/postgres/sess_postgresql.go | 139 ---- adapter/session/provider_adapter.go | 104 --- adapter/session/redis/sess_redis.go | 121 --- .../session/redis_cluster/redis_cluster.go | 120 --- .../redis_sentinel/sess_redis_sentinel.go | 121 --- adapter/session/sess_cookie.go | 114 --- adapter/session/sess_file.go | 106 --- adapter/session/sess_file_test.go | 336 -------- adapter/session/sess_mem.go | 106 --- adapter/session/sess_test.go | 51 -- adapter/session/sess_utils.go | 29 - adapter/session/session.go | 166 ---- adapter/session/ssdb/sess_ssdb.go | 84 -- adapter/session/store_adapter.go | 84 -- adapter/swagger/swagger.go | 68 -- adapter/template.go | 108 --- adapter/templatefunc.go | 151 ---- adapter/templatefunc_test.go | 304 ------- adapter/testing/client.go | 50 -- adapter/toolbox/healthcheck.go | 52 -- adapter/toolbox/profile.go | 50 -- adapter/toolbox/statistics.go | 50 -- adapter/toolbox/task.go | 291 ------- adapter/tree.go | 49 -- adapter/tree_test.go | 249 ------ adapter/utils/captcha/captcha.go | 124 --- adapter/utils/captcha/image.go | 35 - adapter/utils/captcha/image_test.go | 58 -- adapter/utils/debug.go | 34 - adapter/utils/file.go | 47 -- adapter/utils/mail.go | 63 -- adapter/utils/pagination/controller.go | 26 - adapter/utils/pagination/paginator.go | 112 --- adapter/utils/rand.go | 24 - adapter/utils/safemap.go | 58 -- adapter/utils/slice.go | 101 --- adapter/utils/utils.go | 10 - adapter/validation/util.go | 62 -- adapter/validation/validation.go | 274 ------- adapter/validation/validators.go | 512 ------------ admin.go | 420 ++++++++++ admin_test.go | 77 ++ server/web/adminui.go => adminui.go | 4 +- app.go | 496 ++++++++++++ server/web/beego.go => beego.go | 70 +- build_info.go | 13 +- {client/cache => cache}/README.md | 0 {adapter/cache => cache}/cache.go | 0 {adapter/cache => cache}/cache_test.go | 0 {client/cache => cache}/conv.go | 10 +- {adapter/cache => cache}/conv_test.go | 0 {client/cache => cache}/file.go | 99 +-- {client/cache => cache}/memcache/memcache.go | 65 +- .../cache => cache}/memcache/memcache_test.go | 18 +- {client/cache => cache}/memory.go | 88 +- {client/cache => cache}/redis/redis.go | 69 +- {adapter/cache => cache}/redis/redis_test.go | 27 +- {client/cache => cache}/ssdb/ssdb.go | 71 +- {adapter/cache => cache}/ssdb/ssdb_test.go | 11 +- client/cache/cache.go | 104 --- client/cache/cache_test.go | 193 ----- client/cache/conv_test.go | 143 ---- client/cache/memcache/memcache_test.go | 121 --- client/cache/redis/redis_test.go | 163 ---- client/cache/ssdb/ssdb_test.go | 118 --- client/httplib/filter.go | 24 - client/httplib/filter/opentracing/filter.go | 71 -- .../httplib/filter/opentracing/filter_test.go | 42 - client/httplib/filter/prometheus/filter.go | 77 -- .../httplib/filter/prometheus/filter_test.go | 41 - client/httplib/httplib_test.go | 302 ------- client/orm/cmd_utils.go | 171 ---- client/orm/db_alias_test.go | 86 -- client/orm/do_nothing_orm.go | 180 ----- client/orm/do_nothing_orm_test.go | 134 ---- client/orm/filter.go | 40 - .../orm/filter/bean/default_value_filter.go | 137 ---- .../filter/bean/default_value_filter_test.go | 72 -- client/orm/filter/opentracing/filter.go | 71 -- client/orm/filter/opentracing/filter_test.go | 44 - client/orm/filter/prometheus/filter.go | 85 -- client/orm/filter/prometheus/filter_test.go | 62 -- client/orm/filter_orm_decorator.go | 514 ------------ client/orm/filter_orm_decorator_test.go | 434 ---------- client/orm/filter_test.go | 32 - client/orm/hints/db_hints.go | 103 --- client/orm/hints/db_hints_test.go | 127 --- client/orm/invocation.go | 58 -- client/orm/migration/doc.go | 32 - client/orm/model_utils_test.go | 62 -- client/orm/models.go | 569 ------------- client/orm/models_boot.go | 40 - client/orm/models_utils_test.go | 35 - client/orm/qb_postgres.go | 221 ------ client/orm/qb_tidb.go | 21 - client/orm/utils_test.go | 70 -- server/web/config.go => config.go | 149 ++-- {core/config => config}/config.go | 146 +--- {adapter/config => config}/config_test.go | 0 {core/config => config}/env/env.go | 4 +- {adapter/config => config}/env/env_test.go | 0 {core/config => config}/fake.go | 56 +- {core/config => config}/ini.go | 90 +-- {adapter/config => config}/ini_test.go | 0 {core/config/json => config}/json.go | 101 +-- {adapter/config => config}/json_test.go | 0 {core/config => config}/xml/xml.go | 113 +-- {adapter/config => config}/xml/xml_test.go | 2 +- {core/config => config}/yaml/yaml.go | 128 +-- {adapter/config => config}/yaml/yaml_test.go | 2 +- server/web/config_test.go => config_test.go | 10 +- .../web/context => context}/acceptencoder.go | 30 +- .../context => context}/acceptencoder_test.go | 0 {server/web/context => context}/context.go | 51 +- .../web/context => context}/context_test.go | 0 {server/web/context => context}/input.go | 60 +- {server/web/context => context}/input_test.go | 10 - {server/web/context => context}/output.go | 52 +- {server/web/context => context}/param/conv.go | 4 +- .../context => context}/param/methodparams.go | 4 +- .../web/context => context}/param/options.go | 0 .../web/context => context}/param/parsers.go | 0 .../context => context}/param/parsers_test.go | 8 +- {server/web/context => context}/renderer.go | 2 +- {adapter/context => context}/response.go | 7 +- server/web/controller.go => controller.go | 19 +- .../controller_test.go => controller_test.go | 16 +- core/bean/context.go | 20 - core/bean/doc.go | 17 - core/bean/factory.go | 25 - core/bean/metadata.go | 28 - core/bean/tag_auto_wire_bean_factory.go | 231 ------ core/bean/tag_auto_wire_bean_factory_test.go | 75 -- core/bean/time_type_adapter.go | 35 - core/bean/time_type_adapter_test.go | 29 - core/bean/type_adapter.go | 26 - core/config/base_config_test.go | 72 -- core/config/config_test.go | 55 -- core/config/env/env_test.go | 75 -- core/config/error.go | 25 - core/config/etcd/config.go | 195 ----- core/config/etcd/config_test.go | 117 --- core/config/global.go | 103 --- core/config/global_test.go | 104 --- core/config/ini_test.go | 191 ----- core/config/json/json_test.go | 251 ------ core/config/toml/toml.go | 357 --------- core/config/toml/toml_test.go | 379 --------- core/config/xml/xml_test.go | 157 ---- core/config/yaml/yaml_test.go | 151 ---- core/governor/command.go | 87 -- core/governor/profile_test.go | 28 - core/logs/access_log_test.go | 38 - core/logs/conn_test.go | 97 --- core/logs/es/index.go | 39 - core/logs/es/index_test.go | 34 - core/logs/formatter.go | 89 --- core/logs/formatter_test.go | 95 --- core/logs/jianliao_test.go | 36 - core/logs/log_msg.go | 55 -- core/logs/log_msg_test.go | 44 - core/logs/log_test.go | 27 - core/logs/slack.go | 82 -- core/utils/caller_test.go | 28 - core/utils/debug_test.go | 46 -- core/utils/kv.go | 87 -- core/utils/kv_test.go | 38 - core/utils/mail_test.go | 41 - core/utils/pagination/doc.go | 58 -- core/utils/rand_test.go | 33 - core/utils/safemap_test.go | 89 --- core/utils/slice_test.go | 29 - core/utils/time.go | 48 -- core/validation/validation_test.go | 634 --------------- doc.go | 28 +- server/web/error.go => error.go | 32 +- server/web/error_test.go => error_test.go | 2 +- adapter/filter.go => filter.go | 24 +- server/web/filter_test.go => filter_test.go | 4 +- server/web/flash.go => flash.go | 2 +- server/web/flash_test.go => flash_test.go | 2 +- server/web/fs.go => fs.go | 2 +- go.mod | 27 +- go.sum | 144 +--- {server/web/grace => grace}/grace.go | 0 {server/web/grace => grace}/server.go | 4 +- server/web/hooks.go => hooks.go | 30 +- {client/httplib => httplib}/README.md | 0 {client/httplib => httplib}/httplib.go | 107 +-- {adapter/httplib => httplib}/httplib_test.go | 33 +- adapter/log.go => log.go | 22 +- {core/logs => logs}/README.md | 0 core/logs/access_log.go => logs/accesslog.go | 18 +- {core/logs => logs}/alils/alils.go | 64 +- {core/logs => logs}/alils/config.go | 4 +- {core/logs => logs}/alils/log.pb.go | 53 +- {core/logs => logs}/alils/log_config.go | 6 +- {core/logs => logs}/alils/log_project.go | 2 +- {core/logs => logs}/alils/log_store.go | 6 +- {core/logs => logs}/alils/machine_group.go | 10 +- {core/logs => logs}/alils/request.go | 0 {core/logs => logs}/alils/signature.go | 0 {core/logs => logs}/conn.go | 51 +- adapter/utils/caller.go => logs/conn_test.go | 11 +- {core/logs => logs}/console.go | 66 +- {core/logs => logs}/console_test.go | 18 - {core/logs => logs}/es/es.go | 72 +- {core/logs => logs}/file.go | 100 +-- {core/logs => logs}/file_test.go | 57 +- {core/logs => logs}/jianliao.go | 45 +- {core/logs => logs}/log.go | 242 ++---- {core/logs => logs}/logger.go | 7 +- {core/logs => logs}/logger_test.go | 0 {core/logs => logs}/multifile.go | 34 +- {core/logs => logs}/multifile_test.go | 0 logs/slack.go | 60 ++ {core/logs => logs}/smtp.go | 43 +- {core/logs => logs}/smtp_test.go | 0 {adapter/metric => metric}/prometheus.go | 17 +- {adapter/metric => metric}/prometheus_test.go | 2 +- {client/orm/migration => migration}/ddl.go | 54 +- {adapter/migration => migration}/doc.go | 0 .../orm/migration => migration}/migration.go | 4 +- server/web/mime.go => mime.go | 2 +- server/web/namespace.go => namespace.go | 6 +- .../namespace_test.go => namespace_test.go | 4 +- {client/orm => orm}/README.md | 0 {client/orm => orm}/cmd.go | 44 +- orm/cmd_utils.go | 320 ++++++++ {client/orm => orm}/db.go | 99 +-- {client/orm => orm}/db_alias.go | 303 +++---- {client/orm => orm}/db_mysql.go | 55 +- {client/orm => orm}/db_oracle.go | 69 +- {client/orm => orm}/db_postgres.go | 47 +- {client/orm => orm}/db_sqlite.go | 58 +- {client/orm => orm}/db_tables.go | 9 - {client/orm => orm}/db_tidb.go | 0 {client/orm => orm}/db_utils.go | 0 orm/models.go | 99 +++ orm/models_boot.go | 347 ++++++++ {client/orm => orm}/models_fields.go | 0 {client/orm => orm}/models_info_f.go | 15 +- {client/orm => orm}/models_info_m.go | 2 +- {client/orm => orm}/models_test.go | 184 ++--- {client/orm => orm}/models_utils.go | 13 - {client/orm => orm}/orm.go | 364 ++++----- {client/orm => orm}/orm_conds.go | 0 {client/orm => orm}/orm_log.go | 27 +- {client/orm => orm}/orm_object.go | 4 +- {client/orm => orm}/orm_querym2m.go | 2 +- {client/orm => orm}/orm_queryset.go | 33 +- {client/orm => orm}/orm_raw.go | 72 +- {client/orm => orm}/orm_test.go | 316 ++------ {client/orm => orm}/qb.go | 2 +- {client/orm => orm}/qb_mysql.go | 58 +- {adapter/orm => orm}/qb_tidb.go | 89 ++- {client/orm => orm}/types.go | 252 ++---- {client/orm => orm}/utils.go | 0 {adapter/orm => orm}/utils_test.go | 0 server/web/parser.go => parser.go | 53 +- .../web/filter => plugins}/apiauth/apiauth.go | 37 +- .../apiauth/apiauth_test.go | 0 {server/web/filter => plugins}/auth/basic.go | 8 +- {server/web/filter => plugins}/authz/authz.go | 10 +- .../authz/authz_model.conf | 0 .../authz/authz_policy.csv | 0 .../plugins => plugins}/authz/authz_test.go | 9 +- {server/web/filter => plugins}/cors/cors.go | 6 +- .../web/filter => plugins}/cors/cors_test.go | 38 +- server/web/policy.go => policy.go | 4 +- server/web/router.go => router.go | 332 ++++---- server/web/router_test.go => router_test.go | 85 +- scripts/gobuild.sh | 112 +++ scripts/report_build_info.sh | 52 ++ server/web/LICENSE | 13 - server/web/admin.go | 126 --- server/web/admin_controller.go | 297 ------- server/web/admin_test.go | 249 ------ server/web/captcha/LICENSE | 19 - server/web/captcha/README.md | 45 -- server/web/context/response.go | 26 - server/web/doc.go | 17 - server/web/filter.go | 134 ---- server/web/filter/apiauth/apiauth_test.go | 20 - server/web/filter/authz/authz_model.conf | 14 - server/web/filter/authz/authz_policy.csv | 7 - server/web/filter/authz/authz_test.go | 109 --- server/web/filter/opentracing/filter.go | 86 -- server/web/filter/opentracing/filter_test.go | 47 -- server/web/filter/prometheus/filter.go | 87 -- server/web/filter/prometheus/filter_test.go | 40 - server/web/filter_chain_test.go | 48 -- server/web/server.go | 751 ------------------ server/web/server_test.go | 30 - .../session/couchbase/sess_couchbase_test.go | 43 - .../web/session/ledis/ledis_session_test.go | 41 - server/web/session/redis/sess_redis_test.go | 112 --- .../redis_cluster/redis_cluster_test.go | 35 - .../sess_redis_sentinel_test.go | 106 --- server/web/session/sess_cookie_test.go | 105 --- server/web/session/sess_file_test.go | 427 ---------- server/web/session/sess_mem_test.go | 58 -- server/web/session/ssdb/sess_ssdb_test.go | 41 - server/web/statistics_test.go | 40 - {server/web/session => session}/README.md | 2 +- .../couchbase/sess_couchbase.go | 69 +- .../ledis/ledis_session.go | 78 +- .../memcache/sess_memcache.go | 35 +- .../session => session}/mysql/sess_mysql.go | 37 +- .../postgres/sess_postgresql.go | 37 +- .../session => session}/redis/sess_redis.go | 187 ++--- .../redis_cluster/redis_cluster.go | 143 +--- .../redis_sentinel/sess_redis_sentinel.go | 182 ++--- .../sess_redis_sentinel_test.go | 4 +- .../web/session => session}/sess_cookie.go | 31 +- .../session => session}/sess_cookie_test.go | 0 {server/web/session => session}/sess_file.go | 36 +- {server/web/session => session}/sess_mem.go | 37 +- {adapter/session => session}/sess_mem_test.go | 0 {server/web/session => session}/sess_test.go | 0 {server/web/session => session}/sess_utils.go | 2 +- {server/web/session => session}/session.go | 53 +- .../web/session => session}/ssdb/sess_ssdb.go | 68 +- server/web/staticfile.go => staticfile.go | 11 +- .../staticfile_test.go => staticfile_test.go | 2 +- {server/web/swagger => swagger}/swagger.go | 0 task/govenor_command.go | 92 --- task/governor_command_test.go | 111 --- task/task_test.go | 117 --- server/web/template.go => template.go | 14 +- .../web/template_test.go => template_test.go | 41 +- server/web/templatefunc.go => templatefunc.go | 16 +- ...mplatefunc_test.go => templatefunc_test.go | 2 +- {test => testdata}/Makefile | 0 {test => testdata}/bindata.go | 5 +- {test => testdata}/views/blocks/block.tpl | 0 {test => testdata}/views/header.tpl | 0 {test => testdata}/views/index.tpl | 0 .../config/json.go => testing/assertions.go | 6 +- {client/httplib/testing => testing}/client.go | 13 +- {core/governor => toolbox}/healthcheck.go | 4 +- {core/governor => toolbox}/profile.go | 42 +- {adapter/toolbox => toolbox}/profile_test.go | 0 {server/web => toolbox}/statistics.go | 20 +- .../toolbox => toolbox}/statistics_test.go | 0 {task => toolbox}/task.go | 246 ++---- {adapter/toolbox => toolbox}/task_test.go | 4 - server/web/tree.go => tree.go | 31 +- server/web/tree_test.go => tree_test.go | 4 +- .../unregroute_test.go => unregroute_test.go | 2 +- {core/utils => utils}/caller.go | 0 {adapter/utils => utils}/caller_test.go | 0 {adapter/utils => utils}/captcha/LICENSE | 0 {adapter/utils => utils}/captcha/README.md | 0 {server/web => utils}/captcha/captcha.go | 43 +- {server/web => utils}/captcha/image.go | 0 {server/web => utils}/captcha/image_test.go | 2 +- {server/web => utils}/captcha/siprng.go | 0 {server/web => utils}/captcha/siprng_test.go | 0 {core/utils => utils}/debug.go | 0 {adapter/utils => utils}/debug_test.go | 0 {core/utils => utils}/file.go | 0 {core/utils => utils}/file_test.go | 0 {core/utils => utils}/mail.go | 0 {adapter/utils => utils}/mail_test.go | 0 .../web => utils}/pagination/controller.go | 7 +- {adapter/utils => utils}/pagination/doc.go | 0 {core/utils => utils}/pagination/paginator.go | 0 {core/utils => utils}/pagination/utils.go | 0 {core/utils => utils}/rand.go | 0 {adapter/utils => utils}/rand_test.go | 0 {core/utils => utils}/safemap.go | 0 {adapter/utils => utils}/safemap_test.go | 0 {core/utils => utils}/slice.go | 0 {adapter/utils => utils}/slice_test.go | 0 {core/utils => utils}/testdata/grepe.test | 0 {core/utils => utils}/utils.go | 0 {core/utils => utils}/utils_test.go | 0 {core/validation => validation}/README.md | 0 {core/validation => validation}/util.go | 2 +- {core/validation => validation}/util_test.go | 0 {core/validation => validation}/validation.go | 5 - .../validation_test.go | 0 {core/validation => validation}/validators.go | 3 +- 455 files changed, 4657 insertions(+), 29993 deletions(-) delete mode 100644 .github/workflows/stale.yml delete mode 100644 adapter/admin.go delete mode 100644 adapter/app.go delete mode 100644 adapter/beego.go delete mode 100644 adapter/build_info.go delete mode 100644 adapter/cache/cache_adapter.go delete mode 100644 adapter/cache/conv.go delete mode 100644 adapter/cache/file.go delete mode 100644 adapter/cache/memcache/memcache.go delete mode 100644 adapter/cache/memory.go delete mode 100644 adapter/cache/redis/redis.go delete mode 100644 adapter/cache/ssdb/ssdb.go delete mode 100644 adapter/config.go delete mode 100644 adapter/config/adapter.go delete mode 100644 adapter/config/config.go delete mode 100644 adapter/config/env/env.go delete mode 100644 adapter/config/fake.go delete mode 100644 adapter/config/xml/xml.go delete mode 100644 adapter/config/yaml/yaml.go delete mode 100644 adapter/context/acceptencoder.go delete mode 100644 adapter/context/context.go delete mode 100644 adapter/context/input.go delete mode 100644 adapter/context/output.go delete mode 100644 adapter/context/renderer.go delete mode 100644 adapter/controller.go delete mode 100644 adapter/doc.go delete mode 100644 adapter/error.go delete mode 100644 adapter/flash.go delete mode 100644 adapter/fs.go delete mode 100644 adapter/grace/grace.go delete mode 100644 adapter/grace/server.go delete mode 100644 adapter/httplib/httplib.go delete mode 100644 adapter/logs/accesslog.go delete mode 100644 adapter/logs/alils/alils.go delete mode 100644 adapter/logs/es/es.go delete mode 100644 adapter/logs/log.go delete mode 100644 adapter/logs/log_adapter.go delete mode 100644 adapter/logs/logger.go delete mode 100644 adapter/logs/logger_test.go delete mode 100644 adapter/migration/ddl.go delete mode 100644 adapter/migration/migration.go delete mode 100644 adapter/namespace.go delete mode 100644 adapter/orm/cmd.go delete mode 100644 adapter/orm/db.go delete mode 100644 adapter/orm/db_alias.go delete mode 100644 adapter/orm/models.go delete mode 100644 adapter/orm/models_boot.go delete mode 100644 adapter/orm/models_fields.go delete mode 100644 adapter/orm/orm.go delete mode 100644 adapter/orm/orm_conds.go delete mode 100644 adapter/orm/orm_log.go delete mode 100644 adapter/orm/orm_queryset.go delete mode 100644 adapter/orm/qb.go delete mode 100644 adapter/orm/qb_mysql.go delete mode 100644 adapter/orm/query_setter_adapter.go delete mode 100644 adapter/orm/types.go delete mode 100644 adapter/orm/utils.go delete mode 100644 adapter/plugins/apiauth/apiauth.go delete mode 100644 adapter/plugins/auth/basic.go delete mode 100644 adapter/plugins/authz/authz.go delete mode 100644 adapter/plugins/cors/cors.go delete mode 100644 adapter/policy.go delete mode 100644 adapter/router.go delete mode 100644 adapter/session/couchbase/sess_couchbase.go delete mode 100644 adapter/session/ledis/ledis_session.go delete mode 100644 adapter/session/memcache/sess_memcache.go delete mode 100644 adapter/session/mysql/sess_mysql.go delete mode 100644 adapter/session/postgres/sess_postgresql.go delete mode 100644 adapter/session/provider_adapter.go delete mode 100644 adapter/session/redis/sess_redis.go delete mode 100644 adapter/session/redis_cluster/redis_cluster.go delete mode 100644 adapter/session/redis_sentinel/sess_redis_sentinel.go delete mode 100644 adapter/session/sess_cookie.go delete mode 100644 adapter/session/sess_file.go delete mode 100644 adapter/session/sess_file_test.go delete mode 100644 adapter/session/sess_mem.go delete mode 100644 adapter/session/sess_test.go delete mode 100644 adapter/session/sess_utils.go delete mode 100644 adapter/session/session.go delete mode 100644 adapter/session/ssdb/sess_ssdb.go delete mode 100644 adapter/session/store_adapter.go delete mode 100644 adapter/swagger/swagger.go delete mode 100644 adapter/template.go delete mode 100644 adapter/templatefunc.go delete mode 100644 adapter/templatefunc_test.go delete mode 100644 adapter/testing/client.go delete mode 100644 adapter/toolbox/healthcheck.go delete mode 100644 adapter/toolbox/profile.go delete mode 100644 adapter/toolbox/statistics.go delete mode 100644 adapter/toolbox/task.go delete mode 100644 adapter/tree.go delete mode 100644 adapter/tree_test.go delete mode 100644 adapter/utils/captcha/captcha.go delete mode 100644 adapter/utils/captcha/image.go delete mode 100644 adapter/utils/captcha/image_test.go delete mode 100644 adapter/utils/debug.go delete mode 100644 adapter/utils/file.go delete mode 100644 adapter/utils/mail.go delete mode 100644 adapter/utils/pagination/controller.go delete mode 100644 adapter/utils/pagination/paginator.go delete mode 100644 adapter/utils/rand.go delete mode 100644 adapter/utils/safemap.go delete mode 100644 adapter/utils/slice.go delete mode 100644 adapter/utils/utils.go delete mode 100644 adapter/validation/util.go delete mode 100644 adapter/validation/validation.go delete mode 100644 adapter/validation/validators.go create mode 100644 admin.go create mode 100644 admin_test.go rename server/web/adminui.go => adminui.go (99%) create mode 100644 app.go rename server/web/beego.go => beego.go (65%) rename {client/cache => cache}/README.md (100%) rename {adapter/cache => cache}/cache.go (100%) rename {adapter/cache => cache}/cache_test.go (100%) rename {client/cache => cache}/conv.go (90%) rename {adapter/cache => cache}/conv_test.go (100%) rename {client/cache => cache}/file.go (68%) rename {client/cache => cache}/memcache/memcache.go (71%) rename {adapter/cache => cache}/memcache/memcache_test.go (90%) rename {client/cache => cache}/memory.go (65%) rename {client/cache => cache}/redis/redis.go (75%) rename {adapter/cache => cache}/redis/redis_test.go (86%) rename {client/cache => cache}/ssdb/ssdb.go (70%) rename {adapter/cache => cache}/ssdb/ssdb_test.go (90%) delete mode 100644 client/cache/cache.go delete mode 100644 client/cache/cache_test.go delete mode 100644 client/cache/conv_test.go delete mode 100644 client/cache/memcache/memcache_test.go delete mode 100644 client/cache/redis/redis_test.go delete mode 100644 client/cache/ssdb/ssdb_test.go delete mode 100644 client/httplib/filter.go delete mode 100644 client/httplib/filter/opentracing/filter.go delete mode 100644 client/httplib/filter/opentracing/filter_test.go delete mode 100644 client/httplib/filter/prometheus/filter.go delete mode 100644 client/httplib/filter/prometheus/filter_test.go delete mode 100644 client/httplib/httplib_test.go delete mode 100644 client/orm/cmd_utils.go delete mode 100644 client/orm/db_alias_test.go delete mode 100644 client/orm/do_nothing_orm.go delete mode 100644 client/orm/do_nothing_orm_test.go delete mode 100644 client/orm/filter.go delete mode 100644 client/orm/filter/bean/default_value_filter.go delete mode 100644 client/orm/filter/bean/default_value_filter_test.go delete mode 100644 client/orm/filter/opentracing/filter.go delete mode 100644 client/orm/filter/opentracing/filter_test.go delete mode 100644 client/orm/filter/prometheus/filter.go delete mode 100644 client/orm/filter/prometheus/filter_test.go delete mode 100644 client/orm/filter_orm_decorator.go delete mode 100644 client/orm/filter_orm_decorator_test.go delete mode 100644 client/orm/filter_test.go delete mode 100644 client/orm/hints/db_hints.go delete mode 100644 client/orm/hints/db_hints_test.go delete mode 100644 client/orm/invocation.go delete mode 100644 client/orm/migration/doc.go delete mode 100644 client/orm/model_utils_test.go delete mode 100644 client/orm/models.go delete mode 100644 client/orm/models_boot.go delete mode 100644 client/orm/models_utils_test.go delete mode 100644 client/orm/qb_postgres.go delete mode 100644 client/orm/qb_tidb.go delete mode 100644 client/orm/utils_test.go rename server/web/config.go => config.go (78%) rename {core/config => config}/config.go (65%) rename {adapter/config => config}/config_test.go (100%) rename {core/config => config}/env/env.go (96%) rename {adapter/config => config}/env/env_test.go (100%) rename {core/config => config}/fake.go (71%) rename {core/config => config}/ini.go (85%) rename {adapter/config => config}/ini_test.go (100%) rename {core/config/json => config}/json.go (71%) rename {adapter/config => config}/json_test.go (100%) rename {core/config => config}/xml/xml.go (66%) rename {adapter/config => config}/xml/xml_test.go (98%) rename {core/config => config}/yaml/yaml.go (71%) rename {adapter/config => config}/yaml/yaml_test.go (98%) rename server/web/config_test.go => config_test.go (95%) rename {server/web/context => context}/acceptencoder.go (86%) rename {server/web/context => context}/acceptencoder_test.go (100%) rename {server/web/context => context}/context.go (80%) rename {server/web/context => context}/context_test.go (100%) rename {server/web/context => context}/input.go (92%) rename {server/web/context => context}/input_test.go (95%) rename {server/web/context => context}/output.go (88%) rename {server/web/context => context}/param/conv.go (95%) rename {server/web/context => context}/param/methodparams.go (91%) rename {server/web/context => context}/param/options.go (100%) rename {server/web/context => context}/param/parsers.go (100%) rename {server/web/context => context}/param/parsers_test.go (98%) rename {server/web/context => context}/renderer.go (77%) rename {adapter/context => context}/response.go (83%) rename server/web/controller.go => controller.go (98%) rename server/web/controller_test.go => controller_test.go (94%) delete mode 100644 core/bean/context.go delete mode 100644 core/bean/doc.go delete mode 100644 core/bean/factory.go delete mode 100644 core/bean/metadata.go delete mode 100644 core/bean/tag_auto_wire_bean_factory.go delete mode 100644 core/bean/tag_auto_wire_bean_factory_test.go delete mode 100644 core/bean/time_type_adapter.go delete mode 100644 core/bean/time_type_adapter_test.go delete mode 100644 core/bean/type_adapter.go delete mode 100644 core/config/base_config_test.go delete mode 100644 core/config/config_test.go delete mode 100644 core/config/env/env_test.go delete mode 100644 core/config/error.go delete mode 100644 core/config/etcd/config.go delete mode 100644 core/config/etcd/config_test.go delete mode 100644 core/config/global.go delete mode 100644 core/config/global_test.go delete mode 100644 core/config/ini_test.go delete mode 100644 core/config/json/json_test.go delete mode 100644 core/config/toml/toml.go delete mode 100644 core/config/toml/toml_test.go delete mode 100644 core/config/xml/xml_test.go delete mode 100644 core/config/yaml/yaml_test.go delete mode 100644 core/governor/command.go delete mode 100644 core/governor/profile_test.go delete mode 100644 core/logs/access_log_test.go delete mode 100644 core/logs/conn_test.go delete mode 100644 core/logs/es/index.go delete mode 100644 core/logs/es/index_test.go delete mode 100644 core/logs/formatter.go delete mode 100644 core/logs/formatter_test.go delete mode 100644 core/logs/jianliao_test.go delete mode 100644 core/logs/log_msg.go delete mode 100644 core/logs/log_msg_test.go delete mode 100644 core/logs/log_test.go delete mode 100644 core/logs/slack.go delete mode 100644 core/utils/caller_test.go delete mode 100644 core/utils/debug_test.go delete mode 100644 core/utils/kv.go delete mode 100644 core/utils/kv_test.go delete mode 100644 core/utils/mail_test.go delete mode 100644 core/utils/pagination/doc.go delete mode 100644 core/utils/rand_test.go delete mode 100644 core/utils/safemap_test.go delete mode 100644 core/utils/slice_test.go delete mode 100644 core/utils/time.go delete mode 100644 core/validation/validation_test.go rename server/web/error.go => error.go (94%) rename server/web/error_test.go => error_test.go (99%) rename adapter/filter.go => filter.go (79%) rename server/web/filter_test.go => filter_test.go (97%) rename server/web/flash.go => flash.go (99%) rename server/web/flash_test.go => flash_test.go (99%) rename server/web/fs.go => fs.go (99%) rename {server/web/grace => grace}/grace.go (100%) rename {server/web/grace => grace}/server.go (98%) rename server/web/hooks.go => hooks.go (84%) rename {client/httplib => httplib}/README.md (100%) rename {client/httplib => httplib}/httplib.go (85%) rename {adapter/httplib => httplib}/httplib_test.go (86%) rename adapter/log.go => log.go (88%) rename {core/logs => logs}/README.md (100%) rename core/logs/access_log.go => logs/accesslog.go (89%) rename {core/logs => logs}/alils/alils.go (69%) rename {core/logs => logs}/alils/config.go (61%) rename {core/logs => logs}/alils/log.pb.go (95%) rename {core/logs => logs}/alils/log_config.go (91%) rename {core/logs => logs}/alils/log_project.go (99%) rename {core/logs => logs}/alils/log_store.go (98%) rename {core/logs => logs}/alils/machine_group.go (88%) rename {core/logs => logs}/alils/request.go (100%) rename {core/logs => logs}/alils/signature.go (100%) rename {core/logs => logs}/conn.go (65%) rename adapter/utils/caller.go => logs/conn_test.go (78%) rename {core/logs => logs}/console.go (57%) rename {core/logs => logs}/console_test.go (78%) rename {core/logs => logs}/es/es.go (56%) rename {core/logs => logs}/file.go (80%) rename {core/logs => logs}/file_test.go (89%) rename {core/logs => logs}/jianliao.go (56%) rename {core/logs => logs}/log.go (78%) rename {core/logs => logs}/logger.go (96%) rename {core/logs => logs}/logger_test.go (100%) rename {core/logs => logs}/multifile.go (83%) rename {core/logs => logs}/multifile_test.go (100%) create mode 100644 logs/slack.go rename {core/logs => logs}/smtp.go (77%) rename {core/logs => logs}/smtp_test.go (100%) rename {adapter/metric => metric}/prometheus.go (87%) rename {adapter/metric => metric}/prometheus_test.go (96%) rename {client/orm/migration => migration}/ddl.go (85%) rename {adapter/migration => migration}/doc.go (100%) rename {client/orm/migration => migration}/migration.go (99%) rename server/web/mime.go => mime.go (99%) rename server/web/namespace.go => namespace.go (98%) rename server/web/namespace_test.go => namespace_test.go (98%) rename {client/orm => orm}/README.md (100%) rename {client/orm => orm}/cmd.go (85%) create mode 100644 orm/cmd_utils.go rename {client/orm => orm}/db.go (94%) rename {client/orm => orm}/db_alias.go (62%) rename {client/orm => orm}/db_mysql.go (79%) rename {client/orm => orm}/db_oracle.go (67%) rename {client/orm => orm}/db_postgres.go (78%) rename {client/orm => orm}/db_sqlite.go (73%) rename {client/orm => orm}/db_tables.go (97%) rename {client/orm => orm}/db_tidb.go (100%) rename {client/orm => orm}/db_utils.go (100%) create mode 100644 orm/models.go create mode 100644 orm/models_boot.go rename {client/orm => orm}/models_fields.go (100%) rename {client/orm => orm}/models_info_f.go (97%) rename {client/orm => orm}/models_info_m.go (98%) rename {client/orm => orm}/models_test.go (66%) rename {client/orm => orm}/models_utils.go (93%) rename {client/orm => orm}/orm.go (57%) rename {client/orm => orm}/orm_conds.go (100%) rename {client/orm => orm}/orm_log.go (88%) rename {client/orm => orm}/orm_object.go (96%) rename {client/orm => orm}/orm_querym2m.go (97%) rename {client/orm => orm}/orm_queryset.go (91%) rename {client/orm => orm}/orm_raw.go (91%) rename {client/orm => orm}/orm_test.go (89%) rename {client/orm => orm}/qb.go (96%) rename {client/orm => orm}/qb_mysql.go (71%) rename {adapter/orm => orm}/qb_tidb.go (60%) rename {client/orm => orm}/types.go (77%) rename {client/orm => orm}/utils.go (100%) rename {adapter/orm => orm}/utils_test.go (100%) rename server/web/parser.go => parser.go (93%) rename {server/web/filter => plugins}/apiauth/apiauth.go (77%) rename {adapter/plugins => plugins}/apiauth/apiauth_test.go (100%) rename {server/web/filter => plugins}/auth/basic.go (94%) rename {server/web/filter => plugins}/authz/authz.go (94%) rename {adapter/plugins => plugins}/authz/authz_model.conf (100%) rename {adapter/plugins => plugins}/authz/authz_policy.csv (100%) rename {adapter/plugins => plugins}/authz/authz_test.go (96%) rename {server/web/filter => plugins}/cors/cors.go (98%) rename {server/web/filter => plugins}/cors/cors_test.go (88%) rename server/web/policy.go => policy.go (97%) rename server/web/router.go => router.go (79%) rename server/web/router_test.go => router_test.go (88%) create mode 100755 scripts/gobuild.sh create mode 100755 scripts/report_build_info.sh delete mode 100644 server/web/LICENSE delete mode 100644 server/web/admin.go delete mode 100644 server/web/admin_controller.go delete mode 100644 server/web/admin_test.go delete mode 100644 server/web/captcha/LICENSE delete mode 100644 server/web/captcha/README.md delete mode 100644 server/web/context/response.go delete mode 100644 server/web/doc.go delete mode 100644 server/web/filter.go delete mode 100644 server/web/filter/apiauth/apiauth_test.go delete mode 100644 server/web/filter/authz/authz_model.conf delete mode 100644 server/web/filter/authz/authz_policy.csv delete mode 100644 server/web/filter/authz/authz_test.go delete mode 100644 server/web/filter/opentracing/filter.go delete mode 100644 server/web/filter/opentracing/filter_test.go delete mode 100644 server/web/filter/prometheus/filter.go delete mode 100644 server/web/filter/prometheus/filter_test.go delete mode 100644 server/web/filter_chain_test.go delete mode 100644 server/web/server.go delete mode 100644 server/web/server_test.go delete mode 100644 server/web/session/couchbase/sess_couchbase_test.go delete mode 100644 server/web/session/ledis/ledis_session_test.go delete mode 100644 server/web/session/redis/sess_redis_test.go delete mode 100644 server/web/session/redis_cluster/redis_cluster_test.go delete mode 100644 server/web/session/redis_sentinel/sess_redis_sentinel_test.go delete mode 100644 server/web/session/sess_cookie_test.go delete mode 100644 server/web/session/sess_file_test.go delete mode 100644 server/web/session/sess_mem_test.go delete mode 100644 server/web/session/ssdb/sess_ssdb_test.go delete mode 100644 server/web/statistics_test.go rename {server/web/session => session}/README.md (98%) rename {server/web/session => session}/couchbase/sess_couchbase.go (69%) rename {server/web/session => session}/ledis/ledis_session.go (59%) rename {server/web/session => session}/memcache/sess_memcache.go (81%) rename {server/web/session => session}/mysql/sess_mysql.go (82%) rename {server/web/session => session}/postgres/sess_postgresql.go (83%) rename {server/web/session => session}/redis/sess_redis.go (50%) rename {server/web/session => session}/redis_cluster/redis_cluster.go (55%) rename {server/web/session => session}/redis_sentinel/sess_redis_sentinel.go (57%) rename {adapter/session => session}/redis_sentinel/sess_redis_sentinel_test.go (96%) rename {server/web/session => session}/sess_cookie.go (77%) rename {adapter/session => session}/sess_cookie_test.go (100%) rename {server/web/session => session}/sess_file.go (85%) rename {server/web/session => session}/sess_mem.go (78%) rename {adapter/session => session}/sess_mem_test.go (100%) rename {server/web/session => session}/sess_test.go (100%) rename {server/web/session => session}/sess_utils.go (99%) rename {server/web/session => session}/session.go (86%) rename {server/web/session => session}/ssdb/sess_ssdb.go (66%) rename server/web/staticfile.go => staticfile.go (96%) rename server/web/staticfile_test.go => staticfile_test.go (99%) rename {server/web/swagger => swagger}/swagger.go (100%) delete mode 100644 task/govenor_command.go delete mode 100644 task/governor_command_test.go delete mode 100644 task/task_test.go rename server/web/template.go => template.go (97%) rename server/web/template_test.go => template_test.go (88%) rename server/web/templatefunc.go => templatefunc.go (98%) rename server/web/templatefunc_test.go => templatefunc_test.go (99%) rename {test => testdata}/Makefile (100%) rename {test => testdata}/bindata.go (99%) rename {test => testdata}/views/blocks/block.tpl (100%) rename {test => testdata}/views/header.tpl (100%) rename {test => testdata}/views/index.tpl (100%) rename adapter/config/json.go => testing/assertions.go (89%) rename {client/httplib/testing => testing}/client.go (88%) rename {core/governor => toolbox}/healthcheck.go (96%) rename {core/governor => toolbox}/profile.go (82%) rename {adapter/toolbox => toolbox}/profile_test.go (100%) rename {server/web => toolbox}/statistics.go (86%) rename {adapter/toolbox => toolbox}/statistics_test.go (100%) rename {task => toolbox}/task.go (74%) rename {adapter/toolbox => toolbox}/task_test.go (97%) rename server/web/tree.go => tree.go (95%) rename server/web/tree_test.go => tree_test.go (99%) rename server/web/unregroute_test.go => unregroute_test.go (99%) rename {core/utils => utils}/caller.go (100%) rename {adapter/utils => utils}/caller_test.go (100%) rename {adapter/utils => utils}/captcha/LICENSE (100%) rename {adapter/utils => utils}/captcha/README.md (100%) rename {server/web => utils}/captcha/captcha.go (82%) rename {server/web => utils}/captcha/image.go (100%) rename {server/web => utils}/captcha/image_test.go (97%) rename {server/web => utils}/captcha/siprng.go (100%) rename {server/web => utils}/captcha/siprng_test.go (100%) rename {core/utils => utils}/debug.go (100%) rename {adapter/utils => utils}/debug_test.go (100%) rename {core/utils => utils}/file.go (100%) rename {core/utils => utils}/file_test.go (100%) rename {core/utils => utils}/mail.go (100%) rename {adapter/utils => utils}/mail_test.go (100%) rename {server/web => utils}/pagination/controller.go (81%) rename {adapter/utils => utils}/pagination/doc.go (100%) rename {core/utils => utils}/pagination/paginator.go (100%) rename {core/utils => utils}/pagination/utils.go (100%) rename {core/utils => utils}/rand.go (100%) rename {adapter/utils => utils}/rand_test.go (100%) rename {core/utils => utils}/safemap.go (100%) rename {adapter/utils => utils}/safemap_test.go (100%) rename {core/utils => utils}/slice.go (100%) rename {adapter/utils => utils}/slice_test.go (100%) rename {core/utils => utils}/testdata/grepe.test (100%) rename {core/utils => utils}/utils.go (100%) rename {core/utils => utils}/utils_test.go (100%) rename {core/validation => validation}/README.md (100%) rename {core/validation => validation}/util.go (99%) rename {core/validation => validation}/util_test.go (100%) rename {core/validation => validation}/validation.go (99%) rename {adapter/validation => validation}/validation_test.go (100%) rename {core/validation => validation}/validators.go (99%) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 3a4d2e9a..00000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Mark stale issues and pull requests - -on: - schedule: - - cron: "30 1 * * *" - -jobs: - stale: - - runs-on: ubuntu-latest - - steps: - - uses: actions/stale@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue is inactive for a long time.' - stale-pr-message: 'This PR is inactive for a long time' - stale-issue-label: 'inactive-issue' - stale-pr-label: 'inactive-pr' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 304c4b73..e1b65291 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,3 @@ *.swp *.swo beego.iml - -_beeTmp/ -_beeTmp2/ -pkg/_beeTmp/ -pkg/_beeTmp2/ -test/tmp/ diff --git a/.travis.yml b/.travis.yml index 973b40ef..c019c999 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,59 +1,30 @@ language: go go: - - "1.14.x" + - "1.13.x" services: - redis-server - mysql - postgresql - memcached - - docker env: global: - GO_REPO_FULLNAME="github.com/astaxie/beego" matrix: - ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db - ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable" - - ORM_DRIVER=mysql export ORM_SOURCE="root:@/orm_test?charset=utf8" before_install: - # link the local repo with ${GOPATH}/src// - - GO_REPO_NAMESPACE=${GO_REPO_FULLNAME%/*} - # relies on GOPATH to contain only one directory... - - mkdir -p ${GOPATH}/src/${GO_REPO_NAMESPACE} - - ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH}/src/${GO_REPO_FULLNAME} - - cd ${GOPATH}/src/${GO_REPO_FULLNAME} - # get and build ssdb - - git clone git://github.com/ideawu/ssdb.git - - cd ssdb - - make - - cd .. - # - prepare etcd - # - prepare for etcd unit tests - - rm -rf /tmp/etcd-data.tmp - - mkdir -p /tmp/etcd-data.tmp - - docker rmi gcr.io/etcd-development/etcd:v3.3.25 || true && - docker run -d - -p 2379:2379 - -p 2380:2380 - --mount type=bind,source=/tmp/etcd-data.tmp,destination=/etcd-data - --name etcd-gcr-v3.3.25 - gcr.io/etcd-development/etcd:v3.3.25 - /usr/local/bin/etcd - --name s1 - --data-dir /etcd-data - --listen-client-urls http://0.0.0.0:2379 - --advertise-client-urls http://0.0.0.0:2379 - --listen-peer-urls http://0.0.0.0:2380 - --initial-advertise-peer-urls http://0.0.0.0:2380 - --initial-cluster s1=http://0.0.0.0:2380 - --initial-cluster-token tkn - --initial-cluster-state new - - docker exec etcd-gcr-v3.3.25 /bin/sh -c "ETCDCTL_API=3 /usr/local/bin/etcdctl put current.float 1.23" - - docker exec etcd-gcr-v3.3.25 /bin/sh -c "ETCDCTL_API=3 /usr/local/bin/etcdctl put current.bool true" - - docker exec etcd-gcr-v3.3.25 /bin/sh -c "ETCDCTL_API=3 /usr/local/bin/etcdctl put current.int 11" - - docker exec etcd-gcr-v3.3.25 /bin/sh -c "ETCDCTL_API=3 /usr/local/bin/etcdctl put current.string hello" - - docker exec etcd-gcr-v3.3.25 /bin/sh -c "ETCDCTL_API=3 /usr/local/bin/etcdctl put current.serialize.name test" - - docker exec etcd-gcr-v3.3.25 /bin/sh -c "ETCDCTL_API=3 /usr/local/bin/etcdctl put sub.sub.key1 sub.sub.key" + # link the local repo with ${GOPATH}/src// + - GO_REPO_NAMESPACE=${GO_REPO_FULLNAME%/*} + # relies on GOPATH to contain only one directory... + - mkdir -p ${GOPATH}/src/${GO_REPO_NAMESPACE} + - ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH}/src/${GO_REPO_FULLNAME} + - cd ${GOPATH}/src/${GO_REPO_FULLNAME} + # get and build ssdb + - git clone git://github.com/ideawu/ssdb.git + - cd ssdb + - make + - cd .. install: - go get github.com/lib/pq - go get github.com/go-sql-driver/mysql @@ -80,10 +51,7 @@ install: - go get -u golang.org/x/lint/golint - go get -u github.com/go-redis/redis before_script: - - # - - psql --version - # - prepare for orm unit tests - sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi" - sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi" - sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi" @@ -95,11 +63,11 @@ after_script: - killall -w ssdb-server - rm -rf ./res/var/* script: - - go test ./... - - staticcheck -show-ignored -checks "-ST1017,-U1000,-ST1005,-S1034,-S1012,-SA4006,-SA6005,-SA1019,-SA1024" ./ + - go test -v ./... + - staticcheck -show-ignored -checks "-ST1017,-U1000,-ST1005,-S1034,-S1012,-SA4006,-SA6005,-SA1019,-SA1024" - unconvert $(go list ./... | grep -v /vendor/) - ineffassign . - find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s - golint ./... addons: - postgresql: "9.6" \ No newline at end of file + postgresql: "9.6" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cb279cbb..9d511616 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,58 +7,17 @@ It is the work of hundreds of contributors. We appreciate your help! Here are instructions to get you started. They are probably not perfect, please let us know if anything feels wrong or incomplete. -## Prepare environment - -Firstly, install some tools. Execute those commands **outside** the project. Or those command will modify go.mod file. - -```shell script -go get -u golang.org/x/tools/cmd/goimports - -go get -u github.com/gordonklaus/ineffassign -``` - -Put those lines into your pre-commit githook script: -```shell script -goimports -w -format-only ./ - -ineffassign . - -staticcheck -show-ignored -checks "-ST1017,-U1000,-ST1005,-S1034,-S1012,-SA4006,-SA6005,-SA1019,-SA1024" ./ -``` - -## Prepare middleware - -Beego uses many middlewares, including MySQL, Redis, SSDB and so on. - -We provide docker compose file to start all middlewares. - -You can run: -```shell script -docker-compose -f scripts/test_docker_compose.yml up -d -``` -Unit tests read addresses from environment, here is an example: -```shell script -export ORM_DRIVER=mysql -export ORM_SOURCE="beego:test@tcp(192.168.0.105:13306)/orm_test?charset=utf8" -export MEMCACHE_ADDR="192.168.0.105:11211" -export REDIS_ADDR="192.168.0.105:6379" -export SSDB_ADDR="192.168.0.105:8888" -``` - - ## Contribution guidelines ### Pull requests First of all. beego follow the gitflow. So please send you pull request -to **develop-2** branch. We will close the pull request to master branch. +to **develop** branch. We will close the pull request to master branch. We are always happy to receive pull requests, and do our best to review them as fast as possible. Not sure if that typo is worth a pull request? Do it! We will appreciate it. -Don't forget to rebase your commits! - If your pull request is not accepted on the first try, don't be discouraged! Sometimes we can make a mistake, please do more explaining for us. We will appreciate it. @@ -89,5 +48,5 @@ documenting your bug report or improvement proposal. If it does, it never hurts to add a quick "+1" or "I have this problem too". This will help prioritize the most common problems and requests. -Also, if you don't know how to use it. please make sure you have read through +Also if you don't know how to use it. please make sure you have read though the docs in http://beego.me/docs \ No newline at end of file diff --git a/README.md b/README.md index 934fc429..3b414c6f 100644 --- a/README.md +++ b/README.md @@ -8,21 +8,6 @@ It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific feature ## Quick Start -###### Please see [Documentation](http://beego.me/docs) for more. - -###### [beego-example](https://github.com/beego-dev/beego-example) - -### Web Application - -#### Create `hello` directory, cd `hello` directory - - mkdir hello - cd hello - -#### Init module - - go mod init - #### Download and install go get github.com/astaxie/beego @@ -31,10 +16,10 @@ It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific feature ```go package main -import "github.com/astaxie/beego/server/web" +import "github.com/astaxie/beego" func main(){ - web.Run() + beego.Run() } ``` #### Build and run @@ -46,204 +31,9 @@ func main(){ Congratulations! You've just built your first **beego** app. -### Using ORM module - -```go - -package main - -import ( - "github.com/astaxie/beego/client/orm" - "github.com/astaxie/beego/core/logs" - _ "github.com/go-sql-driver/mysql" -) - -// User - -type User struct { - ID int `orm:"column(id)"` - Name string `orm:"column(name)"` -} - -func init() { - // need to register models in init - orm.RegisterModel(new(User)) - - // need to register db driver - orm.RegisterDriver("mysql", orm.DRMySQL) - - // need to register default database - orm.RegisterDataBase("default", "mysql", "beego:test@tcp(192.168.0.105:13306)/orm_test?charset=utf8") -} - -func main() { - // automatically build table - orm.RunSyncdb("default", false, true) - - // create orm object, and it will use `default` database - o := orm.NewOrm() - - // data - user := new(User) - user.Name = "mike" - - // insert data - id, err := o.Insert(user) - if err != nil { - logs.Info(err) - } - - // ... -} -``` - -### Using httplib as http client -```go -package main - -import ( - "github.com/astaxie/beego/client/httplib" - "github.com/astaxie/beego/core/logs" -) - -func main() { - // Get, more methods please read docs - req := httplib.Get("http://beego.me/") - str, err := req.String() - if err != nil { - logs.Error(err) - } - logs.Info(str) -} - -``` - -### Using config module - -```go -package main - -import ( - "context" - - "github.com/astaxie/beego/core/config" - "github.com/astaxie/beego/core/logs" -) - -var ( - ConfigFile = "./app.conf" -) - -func main() { - cfg, err := config.NewConfig("ini", ConfigFile) - if err != nil { - logs.Critical("An error occurred:", err) - panic(err) - } - res, _ := cfg.String(context.Background(), "name") - logs.Info("load config name is", res) -} -``` -### Using logs module -```go -package main - -import ( - "github.com/astaxie/beego/core/logs" -) - -func main() { - err := logs.SetLogger(logs.AdapterFile, `{"filename":"project.log","level":7,"maxlines":0,"maxsize":0,"daily":true,"maxdays":10,"color":true}`) - if err != nil { - panic(err) - } - logs.Info("hello beego") -} -``` -### Using timed task - -```go -package main - -import ( - "context" - "time" - - "github.com/astaxie/beego/core/logs" - "github.com/astaxie/beego/task" -) - -func main() { - // create a task - tk1 := task.NewTask("tk1", "0/3 * * * * *", func(ctx context.Context) error { logs.Info("tk1"); return nil }) - - // check task - err := tk1.Run(context.Background()) - if err != nil { - logs.Error(err) - } - - // add task to global todolist - task.AddTask("tk1", tk1) - - // start tasks - task.StartTask() - - // wait 12 second - time.Sleep(12 * time.Second) - defer task.StopTask() -} -``` - -### Using cache module - -```go -package main - -import ( - "context" - "time" - - "github.com/astaxie/beego/client/cache" - - // don't forget this - _ "github.com/astaxie/beego/client/cache/redis" - - "github.com/astaxie/beego/core/logs" -) - -func main() { - // create cache - bm, err := cache.NewCache("redis", `{"key":"default", "conn":":6379", "password":"123456", "dbNum":"0"}`) - if err != nil { - logs.Error(err) - } - - // put - isPut := bm.Put(context.Background(), "astaxie", 1, time.Second*10) - logs.Info(isPut) - - isPut = bm.Put(context.Background(), "hello", "world", time.Second*10) - logs.Info(isPut) - - // get - result, _ := bm.Get(context.Background(),"astaxie") - logs.Info(string(result.([]byte))) - - multiResult, _ := bm.GetMulti(context.Background(), []string{"astaxie", "hello"}) - for i := range multiResult { - logs.Info(string(multiResult[i].([]byte))) - } - - // isExist - isExist, _ := bm.IsExist(context.Background(), "astaxie") - logs.Info(isExist) - - // delete - isDelete := bm.Delete(context.Background(), "astaxie") - logs.Info(isDelete) -} -``` +###### Please see [Documentation](http://beego.me/docs) for more. +###### [beego-example](https://github.com/beego-dev/beego-example) ## Features diff --git a/adapter/admin.go b/adapter/admin.go deleted file mode 100644 index e555f59e..00000000 --- a/adapter/admin.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 adapter - -import ( - "time" - - _ "github.com/astaxie/beego/core/governor" - "github.com/astaxie/beego/server/web" -) - -// FilterMonitorFunc is default monitor filter when admin module is enable. -// if this func returns, admin module records qps for this request by condition of this function logic. -// usage: -// func MyFilterMonitor(method, requestPath string, t time.Duration, pattern string, statusCode int) bool { -// if method == "POST" { -// return false -// } -// if t.Nanoseconds() < 100 { -// return false -// } -// if strings.HasPrefix(requestPath, "/astaxie") { -// return false -// } -// return true -// } -// beego.FilterMonitorFunc = MyFilterMonitor. -var FilterMonitorFunc func(string, string, time.Duration, string, int) bool - -// PrintTree prints all registered routers. -func PrintTree() M { - return (M)(web.BeeApp.PrintTree()) -} diff --git a/adapter/app.go b/adapter/app.go deleted file mode 100644 index e20cd9d2..00000000 --- a/adapter/app.go +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 adapter - -import ( - "net/http" - - context2 "github.com/astaxie/beego/adapter/context" - "github.com/astaxie/beego/server/web" - "github.com/astaxie/beego/server/web/context" -) - -var ( - // BeeApp is an application instance - BeeApp *App -) - -func init() { - // create beego application - BeeApp = (*App)(web.BeeApp) -} - -// App defines beego application with a new PatternServeMux. -type App web.HttpServer - -// NewApp returns a new beego application. -func NewApp() *App { - return (*App)(web.NewHttpSever()) -} - -// MiddleWare function for http.Handler -type MiddleWare web.MiddleWare - -// Run beego application. -func (app *App) Run(mws ...MiddleWare) { - newMws := oldMiddlewareToNew(mws) - (*web.HttpServer)(app).Run("", newMws...) -} - -func oldMiddlewareToNew(mws []MiddleWare) []web.MiddleWare { - newMws := make([]web.MiddleWare, 0, len(mws)) - for _, old := range mws { - newMws = append(newMws, (web.MiddleWare)(old)) - } - return newMws -} - -// Router adds a patterned controller handler to BeeApp. -// it's an alias method of HttpServer.Router. -// usage: -// simple router -// beego.Router("/admin", &admin.UserController{}) -// beego.Router("/admin/index", &admin.ArticleController{}) -// -// regex router -// -// beego.Router("/api/:id([0-9]+)", &controllers.RController{}) -// -// custom rules -// beego.Router("/api/list",&RestController{},"*:ListFood") -// beego.Router("/api/create",&RestController{},"post:CreateFood") -// beego.Router("/api/update",&RestController{},"put:UpdateFood") -// beego.Router("/api/delete",&RestController{},"delete:DeleteFood") -func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App { - return (*App)(web.Router(rootpath, c, mappingMethods...)) -} - -// UnregisterFixedRoute unregisters the route with the specified fixedRoute. It is particularly useful -// in web applications that inherit most routes from a base webapp via the underscore -// import, and aim to overwrite only certain paths. -// The method parameter can be empty or "*" for all HTTP methods, or a particular -// method type (e.g. "GET" or "POST") for selective removal. -// -// Usage (replace "GET" with "*" for all methods): -// beego.UnregisterFixedRoute("/yourpreviouspath", "GET") -// beego.Router("/yourpreviouspath", yourControllerAddress, "get:GetNewPage") -func UnregisterFixedRoute(fixedRoute string, method string) *App { - return (*App)(web.UnregisterFixedRoute(fixedRoute, method)) -} - -// Include will generate router file in the router/xxx.go from the controller's comments -// usage: -// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) -// type BankAccount struct{ -// beego.Controller -// } -// -// register the function -// func (b *BankAccount)Mapping(){ -// b.Mapping("ShowAccount" , b.ShowAccount) -// b.Mapping("ModifyAccount", b.ModifyAccount) -// } -// -// //@router /account/:id [get] -// func (b *BankAccount) ShowAccount(){ -// //logic -// } -// -// -// //@router /account/:id [post] -// func (b *BankAccount) ModifyAccount(){ -// //logic -// } -// -// the comments @router url methodlist -// url support all the function Router's pattern -// methodlist [get post head put delete options *] -func Include(cList ...ControllerInterface) *App { - newList := oldToNewCtrlIntfs(cList) - return (*App)(web.Include(newList...)) -} - -func oldToNewCtrlIntfs(cList []ControllerInterface) []web.ControllerInterface { - newList := make([]web.ControllerInterface, 0, len(cList)) - for _, c := range cList { - newList = append(newList, c) - } - return newList -} - -// RESTRouter adds a restful controller handler to BeeApp. -// its' controller implements beego.ControllerInterface and -// defines a param "pattern/:objectId" to visit each resource. -func RESTRouter(rootpath string, c ControllerInterface) *App { - return (*App)(web.RESTRouter(rootpath, c)) -} - -// AutoRouter adds defined controller handler to BeeApp. -// it's same to HttpServer.AutoRouter. -// if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page, -// visit the url /main/list to exec List function or /main/page to exec Page function. -func AutoRouter(c ControllerInterface) *App { - return (*App)(web.AutoRouter(c)) -} - -// AutoPrefix adds controller handler to BeeApp with prefix. -// it's same to HttpServer.AutoRouterWithPrefix. -// if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page, -// visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function. -func AutoPrefix(prefix string, c ControllerInterface) *App { - return (*App)(web.AutoPrefix(prefix, c)) -} - -// Get used to register router for Get method -// usage: -// beego.Get("/", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Get(rootpath string, f FilterFunc) *App { - return (*App)(web.Get(rootpath, func(ctx *context.Context) { - f((*context2.Context)(ctx)) - })) -} - -// Post used to register router for Post method -// usage: -// beego.Post("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Post(rootpath string, f FilterFunc) *App { - return (*App)(web.Post(rootpath, func(ctx *context.Context) { - f((*context2.Context)(ctx)) - })) -} - -// Delete used to register router for Delete method -// usage: -// beego.Delete("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Delete(rootpath string, f FilterFunc) *App { - return (*App)(web.Delete(rootpath, func(ctx *context.Context) { - f((*context2.Context)(ctx)) - })) -} - -// Put used to register router for Put method -// usage: -// beego.Put("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Put(rootpath string, f FilterFunc) *App { - return (*App)(web.Put(rootpath, func(ctx *context.Context) { - f((*context2.Context)(ctx)) - })) -} - -// Head used to register router for Head method -// usage: -// beego.Head("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Head(rootpath string, f FilterFunc) *App { - return (*App)(web.Head(rootpath, func(ctx *context.Context) { - f((*context2.Context)(ctx)) - })) -} - -// Options used to register router for Options method -// usage: -// beego.Options("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Options(rootpath string, f FilterFunc) *App { - return (*App)(web.Options(rootpath, func(ctx *context.Context) { - f((*context2.Context)(ctx)) - })) -} - -// Patch used to register router for Patch method -// usage: -// beego.Patch("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Patch(rootpath string, f FilterFunc) *App { - return (*App)(web.Patch(rootpath, func(ctx *context.Context) { - f((*context2.Context)(ctx)) - })) -} - -// Any used to register router for all methods -// usage: -// beego.Any("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Any(rootpath string, f FilterFunc) *App { - return (*App)(web.Any(rootpath, func(ctx *context.Context) { - f((*context2.Context)(ctx)) - })) -} - -// Handler used to register a Handler router -// usage: -// beego.Handler("/api", http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { -// fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) -// })) -func Handler(rootpath string, h http.Handler, options ...interface{}) *App { - return (*App)(web.Handler(rootpath, h, options)) -} - -// InsertFilter adds a FilterFunc with pattern condition and action constant. -// The pos means action constant including -// beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter. -// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute) -func InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) *App { - opts := oldToNewFilterOpts(params) - return (*App)(web.InsertFilter(pattern, pos, func(ctx *context.Context) { - filter((*context2.Context)(ctx)) - }, opts...)) -} diff --git a/adapter/beego.go b/adapter/beego.go deleted file mode 100644 index bbe37db8..00000000 --- a/adapter/beego.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 adapter - -import ( - "github.com/astaxie/beego" - "github.com/astaxie/beego/server/web" -) - -const ( - - // VERSION represent beego web framework version. - VERSION = beego.VERSION - - // DEV is for develop - DEV = web.DEV - // PROD is for production - PROD = web.PROD -) - -// M is Map shortcut -type M web.M - -// Hook function to run -type hookfunc func() error - -var ( - hooks = make([]hookfunc, 0) // hook function slice to store the hookfunc -) - -// AddAPPStartHook is used to register the hookfunc -// The hookfuncs will run in beego.Run() -// such as initiating session , starting middleware , building template, starting admin control and so on. -func AddAPPStartHook(hf ...hookfunc) { - for _, f := range hf { - web.AddAPPStartHook(func() error { - return f() - }) - } -} - -// Run beego application. -// beego.Run() default run on HttpPort -// beego.Run("localhost") -// beego.Run(":8089") -// beego.Run("127.0.0.1:8089") -func Run(params ...string) { - web.Run(params...) -} - -// RunWithMiddleWares Run beego application with middlewares. -func RunWithMiddleWares(addr string, mws ...MiddleWare) { - newMws := oldMiddlewareToNew(mws) - web.RunWithMiddleWares(addr, newMws...) -} - -// TestBeegoInit is for test package init -func TestBeegoInit(ap string) { - web.TestBeegoInit(ap) -} - -// InitBeegoBeforeTest is for test package init -func InitBeegoBeforeTest(appConfigPath string) { - web.InitBeegoBeforeTest(appConfigPath) -} diff --git a/adapter/build_info.go b/adapter/build_info.go deleted file mode 100644 index 1e8dacf0..00000000 --- a/adapter/build_info.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2020 astaxie -// -// Licensed 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 adapter - -var ( - BuildVersion string - BuildGitRevision string - BuildStatus string - BuildTag string - BuildTime string - - GoVersion string - - GitBranch string -) diff --git a/adapter/cache/cache_adapter.go b/adapter/cache/cache_adapter.go deleted file mode 100644 index 3bfd0bf8..00000000 --- a/adapter/cache/cache_adapter.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 cache - -import ( - "context" - "time" - - "github.com/astaxie/beego/client/cache" -) - -type newToOldCacheAdapter struct { - delegate cache.Cache -} - -func (c *newToOldCacheAdapter) Get(key string) interface{} { - res, _ := c.delegate.Get(context.Background(), key) - return res -} - -func (c *newToOldCacheAdapter) GetMulti(keys []string) []interface{} { - res, _ := c.delegate.GetMulti(context.Background(), keys) - return res -} - -func (c *newToOldCacheAdapter) Put(key string, val interface{}, timeout time.Duration) error { - return c.delegate.Put(context.Background(), key, val, timeout) -} - -func (c *newToOldCacheAdapter) Delete(key string) error { - return c.delegate.Delete(context.Background(), key) -} - -func (c *newToOldCacheAdapter) Incr(key string) error { - return c.delegate.Incr(context.Background(), key) -} - -func (c *newToOldCacheAdapter) Decr(key string) error { - return c.delegate.Decr(context.Background(), key) -} - -func (c *newToOldCacheAdapter) IsExist(key string) bool { - res, err := c.delegate.IsExist(context.Background(), key) - return res && err == nil -} - -func (c *newToOldCacheAdapter) ClearAll() error { - return c.delegate.ClearAll(context.Background()) -} - -func (c *newToOldCacheAdapter) StartAndGC(config string) error { - return c.delegate.StartAndGC(config) -} - -func CreateNewToOldCacheAdapter(delegate cache.Cache) Cache { - return &newToOldCacheAdapter{ - delegate: delegate, - } -} - -type oldToNewCacheAdapter struct { - old Cache -} - -func (o *oldToNewCacheAdapter) Get(ctx context.Context, key string) (interface{}, error) { - return o.old.Get(key), nil -} - -func (o *oldToNewCacheAdapter) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { - return o.old.GetMulti(keys), nil -} - -func (o *oldToNewCacheAdapter) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { - return o.old.Put(key, val, timeout) -} - -func (o *oldToNewCacheAdapter) Delete(ctx context.Context, key string) error { - return o.old.Delete(key) -} - -func (o *oldToNewCacheAdapter) Incr(ctx context.Context, key string) error { - return o.old.Incr(key) -} - -func (o *oldToNewCacheAdapter) Decr(ctx context.Context, key string) error { - return o.old.Decr(key) -} - -func (o *oldToNewCacheAdapter) IsExist(ctx context.Context, key string) (bool, error) { - return o.old.IsExist(key), nil -} - -func (o *oldToNewCacheAdapter) ClearAll(ctx context.Context) error { - return o.old.ClearAll() -} - -func (o *oldToNewCacheAdapter) StartAndGC(config string) error { - return o.old.StartAndGC(config) -} - -func CreateOldToNewAdapter(old Cache) cache.Cache { - return &oldToNewCacheAdapter{ - old: old, - } -} diff --git a/adapter/cache/conv.go b/adapter/cache/conv.go deleted file mode 100644 index 18b8a255..00000000 --- a/adapter/cache/conv.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 cache - -import ( - "github.com/astaxie/beego/client/cache" -) - -// GetString convert interface to string. -func GetString(v interface{}) string { - return cache.GetString(v) -} - -// GetInt convert interface to int. -func GetInt(v interface{}) int { - return cache.GetInt(v) -} - -// GetInt64 convert interface to int64. -func GetInt64(v interface{}) int64 { - return cache.GetInt64(v) -} - -// GetFloat64 convert interface to float64. -func GetFloat64(v interface{}) float64 { - return cache.GetFloat64(v) -} - -// GetBool convert interface to bool. -func GetBool(v interface{}) bool { - return cache.GetBool(v) -} diff --git a/adapter/cache/file.go b/adapter/cache/file.go deleted file mode 100644 index 74eb980a..00000000 --- a/adapter/cache/file.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 cache - -import ( - "github.com/astaxie/beego/client/cache" -) - -// NewFileCache Create new file cache with no config. -// the level and expiry need set in method StartAndGC as config string. -func NewFileCache() Cache { - // return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix} - return CreateNewToOldCacheAdapter(cache.NewFileCache()) -} - -func init() { - Register("file", NewFileCache) -} diff --git a/adapter/cache/memcache/memcache.go b/adapter/cache/memcache/memcache.go deleted file mode 100644 index b4da1bfe..00000000 --- a/adapter/cache/memcache/memcache.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 memcache for cache provider -// -// depend on github.com/bradfitz/gomemcache/memcache -// -// go install github.com/bradfitz/gomemcache/memcache -// -// Usage: -// import( -// _ "github.com/astaxie/beego/cache/memcache" -// "github.com/astaxie/beego/cache" -// ) -// -// bm, err := cache.NewCache("memcache", `{"conn":"127.0.0.1:11211"}`) -// -// more docs http://beego.me/docs/module/cache.md -package memcache - -import ( - "github.com/astaxie/beego/adapter/cache" - "github.com/astaxie/beego/client/cache/memcache" -) - -// NewMemCache create new memcache adapter. -func NewMemCache() cache.Cache { - return cache.CreateNewToOldCacheAdapter(memcache.NewMemCache()) -} - -func init() { - cache.Register("memcache", NewMemCache) -} diff --git a/adapter/cache/memory.go b/adapter/cache/memory.go deleted file mode 100644 index cf6e3992..00000000 --- a/adapter/cache/memory.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 cache - -import ( - "github.com/astaxie/beego/client/cache" -) - -// NewMemoryCache returns a new MemoryCache. -func NewMemoryCache() Cache { - return CreateNewToOldCacheAdapter(cache.NewMemoryCache()) -} - -func init() { - Register("memory", NewMemoryCache) -} diff --git a/adapter/cache/redis/redis.go b/adapter/cache/redis/redis.go deleted file mode 100644 index 3562057d..00000000 --- a/adapter/cache/redis/redis.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 redis for cache provider -// -// depend on github.com/gomodule/redigo/redis -// -// go install github.com/gomodule/redigo/redis -// -// Usage: -// import( -// _ "github.com/astaxie/beego/cache/redis" -// "github.com/astaxie/beego/cache" -// ) -// -// bm, err := cache.NewCache("redis", `{"conn":"127.0.0.1:11211"}`) -// -// more docs http://beego.me/docs/module/cache.md -package redis - -import ( - "github.com/astaxie/beego/adapter/cache" - redis2 "github.com/astaxie/beego/client/cache/redis" -) - -var ( - // DefaultKey the collection name of redis for cache adapter. - DefaultKey = "beecacheRedis" -) - -// NewRedisCache create new redis cache with default collection name. -func NewRedisCache() cache.Cache { - return cache.CreateNewToOldCacheAdapter(redis2.NewRedisCache()) -} - -func init() { - cache.Register("redis", NewRedisCache) -} diff --git a/adapter/cache/ssdb/ssdb.go b/adapter/cache/ssdb/ssdb.go deleted file mode 100644 index df552043..00000000 --- a/adapter/cache/ssdb/ssdb.go +++ /dev/null @@ -1,15 +0,0 @@ -package ssdb - -import ( - "github.com/astaxie/beego/adapter/cache" - ssdb2 "github.com/astaxie/beego/client/cache/ssdb" -) - -// NewSsdbCache create new ssdb adapter. -func NewSsdbCache() cache.Cache { - return cache.CreateNewToOldCacheAdapter(ssdb2.NewSsdbCache()) -} - -func init() { - cache.Register("ssdb", NewSsdbCache) -} diff --git a/adapter/config.go b/adapter/config.go deleted file mode 100644 index 6280b8f8..00000000 --- a/adapter/config.go +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 adapter - -import ( - "github.com/astaxie/beego/adapter/session" - newCfg "github.com/astaxie/beego/core/config" - "github.com/astaxie/beego/server/web" -) - -// Config is the main struct for BConfig -type Config web.Config - -// Listen holds for http and https related config -type Listen web.Listen - -// WebConfig holds web related config -type WebConfig web.WebConfig - -// SessionConfig holds session related config -type SessionConfig web.SessionConfig - -// LogConfig holds Log related config -type LogConfig web.LogConfig - -var ( - // BConfig is the default config for Application - BConfig *Config - // AppConfig is the instance of Config, store the config information from file - AppConfig *beegoAppConfig - // AppPath is the absolute path to the app - AppPath string - // GlobalSessions is the instance for the session manager - GlobalSessions *session.Manager - - // appConfigPath is the path to the config files - appConfigPath string - // appConfigProvider is the provider for the config, default is ini - appConfigProvider = "ini" - // WorkPath is the absolute path to project root directory - WorkPath string -) - -func init() { - BConfig = (*Config)(web.BConfig) - AppPath = web.AppPath - - WorkPath = web.WorkPath - - AppConfig = &beegoAppConfig{innerConfig: (newCfg.Configer)(web.AppConfig)} -} - -// LoadAppConfig allow developer to apply a config file -func LoadAppConfig(adapterName, configPath string) error { - return web.LoadAppConfig(adapterName, configPath) -} - -type beegoAppConfig struct { - innerConfig newCfg.Configer -} - -func (b *beegoAppConfig) Set(key, val string) error { - if err := b.innerConfig.Set(BConfig.RunMode+"::"+key, val); err != nil { - return b.innerConfig.Set(key, val) - } - return nil -} - -func (b *beegoAppConfig) String(key string) string { - if v, err := b.innerConfig.String(BConfig.RunMode + "::" + key); v != "" && err != nil { - return v - } - res, _ := b.innerConfig.String(key) - return res -} - -func (b *beegoAppConfig) Strings(key string) []string { - if v, err := b.innerConfig.Strings(BConfig.RunMode + "::" + key); len(v) > 0 && err != nil { - return v - } - res, _ := b.innerConfig.Strings(key) - return res -} - -func (b *beegoAppConfig) Int(key string) (int, error) { - if v, err := b.innerConfig.Int(BConfig.RunMode + "::" + key); err == nil { - return v, nil - } - return b.innerConfig.Int(key) -} - -func (b *beegoAppConfig) Int64(key string) (int64, error) { - if v, err := b.innerConfig.Int64(BConfig.RunMode + "::" + key); err == nil { - return v, nil - } - return b.innerConfig.Int64(key) -} - -func (b *beegoAppConfig) Bool(key string) (bool, error) { - if v, err := b.innerConfig.Bool(BConfig.RunMode + "::" + key); err == nil { - return v, nil - } - return b.innerConfig.Bool(key) -} - -func (b *beegoAppConfig) Float(key string) (float64, error) { - if v, err := b.innerConfig.Float(BConfig.RunMode + "::" + key); err == nil { - return v, nil - } - return b.innerConfig.Float(key) -} - -func (b *beegoAppConfig) DefaultString(key string, defaultVal string) string { - if v := b.String(key); v != "" { - return v - } - return defaultVal -} - -func (b *beegoAppConfig) DefaultStrings(key string, defaultVal []string) []string { - if v := b.Strings(key); len(v) != 0 { - return v - } - return defaultVal -} - -func (b *beegoAppConfig) DefaultInt(key string, defaultVal int) int { - if v, err := b.Int(key); err == nil { - return v - } - return defaultVal -} - -func (b *beegoAppConfig) DefaultInt64(key string, defaultVal int64) int64 { - if v, err := b.Int64(key); err == nil { - return v - } - return defaultVal -} - -func (b *beegoAppConfig) DefaultBool(key string, defaultVal bool) bool { - if v, err := b.Bool(key); err == nil { - return v - } - return defaultVal -} - -func (b *beegoAppConfig) DefaultFloat(key string, defaultVal float64) float64 { - if v, err := b.Float(key); err == nil { - return v - } - return defaultVal -} - -func (b *beegoAppConfig) DIY(key string) (interface{}, error) { - return b.innerConfig.DIY(key) -} - -func (b *beegoAppConfig) GetSection(section string) (map[string]string, error) { - return b.innerConfig.GetSection(section) -} - -func (b *beegoAppConfig) SaveConfigFile(filename string) error { - return b.innerConfig.SaveConfigFile(filename) -} diff --git a/adapter/config/adapter.go b/adapter/config/adapter.go deleted file mode 100644 index 0a9e1d0c..00000000 --- a/adapter/config/adapter.go +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 config - -import ( - "github.com/pkg/errors" - - "github.com/astaxie/beego/core/config" -) - -type newToOldConfigerAdapter struct { - delegate config.Configer -} - -func (c *newToOldConfigerAdapter) Set(key, val string) error { - return c.delegate.Set(key, val) -} - -func (c *newToOldConfigerAdapter) String(key string) string { - res, _ := c.delegate.String(key) - return res -} - -func (c *newToOldConfigerAdapter) Strings(key string) []string { - res, _ := c.delegate.Strings(key) - return res -} - -func (c *newToOldConfigerAdapter) Int(key string) (int, error) { - return c.delegate.Int(key) -} - -func (c *newToOldConfigerAdapter) Int64(key string) (int64, error) { - return c.delegate.Int64(key) -} - -func (c *newToOldConfigerAdapter) Bool(key string) (bool, error) { - return c.delegate.Bool(key) -} - -func (c *newToOldConfigerAdapter) Float(key string) (float64, error) { - return c.delegate.Float(key) -} - -func (c *newToOldConfigerAdapter) DefaultString(key string, defaultVal string) string { - return c.delegate.DefaultString(key, defaultVal) -} - -func (c *newToOldConfigerAdapter) DefaultStrings(key string, defaultVal []string) []string { - return c.delegate.DefaultStrings(key, defaultVal) -} - -func (c *newToOldConfigerAdapter) DefaultInt(key string, defaultVal int) int { - return c.delegate.DefaultInt(key, defaultVal) -} - -func (c *newToOldConfigerAdapter) DefaultInt64(key string, defaultVal int64) int64 { - return c.delegate.DefaultInt64(key, defaultVal) -} - -func (c *newToOldConfigerAdapter) DefaultBool(key string, defaultVal bool) bool { - return c.delegate.DefaultBool(key, defaultVal) -} - -func (c *newToOldConfigerAdapter) DefaultFloat(key string, defaultVal float64) float64 { - return c.delegate.DefaultFloat(key, defaultVal) -} - -func (c *newToOldConfigerAdapter) DIY(key string) (interface{}, error) { - return c.delegate.DIY(key) -} - -func (c *newToOldConfigerAdapter) GetSection(section string) (map[string]string, error) { - return c.delegate.GetSection(section) -} - -func (c *newToOldConfigerAdapter) SaveConfigFile(filename string) error { - return c.delegate.SaveConfigFile(filename) -} - -type oldToNewConfigerAdapter struct { - delegate Configer -} - -func (o *oldToNewConfigerAdapter) Set(key, val string) error { - return o.delegate.Set(key, val) -} - -func (o *oldToNewConfigerAdapter) String(key string) (string, error) { - return o.delegate.String(key), nil -} - -func (o *oldToNewConfigerAdapter) Strings(key string) ([]string, error) { - return o.delegate.Strings(key), nil -} - -func (o *oldToNewConfigerAdapter) Int(key string) (int, error) { - return o.delegate.Int(key) -} - -func (o *oldToNewConfigerAdapter) Int64(key string) (int64, error) { - return o.delegate.Int64(key) -} - -func (o *oldToNewConfigerAdapter) Bool(key string) (bool, error) { - return o.delegate.Bool(key) -} - -func (o *oldToNewConfigerAdapter) Float(key string) (float64, error) { - return o.delegate.Float(key) -} - -func (o *oldToNewConfigerAdapter) DefaultString(key string, defaultVal string) string { - return o.delegate.DefaultString(key, defaultVal) -} - -func (o *oldToNewConfigerAdapter) DefaultStrings(key string, defaultVal []string) []string { - return o.delegate.DefaultStrings(key, defaultVal) -} - -func (o *oldToNewConfigerAdapter) DefaultInt(key string, defaultVal int) int { - return o.delegate.DefaultInt(key, defaultVal) -} - -func (o *oldToNewConfigerAdapter) DefaultInt64(key string, defaultVal int64) int64 { - return o.delegate.DefaultInt64(key, defaultVal) -} - -func (o *oldToNewConfigerAdapter) DefaultBool(key string, defaultVal bool) bool { - return o.delegate.DefaultBool(key, defaultVal) -} - -func (o *oldToNewConfigerAdapter) DefaultFloat(key string, defaultVal float64) float64 { - return o.delegate.DefaultFloat(key, defaultVal) -} - -func (o *oldToNewConfigerAdapter) DIY(key string) (interface{}, error) { - return o.delegate.DIY(key) -} - -func (o *oldToNewConfigerAdapter) GetSection(section string) (map[string]string, error) { - return o.delegate.GetSection(section) -} - -func (o *oldToNewConfigerAdapter) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error { - return errors.New("unsupported operation, please use actual config.Configer") -} - -func (o *oldToNewConfigerAdapter) Sub(key string) (config.Configer, error) { - return nil, errors.New("unsupported operation, please use actual config.Configer") -} - -func (o *oldToNewConfigerAdapter) OnChange(key string, fn func(value string)) { - // do nothing -} - -func (o *oldToNewConfigerAdapter) SaveConfigFile(filename string) error { - return o.delegate.SaveConfigFile(filename) -} - -type oldToNewConfigAdapter struct { - delegate Config -} - -func (o *oldToNewConfigAdapter) Parse(key string) (config.Configer, error) { - old, err := o.delegate.Parse(key) - if err != nil { - return nil, err - } - return &oldToNewConfigerAdapter{delegate: old}, nil -} - -func (o *oldToNewConfigAdapter) ParseData(data []byte) (config.Configer, error) { - old, err := o.delegate.ParseData(data) - if err != nil { - return nil, err - } - return &oldToNewConfigerAdapter{delegate: old}, nil -} diff --git a/adapter/config/config.go b/adapter/config/config.go deleted file mode 100644 index 703555cd..00000000 --- a/adapter/config/config.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 config is used to parse config. -// Usage: -// import "github.com/astaxie/beego/config" -// Examples. -// -// cnf, err := config.NewConfig("ini", "config.conf") -// -// cnf APIS: -// -// cnf.Set(key, val string) error -// cnf.String(key string) string -// cnf.Strings(key string) []string -// cnf.Int(key string) (int, error) -// cnf.Int64(key string) (int64, error) -// cnf.Bool(key string) (bool, error) -// cnf.Float(key string) (float64, error) -// cnf.DefaultString(key string, defaultVal string) string -// cnf.DefaultStrings(key string, defaultVal []string) []string -// cnf.DefaultInt(key string, defaultVal int) int -// cnf.DefaultInt64(key string, defaultVal int64) int64 -// cnf.DefaultBool(key string, defaultVal bool) bool -// cnf.DefaultFloat(key string, defaultVal float64) float64 -// cnf.DIY(key string) (interface{}, error) -// cnf.GetSection(section string) (map[string]string, error) -// cnf.SaveConfigFile(filename string) error -// More docs http://beego.me/docs/module/config.md -package config - -import ( - "github.com/astaxie/beego/core/config" -) - -// Configer defines how to get and set value from configuration raw data. -type Configer interface { - Set(key, val string) error // support section::key type in given key when using ini type. - String(key string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. - Strings(key string) []string // get string slice - Int(key string) (int, error) - Int64(key string) (int64, error) - Bool(key string) (bool, error) - Float(key string) (float64, error) - DefaultString(key string, defaultVal string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. - DefaultStrings(key string, defaultVal []string) []string // get string slice - DefaultInt(key string, defaultVal int) int - DefaultInt64(key string, defaultVal int64) int64 - DefaultBool(key string, defaultVal bool) bool - DefaultFloat(key string, defaultVal float64) float64 - DIY(key string) (interface{}, error) - GetSection(section string) (map[string]string, error) - SaveConfigFile(filename string) error -} - -// Config is the adapter interface for parsing config file to get raw data to Configer. -type Config interface { - Parse(key string) (Configer, error) - ParseData(data []byte) (Configer, error) -} - -var adapters = make(map[string]Config) - -// Register makes a config adapter available by the adapter name. -// If Register is called twice with the same name or if driver is nil, -// it panics. -func Register(name string, adapter Config) { - config.Register(name, &oldToNewConfigAdapter{delegate: adapter}) -} - -// NewConfig adapterName is ini/json/xml/yaml. -// filename is the config file path. -func NewConfig(adapterName, filename string) (Configer, error) { - cfg, err := config.NewConfig(adapterName, filename) - if err != nil { - return nil, err - } - - // it was registered by using Register method - res, ok := cfg.(*oldToNewConfigerAdapter) - if ok { - return res.delegate, nil - } - - return &newToOldConfigerAdapter{ - delegate: cfg, - }, nil -} - -// NewConfigData adapterName is ini/json/xml/yaml. -// data is the config data. -func NewConfigData(adapterName string, data []byte) (Configer, error) { - cfg, err := config.NewConfigData(adapterName, data) - if err != nil { - return nil, err - } - - // it was registered by using Register method - res, ok := cfg.(*oldToNewConfigerAdapter) - if ok { - return res.delegate, nil - } - - return &newToOldConfigerAdapter{ - delegate: cfg, - }, nil -} - -// ExpandValueEnvForMap convert all string value with environment variable. -func ExpandValueEnvForMap(m map[string]interface{}) map[string]interface{} { - return config.ExpandValueEnvForMap(m) -} - -// ExpandValueEnv returns value of convert with environment variable. -// -// Return environment variable if value start with "${" and end with "}". -// Return default value if environment variable is empty or not exist. -// -// It accept value formats "${env}" , "${env||}}" , "${env||defaultValue}" , "defaultvalue". -// Examples: -// v1 := config.ExpandValueEnv("${GOPATH}") // return the GOPATH environment variable. -// v2 := config.ExpandValueEnv("${GOAsta||/usr/local/go}") // return the default value "/usr/local/go/". -// v3 := config.ExpandValueEnv("Astaxie") // return the value "Astaxie". -func ExpandValueEnv(value string) string { - return config.ExpandValueEnv(value) -} - -// ParseBool returns the boolean value represented by the string. -// -// It accepts 1, 1.0, t, T, TRUE, true, True, YES, yes, Yes,Y, y, ON, on, On, -// 0, 0.0, f, F, FALSE, false, False, NO, no, No, N,n, OFF, off, Off. -// Any other value returns an error. -func ParseBool(val interface{}) (value bool, err error) { - return config.ParseBool(val) -} - -// ToString converts values of any type to string. -func ToString(x interface{}) string { - return config.ToString(x) -} diff --git a/adapter/config/env/env.go b/adapter/config/env/env.go deleted file mode 100644 index 839c60c1..00000000 --- a/adapter/config/env/env.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// Copyright 2017 Faissal Elamraoui. All Rights Reserved. -// -// Licensed 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 env is used to parse environment. -package env - -import ( - "github.com/astaxie/beego/core/config/env" -) - -// Get returns a value by key. -// If the key does not exist, the default value will be returned. -func Get(key string, defVal string) string { - return env.Get(key, defVal) -} - -// MustGet returns a value by key. -// If the key does not exist, it will return an error. -func MustGet(key string) (string, error) { - return env.MustGet(key) -} - -// Set sets a value in the ENV copy. -// This does not affect the child process environment. -func Set(key string, value string) { - env.Set(key, value) -} - -// MustSet sets a value in the ENV copy and the child process environment. -// It returns an error in case the set operation failed. -func MustSet(key string, value string) error { - return env.MustSet(key, value) -} - -// GetAll returns all keys/values in the current child process environment. -func GetAll() map[string]string { - return env.GetAll() -} diff --git a/adapter/config/fake.go b/adapter/config/fake.go deleted file mode 100644 index 050f0252..00000000 --- a/adapter/config/fake.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 config - -import ( - "github.com/astaxie/beego/core/config" -) - -// NewFakeConfig return a fake Configer -func NewFakeConfig() Configer { - new := config.NewFakeConfig() - return &newToOldConfigerAdapter{delegate: new} -} diff --git a/adapter/config/xml/xml.go b/adapter/config/xml/xml.go deleted file mode 100644 index 28d5f44e..00000000 --- a/adapter/config/xml/xml.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 xml for config provider. -// -// depend on github.com/beego/x2j. -// -// go install github.com/beego/x2j. -// -// Usage: -// import( -// _ "github.com/astaxie/beego/config/xml" -// "github.com/astaxie/beego/config" -// ) -// -// cnf, err := config.NewConfig("xml", "config.xml") -// -// More docs http://beego.me/docs/module/config.md -package xml - -import ( - _ "github.com/astaxie/beego/core/config/xml" -) diff --git a/adapter/config/yaml/yaml.go b/adapter/config/yaml/yaml.go deleted file mode 100644 index 196c9725..00000000 --- a/adapter/config/yaml/yaml.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 yaml for config provider -// -// depend on github.com/beego/goyaml2 -// -// go install github.com/beego/goyaml2 -// -// Usage: -// import( -// _ "github.com/astaxie/beego/config/yaml" -// "github.com/astaxie/beego/config" -// ) -// -// cnf, err := config.NewConfig("yaml", "config.yaml") -// -// More docs http://beego.me/docs/module/config.md -package yaml - -import ( - _ "github.com/astaxie/beego/core/config/yaml" -) diff --git a/adapter/context/acceptencoder.go b/adapter/context/acceptencoder.go deleted file mode 100644 index 4bfef95e..00000000 --- a/adapter/context/acceptencoder.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2015 beego Author. All Rights Reserved. -// -// Licensed 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 context - -import ( - "io" - "net/http" - "os" - - "github.com/astaxie/beego/server/web/context" -) - -// InitGzip init the gzipcompress -func InitGzip(minLength, compressLevel int, methods []string) { - context.InitGzip(minLength, compressLevel, methods) -} - -// WriteFile reads from file and writes to writer by the specific encoding(gzip/deflate) -func WriteFile(encoding string, writer io.Writer, file *os.File) (bool, string, error) { - return context.WriteFile(encoding, writer, file) -} - -// WriteBody reads writes content to writer by the specific encoding(gzip/deflate) -func WriteBody(encoding string, writer io.Writer, content []byte) (bool, string, error) { - return context.WriteBody(encoding, writer, content) -} - -// ParseEncoding will extract the right encoding for response -// the Accept-Encoding's sec is here: -// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3 -func ParseEncoding(r *http.Request) string { - return context.ParseEncoding(r) -} diff --git a/adapter/context/context.go b/adapter/context/context.go deleted file mode 100644 index 123fdb2c..00000000 --- a/adapter/context/context.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 context provide the context utils -// Usage: -// -// import "github.com/astaxie/beego/context" -// -// ctx := context.Context{Request:req,ResponseWriter:rw} -// -// more docs http://beego.me/docs/module/context.md -package context - -import ( - "bufio" - "net" - "net/http" - - "github.com/astaxie/beego/server/web/context" -) - -// commonly used mime-types -const ( - ApplicationJSON = context.ApplicationJSON - ApplicationXML = context.ApplicationXML - ApplicationYAML = context.ApplicationYAML - TextXML = context.TextXML -) - -// NewContext return the Context with Input and Output -func NewContext() *Context { - return (*Context)(context.NewContext()) -} - -// Context Http request context struct including BeegoInput, BeegoOutput, http.Request and http.ResponseWriter. -// BeegoInput and BeegoOutput provides some api to operate request and response more easily. -type Context context.Context - -// Reset init Context, BeegoInput and BeegoOutput -func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) { - (*context.Context)(ctx).Reset(rw, r) -} - -// Redirect does redirection to localurl with http header status code. -func (ctx *Context) Redirect(status int, localurl string) { - (*context.Context)(ctx).Redirect(status, localurl) -} - -// Abort stops this request. -// if beego.ErrorMaps exists, panic body. -func (ctx *Context) Abort(status int, body string) { - (*context.Context)(ctx).Abort(status, body) -} - -// WriteString Write string to response body. -// it sends response body. -func (ctx *Context) WriteString(content string) { - (*context.Context)(ctx).WriteString(content) -} - -// GetCookie Get cookie from request by a given key. -// It's alias of BeegoInput.Cookie. -func (ctx *Context) GetCookie(key string) string { - return (*context.Context)(ctx).GetCookie(key) -} - -// SetCookie Set cookie for response. -// It's alias of BeegoOutput.Cookie. -func (ctx *Context) SetCookie(name string, value string, others ...interface{}) { - (*context.Context)(ctx).SetCookie(name, value, others) -} - -// GetSecureCookie Get secure cookie from request by a given key. -func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) { - return (*context.Context)(ctx).GetSecureCookie(Secret, key) -} - -// SetSecureCookie Set Secure cookie for response. -func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) { - (*context.Context)(ctx).SetSecureCookie(Secret, name, value, others) -} - -// XSRFToken creates a xsrf token string and returns. -func (ctx *Context) XSRFToken(key string, expire int64) string { - return (*context.Context)(ctx).XSRFToken(key, expire) -} - -// CheckXSRFCookie checks xsrf token in this request is valid or not. -// the token can provided in request header "X-Xsrftoken" and "X-CsrfToken" -// or in form field value named as "_xsrf". -func (ctx *Context) CheckXSRFCookie() bool { - return (*context.Context)(ctx).CheckXSRFCookie() -} - -// RenderMethodResult renders the return value of a controller method to the output -func (ctx *Context) RenderMethodResult(result interface{}) { - (*context.Context)(ctx).RenderMethodResult(result) -} - -// Response is a wrapper for the http.ResponseWriter -// started set to true if response was written to then don't execute other handler -type Response context.Response - -// Write writes the data to the connection as part of an HTTP reply, -// and sets `started` to true. -// started means the response has sent out. -func (r *Response) Write(p []byte) (int, error) { - return (*context.Response)(r).Write(p) -} - -// WriteHeader sends an HTTP response header with status code, -// and sets `started` to true. -func (r *Response) WriteHeader(code int) { - (*context.Response)(r).WriteHeader(code) -} - -// Hijack hijacker for http -func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { - return (*context.Response)(r).Hijack() -} - -// Flush http.Flusher -func (r *Response) Flush() { - (*context.Response)(r).Flush() -} - -// CloseNotify http.CloseNotifier -func (r *Response) CloseNotify() <-chan bool { - return (*context.Response)(r).CloseNotify() -} - -// Pusher http.Pusher -func (r *Response) Pusher() (pusher http.Pusher) { - return (*context.Response)(r).Pusher() -} diff --git a/adapter/context/input.go b/adapter/context/input.go deleted file mode 100644 index 51bb9ea5..00000000 --- a/adapter/context/input.go +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 context - -import ( - "github.com/astaxie/beego/server/web/context" -) - -// BeegoInput operates the http request header, data, cookie and body. -// it also contains router params and current session. -type BeegoInput context.BeegoInput - -// NewInput return BeegoInput generated by Context. -func NewInput() *BeegoInput { - return (*BeegoInput)(context.NewInput()) -} - -// Reset init the BeegoInput -func (input *BeegoInput) Reset(ctx *Context) { - (*context.BeegoInput)(input).Reset((*context.Context)(ctx)) -} - -// Protocol returns request protocol name, such as HTTP/1.1 . -func (input *BeegoInput) Protocol() string { - return (*context.BeegoInput)(input).Protocol() -} - -// URI returns full request url with query string, fragment. -func (input *BeegoInput) URI() string { - return input.Context.Request.RequestURI -} - -// URL returns request url path (without query string, fragment). -func (input *BeegoInput) URL() string { - return (*context.BeegoInput)(input).URL() -} - -// Site returns base site url as scheme://domain type. -func (input *BeegoInput) Site() string { - return (*context.BeegoInput)(input).Site() -} - -// Scheme returns request scheme as "http" or "https". -func (input *BeegoInput) Scheme() string { - return (*context.BeegoInput)(input).Scheme() -} - -// Domain returns host name. -// Alias of Host method. -func (input *BeegoInput) Domain() string { - return (*context.BeegoInput)(input).Domain() -} - -// Host returns host name. -// if no host info in request, return localhost. -func (input *BeegoInput) Host() string { - return (*context.BeegoInput)(input).Host() -} - -// Method returns http request method. -func (input *BeegoInput) Method() string { - return (*context.BeegoInput)(input).Method() -} - -// Is returns boolean of this request is on given method, such as Is("POST"). -func (input *BeegoInput) Is(method string) bool { - return (*context.BeegoInput)(input).Is(method) -} - -// IsGet Is this a GET method request? -func (input *BeegoInput) IsGet() bool { - return (*context.BeegoInput)(input).IsGet() -} - -// IsPost Is this a POST method request? -func (input *BeegoInput) IsPost() bool { - return (*context.BeegoInput)(input).IsPost() -} - -// IsHead Is this a Head method request? -func (input *BeegoInput) IsHead() bool { - return (*context.BeegoInput)(input).IsHead() -} - -// IsOptions Is this a OPTIONS method request? -func (input *BeegoInput) IsOptions() bool { - return (*context.BeegoInput)(input).IsOptions() -} - -// IsPut Is this a PUT method request? -func (input *BeegoInput) IsPut() bool { - return (*context.BeegoInput)(input).IsPut() -} - -// IsDelete Is this a DELETE method request? -func (input *BeegoInput) IsDelete() bool { - return (*context.BeegoInput)(input).IsDelete() -} - -// IsPatch Is this a PATCH method request? -func (input *BeegoInput) IsPatch() bool { - return (*context.BeegoInput)(input).IsPatch() -} - -// IsAjax returns boolean of this request is generated by ajax. -func (input *BeegoInput) IsAjax() bool { - return (*context.BeegoInput)(input).IsAjax() -} - -// IsSecure returns boolean of this request is in https. -func (input *BeegoInput) IsSecure() bool { - return (*context.BeegoInput)(input).IsSecure() -} - -// IsWebsocket returns boolean of this request is in webSocket. -func (input *BeegoInput) IsWebsocket() bool { - return (*context.BeegoInput)(input).IsWebsocket() -} - -// IsUpload returns boolean of whether file uploads in this request or not.. -func (input *BeegoInput) IsUpload() bool { - return (*context.BeegoInput)(input).IsUpload() -} - -// AcceptsHTML Checks if request accepts html response -func (input *BeegoInput) AcceptsHTML() bool { - return (*context.BeegoInput)(input).AcceptsHTML() -} - -// AcceptsXML Checks if request accepts xml response -func (input *BeegoInput) AcceptsXML() bool { - return (*context.BeegoInput)(input).AcceptsXML() -} - -// AcceptsJSON Checks if request accepts json response -func (input *BeegoInput) AcceptsJSON() bool { - return (*context.BeegoInput)(input).AcceptsJSON() -} - -// AcceptsYAML Checks if request accepts json response -func (input *BeegoInput) AcceptsYAML() bool { - return (*context.BeegoInput)(input).AcceptsYAML() -} - -// IP returns request client ip. -// if in proxy, return first proxy id. -// if error, return RemoteAddr. -func (input *BeegoInput) IP() string { - return (*context.BeegoInput)(input).IP() -} - -// Proxy returns proxy client ips slice. -func (input *BeegoInput) Proxy() []string { - return (*context.BeegoInput)(input).Proxy() -} - -// Referer returns http referer header. -func (input *BeegoInput) Referer() string { - return (*context.BeegoInput)(input).Referer() -} - -// Refer returns http referer header. -func (input *BeegoInput) Refer() string { - return (*context.BeegoInput)(input).Refer() -} - -// SubDomains returns sub domain string. -// if aa.bb.domain.com, returns aa.bb . -func (input *BeegoInput) SubDomains() string { - return (*context.BeegoInput)(input).SubDomains() -} - -// Port returns request client port. -// when error or empty, return 80. -func (input *BeegoInput) Port() int { - return (*context.BeegoInput)(input).Port() -} - -// UserAgent returns request client user agent string. -func (input *BeegoInput) UserAgent() string { - return (*context.BeegoInput)(input).UserAgent() -} - -// ParamsLen return the length of the params -func (input *BeegoInput) ParamsLen() int { - return (*context.BeegoInput)(input).ParamsLen() -} - -// Param returns router param by a given key. -func (input *BeegoInput) Param(key string) string { - return (*context.BeegoInput)(input).Param(key) -} - -// Params returns the map[key]value. -func (input *BeegoInput) Params() map[string]string { - return (*context.BeegoInput)(input).Params() -} - -// SetParam will set the param with key and value -func (input *BeegoInput) SetParam(key, val string) { - (*context.BeegoInput)(input).SetParam(key, val) -} - -// ResetParams clears any of the input's Params -// This function is used to clear parameters so they may be reset between filter -// passes. -func (input *BeegoInput) ResetParams() { - (*context.BeegoInput)(input).ResetParams() -} - -// Query returns input data item string by a given string. -func (input *BeegoInput) Query(key string) string { - return (*context.BeegoInput)(input).Query(key) -} - -// Header returns request header item string by a given string. -// if non-existed, return empty string. -func (input *BeegoInput) Header(key string) string { - return (*context.BeegoInput)(input).Header(key) -} - -// Cookie returns request cookie item string by a given key. -// if non-existed, return empty string. -func (input *BeegoInput) Cookie(key string) string { - return (*context.BeegoInput)(input).Cookie(key) -} - -// Session returns current session item value by a given key. -// if non-existed, return nil. -func (input *BeegoInput) Session(key interface{}) interface{} { - return (*context.BeegoInput)(input).Session(key) -} - -// CopyBody returns the raw request body data as bytes. -func (input *BeegoInput) CopyBody(MaxMemory int64) []byte { - return (*context.BeegoInput)(input).CopyBody(MaxMemory) -} - -// Data return the implicit data in the input -func (input *BeegoInput) Data() map[interface{}]interface{} { - return (*context.BeegoInput)(input).Data() -} - -// GetData returns the stored data in this context. -func (input *BeegoInput) GetData(key interface{}) interface{} { - return (*context.BeegoInput)(input).GetData(key) -} - -// SetData stores data with given key in this context. -// This data are only available in this context. -func (input *BeegoInput) SetData(key, val interface{}) { - (*context.BeegoInput)(input).SetData(key, val) -} - -// ParseFormOrMulitForm parseForm or parseMultiForm based on Content-type -func (input *BeegoInput) ParseFormOrMulitForm(maxMemory int64) error { - return (*context.BeegoInput)(input).ParseFormOrMultiForm(maxMemory) -} - -// Bind data from request.Form[key] to dest -// like /?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie -// var id int beegoInput.Bind(&id, "id") id ==123 -// var isok bool beegoInput.Bind(&isok, "isok") isok ==true -// var ft float64 beegoInput.Bind(&ft, "ft") ft ==1.2 -// ol := make([]int, 0, 2) beegoInput.Bind(&ol, "ol") ol ==[1 2] -// ul := make([]string, 0, 2) beegoInput.Bind(&ul, "ul") ul ==[str array] -// user struct{Name} beegoInput.Bind(&user, "user") user == {Name:"astaxie"} -func (input *BeegoInput) Bind(dest interface{}, key string) error { - return (*context.BeegoInput)(input).Bind(dest, key) -} diff --git a/adapter/context/output.go b/adapter/context/output.go deleted file mode 100644 index 0223679b..00000000 --- a/adapter/context/output.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 context - -import ( - "github.com/astaxie/beego/server/web/context" -) - -// BeegoOutput does work for sending response header. -type BeegoOutput context.BeegoOutput - -// NewOutput returns new BeegoOutput. -// it contains nothing now. -func NewOutput() *BeegoOutput { - return (*BeegoOutput)(context.NewOutput()) -} - -// Reset init BeegoOutput -func (output *BeegoOutput) Reset(ctx *Context) { - (*context.BeegoOutput)(output).Reset((*context.Context)(ctx)) -} - -// Header sets response header item string via given key. -func (output *BeegoOutput) Header(key, val string) { - (*context.BeegoOutput)(output).Header(key, val) -} - -// Body sets response body content. -// if EnableGzip, compress content string. -// it sends out response body directly. -func (output *BeegoOutput) Body(content []byte) error { - return (*context.BeegoOutput)(output).Body(content) -} - -// Cookie sets cookie value via given key. -// others are ordered as cookie's max age time, path,domain, secure and httponly. -func (output *BeegoOutput) Cookie(name string, value string, others ...interface{}) { - (*context.BeegoOutput)(output).Cookie(name, value, others) -} - -// JSON writes json to response body. -// if encoding is true, it converts utf-8 to \u0000 type. -func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) error { - return (*context.BeegoOutput)(output).JSON(data, hasIndent, encoding) -} - -// YAML writes yaml to response body. -func (output *BeegoOutput) YAML(data interface{}) error { - return (*context.BeegoOutput)(output).YAML(data) -} - -// JSONP writes jsonp to response body. -func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error { - return (*context.BeegoOutput)(output).JSONP(data, hasIndent) -} - -// XML writes xml string to response body. -func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error { - return (*context.BeegoOutput)(output).XML(data, hasIndent) -} - -// ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header -func (output *BeegoOutput) ServeFormatted(data interface{}, hasIndent bool, hasEncode ...bool) { - (*context.BeegoOutput)(output).ServeFormatted(data, hasIndent, hasEncode...) -} - -// Download forces response for download file. -// it prepares the download response header automatically. -func (output *BeegoOutput) Download(file string, filename ...string) { - (*context.BeegoOutput)(output).Download(file, filename...) -} - -// ContentType sets the content type from ext string. -// MIME type is given in mime package. -func (output *BeegoOutput) ContentType(ext string) { - (*context.BeegoOutput)(output).ContentType(ext) -} - -// SetStatus sets response status code. -// It writes response header directly. -func (output *BeegoOutput) SetStatus(status int) { - (*context.BeegoOutput)(output).SetStatus(status) -} - -// IsCachable returns boolean of this request is cached. -// HTTP 304 means cached. -func (output *BeegoOutput) IsCachable() bool { - return (*context.BeegoOutput)(output).IsCachable() -} - -// IsEmpty returns boolean of this request is empty. -// HTTP 201,204 and 304 means empty. -func (output *BeegoOutput) IsEmpty() bool { - return (*context.BeegoOutput)(output).IsEmpty() -} - -// IsOk returns boolean of this request runs well. -// HTTP 200 means ok. -func (output *BeegoOutput) IsOk() bool { - return (*context.BeegoOutput)(output).IsOk() -} - -// IsSuccessful returns boolean of this request runs successfully. -// HTTP 2xx means ok. -func (output *BeegoOutput) IsSuccessful() bool { - return (*context.BeegoOutput)(output).IsSuccessful() -} - -// IsRedirect returns boolean of this request is redirection header. -// HTTP 301,302,307 means redirection. -func (output *BeegoOutput) IsRedirect() bool { - return (*context.BeegoOutput)(output).IsRedirect() -} - -// IsForbidden returns boolean of this request is forbidden. -// HTTP 403 means forbidden. -func (output *BeegoOutput) IsForbidden() bool { - return (*context.BeegoOutput)(output).IsForbidden() -} - -// IsNotFound returns boolean of this request is not found. -// HTTP 404 means not found. -func (output *BeegoOutput) IsNotFound() bool { - return (*context.BeegoOutput)(output).IsNotFound() -} - -// IsClientError returns boolean of this request client sends error data. -// HTTP 4xx means client error. -func (output *BeegoOutput) IsClientError() bool { - return (*context.BeegoOutput)(output).IsClientError() -} - -// IsServerError returns boolean of this server handler errors. -// HTTP 5xx means server internal error. -func (output *BeegoOutput) IsServerError() bool { - return (*context.BeegoOutput)(output).IsServerError() -} - -// Session sets session item value with given key. -func (output *BeegoOutput) Session(name interface{}, value interface{}) { - (*context.BeegoOutput)(output).Session(name, value) -} diff --git a/adapter/context/renderer.go b/adapter/context/renderer.go deleted file mode 100644 index 1309365a..00000000 --- a/adapter/context/renderer.go +++ /dev/null @@ -1,8 +0,0 @@ -package context - -import ( - "github.com/astaxie/beego/server/web/context" -) - -// Renderer defines an http response renderer -type Renderer context.Renderer diff --git a/adapter/controller.go b/adapter/controller.go deleted file mode 100644 index 14dc9b97..00000000 --- a/adapter/controller.go +++ /dev/null @@ -1,399 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 adapter - -import ( - "mime/multipart" - "net/url" - - "github.com/astaxie/beego/adapter/session" - webContext "github.com/astaxie/beego/server/web/context" - - "github.com/astaxie/beego/server/web" -) - -var ( - // ErrAbort custom error when user stop request handler manually. - ErrAbort = web.ErrAbort - // GlobalControllerRouter store comments with controller. pkgpath+controller:comments - GlobalControllerRouter = web.GlobalControllerRouter -) - -// ControllerFilter store the filter for controller -type ControllerFilter web.ControllerFilter - -// ControllerFilterComments store the comment for controller level filter -type ControllerFilterComments web.ControllerFilterComments - -// ControllerImportComments store the import comment for controller needed -type ControllerImportComments web.ControllerImportComments - -// ControllerComments store the comment for the controller method -type ControllerComments web.ControllerComments - -// ControllerCommentsSlice implements the sort interface -type ControllerCommentsSlice web.ControllerCommentsSlice - -func (p ControllerCommentsSlice) Len() int { - return (web.ControllerCommentsSlice)(p).Len() -} -func (p ControllerCommentsSlice) Less(i, j int) bool { - return (web.ControllerCommentsSlice)(p).Less(i, j) -} -func (p ControllerCommentsSlice) Swap(i, j int) { - (web.ControllerCommentsSlice)(p).Swap(i, j) -} - -// Controller defines some basic http request handler operations, such as -// http context, template and view, session and xsrf. -type Controller web.Controller - -func (c *Controller) Init(ctx *webContext.Context, controllerName, actionName string, app interface{}) { - (*web.Controller)(c).Init(ctx, controllerName, actionName, app) -} - -// ControllerInterface is an interface to uniform all controller handler. -type ControllerInterface web.ControllerInterface - -// Prepare runs after Init before request function execution. -func (c *Controller) Prepare() { - (*web.Controller)(c).Prepare() -} - -// Finish runs after request function execution. -func (c *Controller) Finish() { - (*web.Controller)(c).Finish() -} - -// Get adds a request function to handle GET request. -func (c *Controller) Get() { - (*web.Controller)(c).Get() -} - -// Post adds a request function to handle POST request. -func (c *Controller) Post() { - (*web.Controller)(c).Post() -} - -// Delete adds a request function to handle DELETE request. -func (c *Controller) Delete() { - (*web.Controller)(c).Delete() -} - -// Put adds a request function to handle PUT request. -func (c *Controller) Put() { - (*web.Controller)(c).Put() -} - -// Head adds a request function to handle HEAD request. -func (c *Controller) Head() { - (*web.Controller)(c).Head() -} - -// Patch adds a request function to handle PATCH request. -func (c *Controller) Patch() { - (*web.Controller)(c).Patch() -} - -// Options adds a request function to handle OPTIONS request. -func (c *Controller) Options() { - (*web.Controller)(c).Options() -} - -// Trace adds a request function to handle Trace request. -// this method SHOULD NOT be overridden. -// https://tools.ietf.org/html/rfc7231#section-4.3.8 -// The TRACE method requests a remote, application-level loop-back of -// the request message. The final recipient of the request SHOULD -// reflect the message received, excluding some fields described below, -// back to the client as the message body of a 200 (OK) response with a -// Content-Type of "message/http" (Section 8.3.1 of [RFC7230]). -func (c *Controller) Trace() { - (*web.Controller)(c).Trace() -} - -// HandlerFunc call function with the name -func (c *Controller) HandlerFunc(fnname string) bool { - return (*web.Controller)(c).HandlerFunc(fnname) -} - -// URLMapping register the internal Controller router. -func (c *Controller) URLMapping() { - (*web.Controller)(c).URLMapping() -} - -// Mapping the method to function -func (c *Controller) Mapping(method string, fn func()) { - (*web.Controller)(c).Mapping(method, fn) -} - -// Render sends the response with rendered template bytes as text/html type. -func (c *Controller) Render() error { - return (*web.Controller)(c).Render() -} - -// RenderString returns the rendered template string. Do not send out response. -func (c *Controller) RenderString() (string, error) { - return (*web.Controller)(c).RenderString() -} - -// RenderBytes returns the bytes of rendered template string. Do not send out response. -func (c *Controller) RenderBytes() ([]byte, error) { - return (*web.Controller)(c).RenderBytes() -} - -// Redirect sends the redirection response to url with status code. -func (c *Controller) Redirect(url string, code int) { - (*web.Controller)(c).Redirect(url, code) -} - -// SetData set the data depending on the accepted -func (c *Controller) SetData(data interface{}) { - (*web.Controller)(c).SetData(data) -} - -// Abort stops controller handler and show the error data if code is defined in ErrorMap or code string. -func (c *Controller) Abort(code string) { - (*web.Controller)(c).Abort(code) -} - -// CustomAbort stops controller handler and show the error data, it's similar Aborts, but support status code and body. -func (c *Controller) CustomAbort(status int, body string) { - (*web.Controller)(c).CustomAbort(status, body) -} - -// StopRun makes panic of USERSTOPRUN error and go to recover function if defined. -func (c *Controller) StopRun() { - (*web.Controller)(c).StopRun() -} - -// URLFor does another controller handler in this request function. -// it goes to this controller method if endpoint is not clear. -func (c *Controller) URLFor(endpoint string, values ...interface{}) string { - return (*web.Controller)(c).URLFor(endpoint, values...) -} - -// ServeJSON sends a json response with encoding charset. -func (c *Controller) ServeJSON(encoding ...bool) { - (*web.Controller)(c).ServeJSON(encoding...) -} - -// ServeJSONP sends a jsonp response. -func (c *Controller) ServeJSONP() { - (*web.Controller)(c).ServeJSONP() -} - -// ServeXML sends xml response. -func (c *Controller) ServeXML() { - (*web.Controller)(c).ServeXML() -} - -// ServeYAML sends yaml response. -func (c *Controller) ServeYAML() { - (*web.Controller)(c).ServeYAML() -} - -// ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header -func (c *Controller) ServeFormatted(encoding ...bool) { - (*web.Controller)(c).ServeFormatted(encoding...) -} - -// Input returns the input data map from POST or PUT request body and query string. -func (c *Controller) Input() url.Values { - return (*web.Controller)(c).Input() -} - -// ParseForm maps input data map to obj struct. -func (c *Controller) ParseForm(obj interface{}) error { - return (*web.Controller)(c).ParseForm(obj) -} - -// GetString returns the input value by key string or the default value while it's present and input is blank -func (c *Controller) GetString(key string, def ...string) string { - return (*web.Controller)(c).GetString(key, def...) -} - -// GetStrings returns the input string slice by key string or the default value while it's present and input is blank -// it's designed for multi-value input field such as checkbox(input[type=checkbox]), multi-selection. -func (c *Controller) GetStrings(key string, def ...[]string) []string { - return (*web.Controller)(c).GetStrings(key, def...) -} - -// GetInt returns input as an int or the default value while it's present and input is blank -func (c *Controller) GetInt(key string, def ...int) (int, error) { - return (*web.Controller)(c).GetInt(key, def...) -} - -// GetInt8 return input as an int8 or the default value while it's present and input is blank -func (c *Controller) GetInt8(key string, def ...int8) (int8, error) { - return (*web.Controller)(c).GetInt8(key, def...) -} - -// GetUint8 return input as an uint8 or the default value while it's present and input is blank -func (c *Controller) GetUint8(key string, def ...uint8) (uint8, error) { - return (*web.Controller)(c).GetUint8(key, def...) -} - -// GetInt16 returns input as an int16 or the default value while it's present and input is blank -func (c *Controller) GetInt16(key string, def ...int16) (int16, error) { - return (*web.Controller)(c).GetInt16(key, def...) -} - -// GetUint16 returns input as an uint16 or the default value while it's present and input is blank -func (c *Controller) GetUint16(key string, def ...uint16) (uint16, error) { - return (*web.Controller)(c).GetUint16(key, def...) -} - -// GetInt32 returns input as an int32 or the default value while it's present and input is blank -func (c *Controller) GetInt32(key string, def ...int32) (int32, error) { - return (*web.Controller)(c).GetInt32(key, def...) -} - -// GetUint32 returns input as an uint32 or the default value while it's present and input is blank -func (c *Controller) GetUint32(key string, def ...uint32) (uint32, error) { - return (*web.Controller)(c).GetUint32(key, def...) -} - -// GetInt64 returns input value as int64 or the default value while it's present and input is blank. -func (c *Controller) GetInt64(key string, def ...int64) (int64, error) { - return (*web.Controller)(c).GetInt64(key, def...) -} - -// GetUint64 returns input value as uint64 or the default value while it's present and input is blank. -func (c *Controller) GetUint64(key string, def ...uint64) (uint64, error) { - return (*web.Controller)(c).GetUint64(key, def...) -} - -// GetBool returns input value as bool or the default value while it's present and input is blank. -func (c *Controller) GetBool(key string, def ...bool) (bool, error) { - return (*web.Controller)(c).GetBool(key, def...) -} - -// GetFloat returns input value as float64 or the default value while it's present and input is blank. -func (c *Controller) GetFloat(key string, def ...float64) (float64, error) { - return (*web.Controller)(c).GetFloat(key, def...) -} - -// GetFile returns the file data in file upload field named as key. -// it returns the first one of multi-uploaded files. -func (c *Controller) GetFile(key string) (multipart.File, *multipart.FileHeader, error) { - return (*web.Controller)(c).GetFile(key) -} - -// GetFiles return multi-upload files -// files, err:=c.GetFiles("myfiles") -// if err != nil { -// http.Error(w, err.Error(), http.StatusNoContent) -// return -// } -// for i, _ := range files { -// //for each fileheader, get a handle to the actual file -// file, err := files[i].Open() -// defer file.Close() -// if err != nil { -// http.Error(w, err.Error(), http.StatusInternalServerError) -// return -// } -// //create destination file making sure the path is writeable. -// dst, err := os.Create("upload/" + files[i].Filename) -// defer dst.Close() -// if err != nil { -// http.Error(w, err.Error(), http.StatusInternalServerError) -// return -// } -// //copy the uploaded file to the destination file -// if _, err := io.Copy(dst, file); err != nil { -// http.Error(w, err.Error(), http.StatusInternalServerError) -// return -// } -// } -func (c *Controller) GetFiles(key string) ([]*multipart.FileHeader, error) { - return (*web.Controller)(c).GetFiles(key) -} - -// SaveToFile saves uploaded file to new path. -// it only operates the first one of mutil-upload form file field. -func (c *Controller) SaveToFile(fromfile, tofile string) error { - return (*web.Controller)(c).SaveToFile(fromfile, tofile) -} - -// StartSession starts session and load old session data info this controller. -func (c *Controller) StartSession() session.Store { - s := (*web.Controller)(c).StartSession() - return session.CreateNewToOldStoreAdapter(s) -} - -// SetSession puts value into session. -func (c *Controller) SetSession(name interface{}, value interface{}) { - (*web.Controller)(c).SetSession(name, value) -} - -// GetSession gets value from session. -func (c *Controller) GetSession(name interface{}) interface{} { - return (*web.Controller)(c).GetSession(name) -} - -// DelSession removes value from session. -func (c *Controller) DelSession(name interface{}) { - (*web.Controller)(c).DelSession(name) -} - -// SessionRegenerateID regenerates session id for this session. -// the session data have no changes. -func (c *Controller) SessionRegenerateID() { - (*web.Controller)(c).SessionRegenerateID() -} - -// DestroySession cleans session data and session cookie. -func (c *Controller) DestroySession() { - (*web.Controller)(c).DestroySession() -} - -// IsAjax returns this request is ajax or not. -func (c *Controller) IsAjax() bool { - return (*web.Controller)(c).IsAjax() -} - -// GetSecureCookie returns decoded cookie value from encoded browser cookie values. -func (c *Controller) GetSecureCookie(Secret, key string) (string, bool) { - return (*web.Controller)(c).GetSecureCookie(Secret, key) -} - -// SetSecureCookie puts value into cookie after encoded the value. -func (c *Controller) SetSecureCookie(Secret, name, value string, others ...interface{}) { - (*web.Controller)(c).SetSecureCookie(Secret, name, value, others...) -} - -// XSRFToken creates a CSRF token string and returns. -func (c *Controller) XSRFToken() string { - return (*web.Controller)(c).XSRFToken() -} - -// CheckXSRFCookie checks xsrf token in this request is valid or not. -// the token can provided in request header "X-Xsrftoken" and "X-CsrfToken" -// or in form field value named as "_xsrf". -func (c *Controller) CheckXSRFCookie() bool { - return (*web.Controller)(c).CheckXSRFCookie() -} - -// XSRFFormHTML writes an input field contains xsrf token value. -func (c *Controller) XSRFFormHTML() string { - return (*web.Controller)(c).XSRFFormHTML() -} - -// GetControllerAndAction gets the executing controller name and action name. -func (c *Controller) GetControllerAndAction() (string, string) { - return (*web.Controller)(c).GetControllerAndAction() -} diff --git a/adapter/doc.go b/adapter/doc.go deleted file mode 100644 index c8f2174c..00000000 --- a/adapter/doc.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2020 -// -// Licensed 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. - -// used to keep compatible with v1.x -package adapter diff --git a/adapter/error.go b/adapter/error.go deleted file mode 100644 index 35ff7f35..00000000 --- a/adapter/error.go +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 adapter - -import ( - "net/http" - - "github.com/astaxie/beego/adapter/context" - beecontext "github.com/astaxie/beego/server/web/context" - - "github.com/astaxie/beego/server/web" -) - -const ( - errorTypeHandler = iota - errorTypeController -) - -var tpl = ` - - - - - beego application error - - - - - -
- - - - - - - - - - -
Request Method: {{.RequestMethod}}
Request URL: {{.RequestURL}}
RemoteAddr: {{.RemoteAddr }}
-
- Stack -
{{.Stack}}
-
-
- - - -` - -var errtpl = ` - - - - - {{.Title}} - - - -
-
- -
- {{.Content}} - Go Home
- -
Powered by beego {{.BeegoVersion}} -
-
-
- - -` - -// ErrorMaps holds map of http handlers for each error string. -// there is 10 kinds default error(40x and 50x) -var ErrorMaps = web.ErrorMaps - -// ErrorHandler registers http.HandlerFunc to each http err code string. -// usage: -// beego.ErrorHandler("404",NotFound) -// beego.ErrorHandler("500",InternalServerError) -func ErrorHandler(code string, h http.HandlerFunc) *App { - return (*App)(web.ErrorHandler(code, h)) -} - -// ErrorController registers ControllerInterface to each http err code string. -// usage: -// beego.ErrorController(&controllers.ErrorController{}) -func ErrorController(c ControllerInterface) *App { - return (*App)(web.ErrorController(c)) -} - -// Exception Write HttpStatus with errCode and Exec error handler if exist. -func Exception(errCode uint64, ctx *context.Context) { - web.Exception(errCode, (*beecontext.Context)(ctx)) -} diff --git a/adapter/flash.go b/adapter/flash.go deleted file mode 100644 index 2b47ee62..00000000 --- a/adapter/flash.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 adapter - -import ( - "github.com/astaxie/beego/server/web" -) - -// FlashData is a tools to maintain data when using across request. -type FlashData web.FlashData - -// NewFlash return a new empty FlashData struct. -func NewFlash() *FlashData { - return (*FlashData)(web.NewFlash()) -} - -// Set message to flash -func (fd *FlashData) Set(key string, msg string, args ...interface{}) { - (*web.FlashData)(fd).Set(key, msg, args...) -} - -// Success writes success message to flash. -func (fd *FlashData) Success(msg string, args ...interface{}) { - (*web.FlashData)(fd).Success(msg, args...) -} - -// Notice writes notice message to flash. -func (fd *FlashData) Notice(msg string, args ...interface{}) { - (*web.FlashData)(fd).Notice(msg, args...) -} - -// Warning writes warning message to flash. -func (fd *FlashData) Warning(msg string, args ...interface{}) { - (*web.FlashData)(fd).Warning(msg, args...) -} - -// Error writes error message to flash. -func (fd *FlashData) Error(msg string, args ...interface{}) { - (*web.FlashData)(fd).Error(msg, args...) -} - -// Store does the saving operation of flash data. -// the data are encoded and saved in cookie. -func (fd *FlashData) Store(c *Controller) { - (*web.FlashData)(fd).Store((*web.Controller)(c)) -} - -// ReadFromRequest parsed flash data from encoded values in cookie. -func ReadFromRequest(c *Controller) *FlashData { - return (*FlashData)(web.ReadFromRequest((*web.Controller)(c))) -} diff --git a/adapter/fs.go b/adapter/fs.go deleted file mode 100644 index e48e75b5..00000000 --- a/adapter/fs.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 adapter - -import ( - "net/http" - "path/filepath" - - "github.com/astaxie/beego/server/web" -) - -type FileSystem web.FileSystem - -func (d FileSystem) Open(name string) (http.File, error) { - return (web.FileSystem)(d).Open(name) -} - -// Walk walks the file tree rooted at root in filesystem, calling walkFn for each file or -// directory in the tree, including root. All errors that arise visiting files -// and directories are filtered by walkFn. -func Walk(fs http.FileSystem, root string, walkFn filepath.WalkFunc) error { - return web.Walk(fs, root, walkFn) -} diff --git a/adapter/grace/grace.go b/adapter/grace/grace.go deleted file mode 100644 index 75ceef21..00000000 --- a/adapter/grace/grace.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 grace use to hot reload -// Description: http://grisha.org/blog/2014/06/03/graceful-restart-in-golang/ -// -// Usage: -// -// import( -// "log" -// "net/http" -// "os" -// -// "github.com/astaxie/beego/grace" -// ) -// -// func handler(w http.ResponseWriter, r *http.Request) { -// w.Write([]byte("WORLD!")) -// } -// -// func main() { -// mux := http.NewServeMux() -// mux.HandleFunc("/hello", handler) -// -// err := grace.ListenAndServe("localhost:8080", mux) -// if err != nil { -// log.Println(err) -// } -// log.Println("Server on 8080 stopped") -// os.Exit(0) -// } -package grace - -import ( - "net/http" - "time" - - "github.com/astaxie/beego/server/web/grace" -) - -const ( - // PreSignal is the position to add filter before signal - PreSignal = iota - // PostSignal is the position to add filter after signal - PostSignal - // StateInit represent the application inited - StateInit - // StateRunning represent the application is running - StateRunning - // StateShuttingDown represent the application is shutting down - StateShuttingDown - // StateTerminate represent the application is killed - StateTerminate -) - -var ( - - // DefaultReadTimeOut is the HTTP read timeout - DefaultReadTimeOut time.Duration - // DefaultWriteTimeOut is the HTTP Write timeout - DefaultWriteTimeOut time.Duration - // DefaultMaxHeaderBytes is the Max HTTP Header size, default is 0, no limit - DefaultMaxHeaderBytes int - // DefaultTimeout is the shutdown server's timeout. default is 60s - DefaultTimeout = grace.DefaultTimeout -) - -// NewServer returns a new graceServer. -func NewServer(addr string, handler http.Handler) (srv *Server) { - return (*Server)(grace.NewServer(addr, handler)) -} - -// ListenAndServe refer http.ListenAndServe -func ListenAndServe(addr string, handler http.Handler) error { - server := NewServer(addr, handler) - return server.ListenAndServe() -} - -// ListenAndServeTLS refer http.ListenAndServeTLS -func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error { - server := NewServer(addr, handler) - return server.ListenAndServeTLS(certFile, keyFile) -} diff --git a/adapter/grace/server.go b/adapter/grace/server.go deleted file mode 100644 index 0dfb2fd6..00000000 --- a/adapter/grace/server.go +++ /dev/null @@ -1,48 +0,0 @@ -package grace - -import ( - "os" - - "github.com/astaxie/beego/server/web/grace" -) - -// Server embedded http.Server -type Server grace.Server - -// Serve accepts incoming connections on the Listener l, -// creating a new service goroutine for each. -// The service goroutines read requests and then call srv.Handler to reply to them. -func (srv *Server) Serve() (err error) { - return (*grace.Server)(srv).Serve() -} - -// ListenAndServe listens on the TCP network address srv.Addr and then calls Serve -// to handle requests on incoming connections. If srv.Addr is blank, ":http" is -// used. -func (srv *Server) ListenAndServe() (err error) { - return (*grace.Server)(srv).ListenAndServe() -} - -// ListenAndServeTLS listens on the TCP network address srv.Addr and then calls -// Serve to handle requests on incoming TLS connections. -// -// Filenames containing a certificate and matching private key for the server must -// be provided. If the certificate is signed by a certificate authority, the -// certFile should be the concatenation of the server's certificate followed by the -// CA's certificate. -// -// If srv.Addr is blank, ":https" is used. -func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error { - return (*grace.Server)(srv).ListenAndServeTLS(certFile, keyFile) -} - -// ListenAndServeMutualTLS listens on the TCP network address srv.Addr and then calls -// Serve to handle requests on incoming mutual TLS connections. -func (srv *Server) ListenAndServeMutualTLS(certFile, keyFile, trustFile string) error { - return (*grace.Server)(srv).ListenAndServeMutualTLS(certFile, keyFile, trustFile) -} - -// RegisterSignalHook registers a function to be run PreSignal or PostSignal for a given signal. -func (srv *Server) RegisterSignalHook(ppFlag int, sig os.Signal, f func()) error { - return (*grace.Server)(srv).RegisterSignalHook(ppFlag, sig, f) -} diff --git a/adapter/httplib/httplib.go b/adapter/httplib/httplib.go deleted file mode 100644 index d9ff1ea5..00000000 --- a/adapter/httplib/httplib.go +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 httplib is used as http.Client -// Usage: -// -// import "github.com/astaxie/beego/httplib" -// -// b := httplib.Post("http://beego.me/") -// b.Param("username","astaxie") -// b.Param("password","123456") -// b.PostFile("uploadfile1", "httplib.pdf") -// b.PostFile("uploadfile2", "httplib.txt") -// str, err := b.String() -// if err != nil { -// t.Fatal(err) -// } -// fmt.Println(str) -// -// more docs http://beego.me/docs/module/httplib.md -package httplib - -import ( - "crypto/tls" - "net" - "net/http" - "net/url" - "time" - - "github.com/astaxie/beego/client/httplib" -) - -// SetDefaultSetting Overwrite default settings -func SetDefaultSetting(setting BeegoHTTPSettings) { - httplib.SetDefaultSetting(httplib.BeegoHTTPSettings(setting)) -} - -// NewBeegoRequest return *BeegoHttpRequest with specific method -func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest { - return &BeegoHTTPRequest{ - delegate: httplib.NewBeegoRequest(rawurl, method), - } -} - -// Get returns *BeegoHttpRequest with GET method. -func Get(url string) *BeegoHTTPRequest { - return NewBeegoRequest(url, "GET") -} - -// Post returns *BeegoHttpRequest with POST method. -func Post(url string) *BeegoHTTPRequest { - return NewBeegoRequest(url, "POST") -} - -// Put returns *BeegoHttpRequest with PUT method. -func Put(url string) *BeegoHTTPRequest { - return NewBeegoRequest(url, "PUT") -} - -// Delete returns *BeegoHttpRequest DELETE method. -func Delete(url string) *BeegoHTTPRequest { - return NewBeegoRequest(url, "DELETE") -} - -// Head returns *BeegoHttpRequest with HEAD method. -func Head(url string) *BeegoHTTPRequest { - return NewBeegoRequest(url, "HEAD") -} - -// BeegoHTTPSettings is the http.Client setting -type BeegoHTTPSettings httplib.BeegoHTTPSettings - -// BeegoHTTPRequest provides more useful methods for requesting one url than http.Request. -type BeegoHTTPRequest struct { - delegate *httplib.BeegoHTTPRequest -} - -// GetRequest return the request object -func (b *BeegoHTTPRequest) GetRequest() *http.Request { - return b.delegate.GetRequest() -} - -// Setting Change request settings -func (b *BeegoHTTPRequest) Setting(setting BeegoHTTPSettings) *BeegoHTTPRequest { - b.delegate.Setting(httplib.BeegoHTTPSettings(setting)) - return b -} - -// SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password. -func (b *BeegoHTTPRequest) SetBasicAuth(username, password string) *BeegoHTTPRequest { - b.delegate.SetBasicAuth(username, password) - return b -} - -// SetEnableCookie sets enable/disable cookiejar -func (b *BeegoHTTPRequest) SetEnableCookie(enable bool) *BeegoHTTPRequest { - b.delegate.SetEnableCookie(enable) - return b -} - -// SetUserAgent sets User-Agent header field -func (b *BeegoHTTPRequest) SetUserAgent(useragent string) *BeegoHTTPRequest { - b.delegate.SetUserAgent(useragent) - return b -} - -// Debug sets show debug or not when executing request. -func (b *BeegoHTTPRequest) Debug(isdebug bool) *BeegoHTTPRequest { - b.delegate.Debug(isdebug) - return b -} - -// Retries sets Retries times. -// default is 0 means no retried. -// -1 means retried forever. -// others means retried times. -func (b *BeegoHTTPRequest) Retries(times int) *BeegoHTTPRequest { - b.delegate.Retries(times) - return b -} - -func (b *BeegoHTTPRequest) RetryDelay(delay time.Duration) *BeegoHTTPRequest { - b.delegate.RetryDelay(delay) - return b -} - -// DumpBody setting whether need to Dump the Body. -func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest { - b.delegate.DumpBody(isdump) - return b -} - -// DumpRequest return the DumpRequest -func (b *BeegoHTTPRequest) DumpRequest() []byte { - return b.delegate.DumpRequest() -} - -// SetTimeout sets connect time out and read-write time out for BeegoRequest. -func (b *BeegoHTTPRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHTTPRequest { - b.delegate.SetTimeout(connectTimeout, readWriteTimeout) - return b -} - -// SetTLSClientConfig sets tls connection configurations if visiting https url. -func (b *BeegoHTTPRequest) SetTLSClientConfig(config *tls.Config) *BeegoHTTPRequest { - b.delegate.SetTLSClientConfig(config) - return b -} - -// Header add header item string in request. -func (b *BeegoHTTPRequest) Header(key, value string) *BeegoHTTPRequest { - b.delegate.Header(key, value) - return b -} - -// SetHost set the request host -func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest { - b.delegate.SetHost(host) - return b -} - -// SetProtocolVersion Set the protocol version for incoming requests. -// Client requests always use HTTP/1.1. -func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest { - b.delegate.SetProtocolVersion(vers) - return b -} - -// SetCookie add cookie into request. -func (b *BeegoHTTPRequest) SetCookie(cookie *http.Cookie) *BeegoHTTPRequest { - b.delegate.SetCookie(cookie) - return b -} - -// SetTransport set the setting transport -func (b *BeegoHTTPRequest) SetTransport(transport http.RoundTripper) *BeegoHTTPRequest { - b.delegate.SetTransport(transport) - return b -} - -// SetProxy set the http proxy -// example: -// -// func(req *http.Request) (*url.URL, error) { -// u, _ := url.ParseRequestURI("http://127.0.0.1:8118") -// return u, nil -// } -func (b *BeegoHTTPRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *BeegoHTTPRequest { - b.delegate.SetProxy(proxy) - return b -} - -// SetCheckRedirect specifies the policy for handling redirects. -// -// If CheckRedirect is nil, the Client uses its default policy, -// which is to stop after 10 consecutive requests. -func (b *BeegoHTTPRequest) SetCheckRedirect(redirect func(req *http.Request, via []*http.Request) error) *BeegoHTTPRequest { - b.delegate.SetCheckRedirect(redirect) - return b -} - -// Param adds query param in to request. -// params build query string as ?key1=value1&key2=value2... -func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest { - b.delegate.Param(key, value) - return b -} - -// PostFile add a post file to the request -func (b *BeegoHTTPRequest) PostFile(formname, filename string) *BeegoHTTPRequest { - b.delegate.PostFile(formname, filename) - return b -} - -// Body adds request raw body. -// it supports string and []byte. -func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest { - b.delegate.Body(data) - return b -} - -// XMLBody adds request raw body encoding by XML. -func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) { - _, err := b.delegate.XMLBody(obj) - return b, err -} - -// YAMLBody adds request raw body encoding by YAML. -func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error) { - _, err := b.delegate.YAMLBody(obj) - return b, err -} - -// JSONBody adds request raw body encoding by JSON. -func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) { - _, err := b.delegate.JSONBody(obj) - return b, err -} - -// DoRequest will do the client.Do -func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) { - return b.delegate.DoRequest() -} - -// String returns the body string in response. -// it calls Response inner. -func (b *BeegoHTTPRequest) String() (string, error) { - return b.delegate.String() -} - -// Bytes returns the body []byte in response. -// it calls Response inner. -func (b *BeegoHTTPRequest) Bytes() ([]byte, error) { - return b.delegate.Bytes() -} - -// ToFile saves the body data in response to one file. -// it calls Response inner. -func (b *BeegoHTTPRequest) ToFile(filename string) error { - return b.delegate.ToFile(filename) -} - -// ToJSON returns the map that marshals from the body bytes as json in response . -// it calls Response inner. -func (b *BeegoHTTPRequest) ToJSON(v interface{}) error { - return b.delegate.ToJSON(v) -} - -// ToXML returns the map that marshals from the body bytes as xml in response . -// it calls Response inner. -func (b *BeegoHTTPRequest) ToXML(v interface{}) error { - return b.delegate.ToXML(v) -} - -// ToYAML returns the map that marshals from the body bytes as yaml in response . -// it calls Response inner. -func (b *BeegoHTTPRequest) ToYAML(v interface{}) error { - return b.delegate.ToYAML(v) -} - -// Response executes request client gets response mannually. -func (b *BeegoHTTPRequest) Response() (*http.Response, error) { - return b.delegate.Response() -} - -// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field. -func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) { - return httplib.TimeoutDialer(cTimeout, rwTimeout) -} diff --git a/adapter/logs/accesslog.go b/adapter/logs/accesslog.go deleted file mode 100644 index a2150884..00000000 --- a/adapter/logs/accesslog.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 logs - -import ( - "github.com/astaxie/beego/core/logs" -) - -// AccessLogRecord struct for holding access log data. -type AccessLogRecord logs.AccessLogRecord - -// AccessLog - Format and print access log. -func AccessLog(r *AccessLogRecord, format string) { - logs.AccessLog((*logs.AccessLogRecord)(r), format) -} diff --git a/adapter/logs/alils/alils.go b/adapter/logs/alils/alils.go deleted file mode 100644 index 941cba4c..00000000 --- a/adapter/logs/alils/alils.go +++ /dev/null @@ -1,5 +0,0 @@ -package alils - -import ( - _ "github.com/astaxie/beego/core/logs/alils" -) diff --git a/adapter/logs/es/es.go b/adapter/logs/es/es.go deleted file mode 100644 index 0f0fd607..00000000 --- a/adapter/logs/es/es.go +++ /dev/null @@ -1,5 +0,0 @@ -package es - -import ( - _ "github.com/astaxie/beego/core/logs/es" -) diff --git a/adapter/logs/log.go b/adapter/logs/log.go deleted file mode 100644 index 54eb24d5..00000000 --- a/adapter/logs/log.go +++ /dev/null @@ -1,347 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 logs provide a general log interface -// Usage: -// -// import "github.com/astaxie/beego/logs" -// -// log := NewLogger(10000) -// log.SetLogger("console", "") -// -// > the first params stand for how many channel -// -// Use it like this: -// -// log.Trace("trace") -// log.Info("info") -// log.Warn("warning") -// log.Debug("debug") -// log.Critical("critical") -// -// more docs http://beego.me/docs/module/logs.md -package logs - -import ( - "log" - "time" - - "github.com/astaxie/beego/core/logs" -) - -// RFC5424 log message levels. -const ( - LevelEmergency = iota - LevelAlert - LevelCritical - LevelError - LevelWarning - LevelNotice - LevelInformational - LevelDebug -) - -// levelLogLogger is defined to implement log.Logger -// the real log level will be LevelEmergency -const levelLoggerImpl = -1 - -// Name for adapter with beego official support -const ( - AdapterConsole = "console" - AdapterFile = "file" - AdapterMultiFile = "multifile" - AdapterMail = "smtp" - AdapterConn = "conn" - AdapterEs = "es" - AdapterJianLiao = "jianliao" - AdapterSlack = "slack" - AdapterAliLS = "alils" -) - -// Legacy log level constants to ensure backwards compatibility. -const ( - LevelInfo = LevelInformational - LevelTrace = LevelDebug - LevelWarn = LevelWarning -) - -type newLoggerFunc func() Logger - -// Logger defines the behavior of a log provider. -type Logger interface { - Init(config string) error - WriteMsg(when time.Time, msg string, level int) error - Destroy() - Flush() -} - -// Register makes a log provide available by the provided name. -// If Register is called twice with the same name or if driver is nil, -// it panics. -func Register(name string, log newLoggerFunc) { - logs.Register(name, func() logs.Logger { - return &oldToNewAdapter{ - old: log(), - } - }) -} - -// BeeLogger is default logger in beego application. -// it can contain several providers and log message into all providers. -type BeeLogger logs.BeeLogger - -const defaultAsyncMsgLen = 1e3 - -// NewLogger returns a new BeeLogger. -// channelLen means the number of messages in chan(used where asynchronous is true). -// if the buffering chan is full, logger adapters write to file or other way. -func NewLogger(channelLens ...int64) *BeeLogger { - return (*BeeLogger)(logs.NewLogger(channelLens...)) -} - -// Async set the log to asynchronous and start the goroutine -func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger { - (*logs.BeeLogger)(bl).Async(msgLen...) - return bl -} - -// SetLogger provides a given logger adapter into BeeLogger with config string. -// config need to be correct JSON as string: {"interval":360}. -func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error { - return (*logs.BeeLogger)(bl).SetLogger(adapterName, configs...) -} - -// DelLogger remove a logger adapter in BeeLogger. -func (bl *BeeLogger) DelLogger(adapterName string) error { - return (*logs.BeeLogger)(bl).DelLogger(adapterName) -} - -func (bl *BeeLogger) Write(p []byte) (n int, err error) { - return (*logs.BeeLogger)(bl).Write(p) -} - -// SetLevel Set log message level. -// If message level (such as LevelDebug) is higher than logger level (such as LevelWarning), -// log providers will not even be sent the message. -func (bl *BeeLogger) SetLevel(l int) { - (*logs.BeeLogger)(bl).SetLevel(l) -} - -// GetLevel Get Current log message level. -func (bl *BeeLogger) GetLevel() int { - return (*logs.BeeLogger)(bl).GetLevel() -} - -// SetLogFuncCallDepth set log funcCallDepth -func (bl *BeeLogger) SetLogFuncCallDepth(d int) { - (*logs.BeeLogger)(bl).SetLogFuncCallDepth(d) -} - -// GetLogFuncCallDepth return log funcCallDepth for wrapper -func (bl *BeeLogger) GetLogFuncCallDepth() int { - return (*logs.BeeLogger)(bl).GetLogFuncCallDepth() -} - -// EnableFuncCallDepth enable log funcCallDepth -func (bl *BeeLogger) EnableFuncCallDepth(b bool) { - (*logs.BeeLogger)(bl).EnableFuncCallDepth(b) -} - -// set prefix -func (bl *BeeLogger) SetPrefix(s string) { - (*logs.BeeLogger)(bl).SetPrefix(s) -} - -// Emergency Log EMERGENCY level message. -func (bl *BeeLogger) Emergency(format string, v ...interface{}) { - (*logs.BeeLogger)(bl).Emergency(format, v...) -} - -// Alert Log ALERT level message. -func (bl *BeeLogger) Alert(format string, v ...interface{}) { - (*logs.BeeLogger)(bl).Alert(format, v...) -} - -// Critical Log CRITICAL level message. -func (bl *BeeLogger) Critical(format string, v ...interface{}) { - (*logs.BeeLogger)(bl).Critical(format, v...) -} - -// Error Log ERROR level message. -func (bl *BeeLogger) Error(format string, v ...interface{}) { - (*logs.BeeLogger)(bl).Error(format, v...) -} - -// Warning Log WARNING level message. -func (bl *BeeLogger) Warning(format string, v ...interface{}) { - (*logs.BeeLogger)(bl).Warning(format, v...) -} - -// Notice Log NOTICE level message. -func (bl *BeeLogger) Notice(format string, v ...interface{}) { - (*logs.BeeLogger)(bl).Notice(format, v...) -} - -// Informational Log INFORMATIONAL level message. -func (bl *BeeLogger) Informational(format string, v ...interface{}) { - (*logs.BeeLogger)(bl).Informational(format, v...) -} - -// Debug Log DEBUG level message. -func (bl *BeeLogger) Debug(format string, v ...interface{}) { - (*logs.BeeLogger)(bl).Debug(format, v...) -} - -// Warn Log WARN level message. -// compatibility alias for Warning() -func (bl *BeeLogger) Warn(format string, v ...interface{}) { - (*logs.BeeLogger)(bl).Warn(format, v...) -} - -// Info Log INFO level message. -// compatibility alias for Informational() -func (bl *BeeLogger) Info(format string, v ...interface{}) { - (*logs.BeeLogger)(bl).Info(format, v...) -} - -// Trace Log TRACE level message. -// compatibility alias for Debug() -func (bl *BeeLogger) Trace(format string, v ...interface{}) { - (*logs.BeeLogger)(bl).Trace(format, v...) -} - -// Flush flush all chan data. -func (bl *BeeLogger) Flush() { - (*logs.BeeLogger)(bl).Flush() -} - -// Close close logger, flush all chan data and destroy all adapters in BeeLogger. -func (bl *BeeLogger) Close() { - (*logs.BeeLogger)(bl).Close() -} - -// Reset close all outputs, and set bl.outputs to nil -func (bl *BeeLogger) Reset() { - (*logs.BeeLogger)(bl).Reset() -} - -// GetBeeLogger returns the default BeeLogger -func GetBeeLogger() *BeeLogger { - return (*BeeLogger)(logs.GetBeeLogger()) -} - -// GetLogger returns the default BeeLogger -func GetLogger(prefixes ...string) *log.Logger { - return logs.GetLogger(prefixes...) -} - -// Reset will remove all the adapter -func Reset() { - logs.Reset() -} - -// Async set the beelogger with Async mode and hold msglen messages -func Async(msgLen ...int64) *BeeLogger { - return (*BeeLogger)(logs.Async(msgLen...)) -} - -// SetLevel sets the global log level used by the simple logger. -func SetLevel(l int) { - logs.SetLevel(l) -} - -// SetPrefix sets the prefix -func SetPrefix(s string) { - logs.SetPrefix(s) -} - -// EnableFuncCallDepth enable log funcCallDepth -func EnableFuncCallDepth(b bool) { - logs.EnableFuncCallDepth(b) -} - -// SetLogFuncCall set the CallDepth, default is 4 -func SetLogFuncCall(b bool) { - logs.SetLogFuncCall(b) -} - -// SetLogFuncCallDepth set log funcCallDepth -func SetLogFuncCallDepth(d int) { - logs.SetLogFuncCallDepth(d) -} - -// SetLogger sets a new logger. -func SetLogger(adapter string, config ...string) error { - return logs.SetLogger(adapter, config...) -} - -// Emergency logs a message at emergency level. -func Emergency(f interface{}, v ...interface{}) { - logs.Emergency(f, v...) -} - -// Alert logs a message at alert level. -func Alert(f interface{}, v ...interface{}) { - logs.Alert(f, v...) -} - -// Critical logs a message at critical level. -func Critical(f interface{}, v ...interface{}) { - logs.Critical(f, v...) -} - -// Error logs a message at error level. -func Error(f interface{}, v ...interface{}) { - logs.Error(f, v...) -} - -// Warning logs a message at warning level. -func Warning(f interface{}, v ...interface{}) { - logs.Warning(f, v...) -} - -// Warn compatibility alias for Warning() -func Warn(f interface{}, v ...interface{}) { - logs.Warn(f, v...) -} - -// Notice logs a message at notice level. -func Notice(f interface{}, v ...interface{}) { - logs.Notice(f, v...) -} - -// Informational logs a message at info level. -func Informational(f interface{}, v ...interface{}) { - logs.Informational(f, v...) -} - -// Info compatibility alias for Warning() -func Info(f interface{}, v ...interface{}) { - logs.Info(f, v...) -} - -// Debug logs a message at debug level. -func Debug(f interface{}, v ...interface{}) { - logs.Debug(f, v...) -} - -// Trace logs a message at trace level. -// compatibility alias for Warning() -func Trace(f interface{}, v ...interface{}) { - logs.Trace(f, v...) -} - -func init() { - SetLogFuncCallDepth(4) -} diff --git a/adapter/logs/log_adapter.go b/adapter/logs/log_adapter.go deleted file mode 100644 index 6b7022d6..00000000 --- a/adapter/logs/log_adapter.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 logs - -import ( - "time" - - "github.com/astaxie/beego/core/logs" -) - -type oldToNewAdapter struct { - old Logger -} - -func (o *oldToNewAdapter) Init(config string) error { - return o.old.Init(config) -} - -func (o *oldToNewAdapter) WriteMsg(lm *logs.LogMsg) error { - return o.old.WriteMsg(lm.When, lm.OldStyleFormat(), lm.Level) -} - -func (o *oldToNewAdapter) Destroy() { - o.old.Destroy() -} - -func (o *oldToNewAdapter) Flush() { - o.old.Flush() -} - -func (o *oldToNewAdapter) SetFormatter(f logs.LogFormatter) { - panic("unsupported operation, you should not invoke this method") -} - -type newToOldAdapter struct { - n logs.Logger -} - -func (n *newToOldAdapter) Init(config string) error { - return n.n.Init(config) -} - -func (n *newToOldAdapter) WriteMsg(when time.Time, msg string, level int) error { - return n.n.WriteMsg(&logs.LogMsg{ - When: when, - Msg: msg, - Level: level, - }) -} - -func (n *newToOldAdapter) Destroy() { - panic("implement me") -} - -func (n *newToOldAdapter) Flush() { - panic("implement me") -} diff --git a/adapter/logs/logger.go b/adapter/logs/logger.go deleted file mode 100644 index 5a8e0a1c..00000000 --- a/adapter/logs/logger.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 logs - -import ( - "github.com/astaxie/beego/core/logs" -) - -// ColorByStatus return color by http code -// 2xx return Green -// 3xx return White -// 4xx return Yellow -// 5xx return Red -func ColorByStatus(code int) string { - return logs.ColorByStatus(code) -} - -// ColorByMethod return color by http code -func ColorByMethod(method string) string { - return logs.ColorByMethod(method) -} - -// ResetColor return reset color -func ResetColor() string { - return logs.ResetColor() -} diff --git a/adapter/logs/logger_test.go b/adapter/logs/logger_test.go deleted file mode 100644 index 9f2cc5a5..00000000 --- a/adapter/logs/logger_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2016 beego Author. All Rights Reserved. -// -// Licensed 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 logs - -import ( - "testing" -) - -func TestBeeLogger_Info(t *testing.T) { - log := NewLogger(1000) - log.SetLogger("file", `{"net":"tcp","addr":":7020"}`) -} diff --git a/adapter/migration/ddl.go b/adapter/migration/ddl.go deleted file mode 100644 index b43b4d34..00000000 --- a/adapter/migration/ddl.go +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 migration - -import ( - "github.com/astaxie/beego/client/orm/migration" -) - -// Index struct defines the structure of Index Columns -type Index migration.Index - -// Unique struct defines a single unique key combination -type Unique migration.Unique - -// Column struct defines a single column of a table -type Column migration.Column - -// Foreign struct defines a single foreign relationship -type Foreign migration.Foreign - -// RenameColumn struct allows renaming of columns -type RenameColumn migration.RenameColumn - -// CreateTable creates the table on system -func (m *Migration) CreateTable(tablename, engine, charset string, p ...func()) { - (*migration.Migration)(m).CreateTable(tablename, engine, charset, p...) -} - -// AlterTable set the ModifyType to alter -func (m *Migration) AlterTable(tablename string) { - (*migration.Migration)(m).AlterTable(tablename) -} - -// NewCol creates a new standard column and attaches it to m struct -func (m *Migration) NewCol(name string) *Column { - return (*Column)((*migration.Migration)(m).NewCol(name)) -} - -// PriCol creates a new primary column and attaches it to m struct -func (m *Migration) PriCol(name string) *Column { - return (*Column)((*migration.Migration)(m).PriCol(name)) -} - -// UniCol creates / appends columns to specified unique key and attaches it to m struct -func (m *Migration) UniCol(uni, name string) *Column { - return (*Column)((*migration.Migration)(m).UniCol(uni, name)) -} - -// ForeignCol creates a new foreign column and returns the instance of column -func (m *Migration) ForeignCol(colname, foreigncol, foreigntable string) (foreign *Foreign) { - return (*Foreign)((*migration.Migration)(m).ForeignCol(colname, foreigncol, foreigntable)) -} - -// SetOnDelete sets the on delete of foreign -func (foreign *Foreign) SetOnDelete(del string) *Foreign { - (*migration.Foreign)(foreign).SetOnDelete(del) - return foreign -} - -// SetOnUpdate sets the on update of foreign -func (foreign *Foreign) SetOnUpdate(update string) *Foreign { - (*migration.Foreign)(foreign).SetOnUpdate(update) - return foreign -} - -// Remove marks the columns to be removed. -// it allows reverse m to create the column. -func (c *Column) Remove() { - (*migration.Column)(c).Remove() -} - -// SetAuto enables auto_increment of column (can be used once) -func (c *Column) SetAuto(inc bool) *Column { - (*migration.Column)(c).SetAuto(inc) - return c -} - -// SetNullable sets the column to be null -func (c *Column) SetNullable(null bool) *Column { - (*migration.Column)(c).SetNullable(null) - return c -} - -// SetDefault sets the default value, prepend with "DEFAULT " -func (c *Column) SetDefault(def string) *Column { - (*migration.Column)(c).SetDefault(def) - return c -} - -// SetUnsigned sets the column to be unsigned int -func (c *Column) SetUnsigned(unsign bool) *Column { - (*migration.Column)(c).SetUnsigned(unsign) - return c -} - -// SetDataType sets the dataType of the column -func (c *Column) SetDataType(dataType string) *Column { - (*migration.Column)(c).SetDataType(dataType) - return c -} - -// SetOldNullable allows reverting to previous nullable on reverse ms -func (c *RenameColumn) SetOldNullable(null bool) *RenameColumn { - (*migration.RenameColumn)(c).SetOldNullable(null) - return c -} - -// SetOldDefault allows reverting to previous default on reverse ms -func (c *RenameColumn) SetOldDefault(def string) *RenameColumn { - (*migration.RenameColumn)(c).SetOldDefault(def) - return c -} - -// SetOldUnsigned allows reverting to previous unsgined on reverse ms -func (c *RenameColumn) SetOldUnsigned(unsign bool) *RenameColumn { - (*migration.RenameColumn)(c).SetOldUnsigned(unsign) - return c -} - -// SetOldDataType allows reverting to previous datatype on reverse ms -func (c *RenameColumn) SetOldDataType(dataType string) *RenameColumn { - (*migration.RenameColumn)(c).SetOldDataType(dataType) - return c -} - -// SetPrimary adds the columns to the primary key (can only be used any number of times in only one m) -func (c *Column) SetPrimary(m *Migration) *Column { - (*migration.Column)(c).SetPrimary((*migration.Migration)(m)) - return c -} - -// AddColumnsToUnique adds the columns to Unique Struct -func (unique *Unique) AddColumnsToUnique(columns ...*Column) *Unique { - cls := toNewColumnsArray(columns) - (*migration.Unique)(unique).AddColumnsToUnique(cls...) - return unique -} - -// AddColumns adds columns to m struct -func (m *Migration) AddColumns(columns ...*Column) *Migration { - cls := toNewColumnsArray(columns) - (*migration.Migration)(m).AddColumns(cls...) - return m -} - -func toNewColumnsArray(columns []*Column) []*migration.Column { - cls := make([]*migration.Column, 0, len(columns)) - for _, c := range columns { - cls = append(cls, (*migration.Column)(c)) - } - return cls -} - -// AddPrimary adds the column to primary in m struct -func (m *Migration) AddPrimary(primary *Column) *Migration { - (*migration.Migration)(m).AddPrimary((*migration.Column)(primary)) - return m -} - -// AddUnique adds the column to unique in m struct -func (m *Migration) AddUnique(unique *Unique) *Migration { - (*migration.Migration)(m).AddUnique((*migration.Unique)(unique)) - return m -} - -// AddForeign adds the column to foreign in m struct -func (m *Migration) AddForeign(foreign *Foreign) *Migration { - (*migration.Migration)(m).AddForeign((*migration.Foreign)(foreign)) - return m -} - -// AddIndex adds the column to index in m struct -func (m *Migration) AddIndex(index *Index) *Migration { - (*migration.Migration)(m).AddIndex((*migration.Index)(index)) - return m -} - -// RenameColumn allows renaming of columns -func (m *Migration) RenameColumn(from, to string) *RenameColumn { - return (*RenameColumn)((*migration.Migration)(m).RenameColumn(from, to)) -} - -// GetSQL returns the generated sql depending on ModifyType -func (m *Migration) GetSQL() (sql string) { - return (*migration.Migration)(m).GetSQL() -} diff --git a/adapter/migration/migration.go b/adapter/migration/migration.go deleted file mode 100644 index 677c35ca..00000000 --- a/adapter/migration/migration.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 migration is used for migration -// -// The table structure is as follow: -// -// CREATE TABLE `migrations` ( -// `id_migration` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key', -// `name` varchar(255) DEFAULT NULL COMMENT 'migration name, unique', -// `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'date migrated or rolled back', -// `statements` longtext COMMENT 'SQL statements for this migration', -// `rollback_statements` longtext, -// `status` enum('update','rollback') DEFAULT NULL COMMENT 'update indicates it is a normal migration while rollback means this migration is rolled back', -// PRIMARY KEY (`id_migration`) -// ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -package migration - -import ( - "github.com/astaxie/beego/client/orm/migration" -) - -// const the data format for the bee generate migration datatype -const ( - DateFormat = "20060102_150405" - DBDateFormat = "2006-01-02 15:04:05" -) - -// Migrationer is an interface for all Migration struct -type Migrationer interface { - Up() - Down() - Reset() - Exec(name, status string) error - GetCreated() int64 -} - -// Migration defines the migrations by either SQL or DDL -type Migration migration.Migration - -// Up implement in the Inheritance struct for upgrade -func (m *Migration) Up() { - (*migration.Migration)(m).Up() -} - -// Down implement in the Inheritance struct for down -func (m *Migration) Down() { - (*migration.Migration)(m).Down() -} - -// Migrate adds the SQL to the execution list -func (m *Migration) Migrate(migrationType string) { - (*migration.Migration)(m).Migrate(migrationType) -} - -// SQL add sql want to execute -func (m *Migration) SQL(sql string) { - (*migration.Migration)(m).SQL(sql) -} - -// Reset the sqls -func (m *Migration) Reset() { - (*migration.Migration)(m).Reset() -} - -// Exec execute the sql already add in the sql -func (m *Migration) Exec(name, status string) error { - return (*migration.Migration)(m).Exec(name, status) -} - -// GetCreated get the unixtime from the Created -func (m *Migration) GetCreated() int64 { - return (*migration.Migration)(m).GetCreated() -} - -// Register register the Migration in the map -func Register(name string, m Migrationer) error { - return migration.Register(name, m) -} - -// Upgrade upgrade the migration from lasttime -func Upgrade(lasttime int64) error { - return migration.Upgrade(lasttime) -} - -// Rollback rollback the migration by the name -func Rollback(name string) error { - return migration.Rollback(name) -} - -// Reset reset all migration -// run all migration's down function -func Reset() error { - return migration.Reset() -} - -// Refresh first Reset, then Upgrade -func Refresh() error { - return migration.Refresh() -} diff --git a/adapter/namespace.go b/adapter/namespace.go deleted file mode 100644 index 98cbd8a5..00000000 --- a/adapter/namespace.go +++ /dev/null @@ -1,378 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 adapter - -import ( - "net/http" - - adtContext "github.com/astaxie/beego/adapter/context" - "github.com/astaxie/beego/server/web/context" - - "github.com/astaxie/beego/server/web" -) - -type namespaceCond func(*adtContext.Context) bool - -// LinkNamespace used as link action -type LinkNamespace func(*Namespace) - -// Namespace is store all the info -type Namespace web.Namespace - -// NewNamespace get new Namespace -func NewNamespace(prefix string, params ...LinkNamespace) *Namespace { - nps := oldToNewLinkNs(params) - return (*Namespace)(web.NewNamespace(prefix, nps...)) -} - -func oldToNewLinkNs(params []LinkNamespace) []web.LinkNamespace { - nps := make([]web.LinkNamespace, 0, len(params)) - for _, p := range params { - nps = append(nps, func(namespace *web.Namespace) { - p((*Namespace)(namespace)) - }) - } - return nps -} - -// Cond set condition function -// if cond return true can run this namespace, else can't -// usage: -// ns.Cond(func (ctx *context.Context) bool{ -// if ctx.Input.Domain() == "api.beego.me" { -// return true -// } -// return false -// }) -// Cond as the first filter -func (n *Namespace) Cond(cond namespaceCond) *Namespace { - (*web.Namespace)(n).Cond(func(context *context.Context) bool { - return cond((*adtContext.Context)(context)) - }) - return n -} - -// Filter add filter in the Namespace -// action has before & after -// FilterFunc -// usage: -// Filter("before", func (ctx *context.Context){ -// _, ok := ctx.Input.Session("uid").(int) -// if !ok && ctx.Request.RequestURI != "/login" { -// ctx.Redirect(302, "/login") -// } -// }) -func (n *Namespace) Filter(action string, filter ...FilterFunc) *Namespace { - nfs := oldToNewFilter(filter) - (*web.Namespace)(n).Filter(action, nfs...) - return n -} - -func oldToNewFilter(filter []FilterFunc) []web.FilterFunc { - nfs := make([]web.FilterFunc, 0, len(filter)) - for _, f := range filter { - nfs = append(nfs, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - } - return nfs -} - -// Router same as beego.Rourer -// refer: https://godoc.org/github.com/astaxie/beego#Router -func (n *Namespace) Router(rootpath string, c ControllerInterface, mappingMethods ...string) *Namespace { - (*web.Namespace)(n).Router(rootpath, c, mappingMethods...) - return n -} - -// AutoRouter same as beego.AutoRouter -// refer: https://godoc.org/github.com/astaxie/beego#AutoRouter -func (n *Namespace) AutoRouter(c ControllerInterface) *Namespace { - (*web.Namespace)(n).AutoRouter(c) - return n -} - -// AutoPrefix same as beego.AutoPrefix -// refer: https://godoc.org/github.com/astaxie/beego#AutoPrefix -func (n *Namespace) AutoPrefix(prefix string, c ControllerInterface) *Namespace { - (*web.Namespace)(n).AutoPrefix(prefix, c) - return n -} - -// Get same as beego.Get -// refer: https://godoc.org/github.com/astaxie/beego#Get -func (n *Namespace) Get(rootpath string, f FilterFunc) *Namespace { - (*web.Namespace)(n).Get(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - return n -} - -// Post same as beego.Post -// refer: https://godoc.org/github.com/astaxie/beego#Post -func (n *Namespace) Post(rootpath string, f FilterFunc) *Namespace { - (*web.Namespace)(n).Post(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - return n -} - -// Delete same as beego.Delete -// refer: https://godoc.org/github.com/astaxie/beego#Delete -func (n *Namespace) Delete(rootpath string, f FilterFunc) *Namespace { - (*web.Namespace)(n).Delete(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - return n -} - -// Put same as beego.Put -// refer: https://godoc.org/github.com/astaxie/beego#Put -func (n *Namespace) Put(rootpath string, f FilterFunc) *Namespace { - (*web.Namespace)(n).Put(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - return n -} - -// Head same as beego.Head -// refer: https://godoc.org/github.com/astaxie/beego#Head -func (n *Namespace) Head(rootpath string, f FilterFunc) *Namespace { - (*web.Namespace)(n).Head(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - return n -} - -// Options same as beego.Options -// refer: https://godoc.org/github.com/astaxie/beego#Options -func (n *Namespace) Options(rootpath string, f FilterFunc) *Namespace { - (*web.Namespace)(n).Options(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - return n -} - -// Patch same as beego.Patch -// refer: https://godoc.org/github.com/astaxie/beego#Patch -func (n *Namespace) Patch(rootpath string, f FilterFunc) *Namespace { - (*web.Namespace)(n).Patch(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - return n -} - -// Any same as beego.Any -// refer: https://godoc.org/github.com/astaxie/beego#Any -func (n *Namespace) Any(rootpath string, f FilterFunc) *Namespace { - (*web.Namespace)(n).Any(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - return n -} - -// Handler same as beego.Handler -// refer: https://godoc.org/github.com/astaxie/beego#Handler -func (n *Namespace) Handler(rootpath string, h http.Handler) *Namespace { - (*web.Namespace)(n).Handler(rootpath, h) - return n -} - -// Include add include class -// refer: https://godoc.org/github.com/astaxie/beego#Include -func (n *Namespace) Include(cList ...ControllerInterface) *Namespace { - nL := oldToNewCtrlIntfs(cList) - (*web.Namespace)(n).Include(nL...) - return n -} - -// Namespace add nest Namespace -// usage: -// ns := beego.NewNamespace(“/v1”). -// Namespace( -// beego.NewNamespace("/shop"). -// Get("/:id", func(ctx *context.Context) { -// ctx.Output.Body([]byte("shopinfo")) -// }), -// beego.NewNamespace("/order"). -// Get("/:id", func(ctx *context.Context) { -// ctx.Output.Body([]byte("orderinfo")) -// }), -// beego.NewNamespace("/crm"). -// Get("/:id", func(ctx *context.Context) { -// ctx.Output.Body([]byte("crminfo")) -// }), -// ) -func (n *Namespace) Namespace(ns ...*Namespace) *Namespace { - nns := oldToNewNs(ns) - (*web.Namespace)(n).Namespace(nns...) - return n -} - -func oldToNewNs(ns []*Namespace) []*web.Namespace { - nns := make([]*web.Namespace, 0, len(ns)) - for _, n := range ns { - nns = append(nns, (*web.Namespace)(n)) - } - return nns -} - -// AddNamespace register Namespace into beego.Handler -// support multi Namespace -func AddNamespace(nl ...*Namespace) { - nnl := oldToNewNs(nl) - web.AddNamespace(nnl...) -} - -// NSCond is Namespace Condition -func NSCond(cond namespaceCond) LinkNamespace { - return func(namespace *Namespace) { - web.NSCond(func(b *context.Context) bool { - return cond((*adtContext.Context)(b)) - }) - } -} - -// NSBefore Namespace BeforeRouter filter -func NSBefore(filterList ...FilterFunc) LinkNamespace { - return func(namespace *Namespace) { - nfs := oldToNewFilter(filterList) - web.NSBefore(nfs...) - } -} - -// NSAfter add Namespace FinishRouter filter -func NSAfter(filterList ...FilterFunc) LinkNamespace { - return func(namespace *Namespace) { - nfs := oldToNewFilter(filterList) - web.NSAfter(nfs...) - } -} - -// NSInclude Namespace Include ControllerInterface -func NSInclude(cList ...ControllerInterface) LinkNamespace { - return func(namespace *Namespace) { - nfs := oldToNewCtrlIntfs(cList) - web.NSInclude(nfs...) - } -} - -// NSRouter call Namespace Router -func NSRouter(rootpath string, c ControllerInterface, mappingMethods ...string) LinkNamespace { - return func(namespace *Namespace) { - web.Router(rootpath, c, mappingMethods...) - } -} - -// NSGet call Namespace Get -func NSGet(rootpath string, f FilterFunc) LinkNamespace { - return func(ns *Namespace) { - web.NSGet(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - } -} - -// NSPost call Namespace Post -func NSPost(rootpath string, f FilterFunc) LinkNamespace { - return func(ns *Namespace) { - web.Post(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - } -} - -// NSHead call Namespace Head -func NSHead(rootpath string, f FilterFunc) LinkNamespace { - return func(ns *Namespace) { - web.NSHead(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - } -} - -// NSPut call Namespace Put -func NSPut(rootpath string, f FilterFunc) LinkNamespace { - return func(ns *Namespace) { - web.NSPut(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - } -} - -// NSDelete call Namespace Delete -func NSDelete(rootpath string, f FilterFunc) LinkNamespace { - return func(ns *Namespace) { - web.NSDelete(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - } -} - -// NSAny call Namespace Any -func NSAny(rootpath string, f FilterFunc) LinkNamespace { - return func(ns *Namespace) { - web.NSAny(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - } -} - -// NSOptions call Namespace Options -func NSOptions(rootpath string, f FilterFunc) LinkNamespace { - return func(ns *Namespace) { - web.NSOptions(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - } -} - -// NSPatch call Namespace Patch -func NSPatch(rootpath string, f FilterFunc) LinkNamespace { - return func(ns *Namespace) { - web.NSPatch(rootpath, func(ctx *context.Context) { - f((*adtContext.Context)(ctx)) - }) - } -} - -// NSAutoRouter call Namespace AutoRouter -func NSAutoRouter(c ControllerInterface) LinkNamespace { - return func(ns *Namespace) { - web.NSAutoRouter(c) - } -} - -// NSAutoPrefix call Namespace AutoPrefix -func NSAutoPrefix(prefix string, c ControllerInterface) LinkNamespace { - return func(ns *Namespace) { - web.NSAutoPrefix(prefix, c) - } -} - -// NSNamespace add sub Namespace -func NSNamespace(prefix string, params ...LinkNamespace) LinkNamespace { - return func(ns *Namespace) { - nps := oldToNewLinkNs(params) - web.NSNamespace(prefix, nps...) - } -} - -// NSHandler add handler -func NSHandler(rootpath string, h http.Handler) LinkNamespace { - return func(ns *Namespace) { - web.NSHandler(rootpath, h) - } -} diff --git a/adapter/orm/cmd.go b/adapter/orm/cmd.go deleted file mode 100644 index fcbd1be4..00000000 --- a/adapter/orm/cmd.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "github.com/astaxie/beego/client/orm" -) - -// RunCommand listen for orm command and then run it if command arguments passed. -func RunCommand() { - orm.RunCommand() -} - -func RunSyncdb(name string, force bool, verbose bool) error { - return orm.RunSyncdb(name, force, verbose) -} diff --git a/adapter/orm/db.go b/adapter/orm/db.go deleted file mode 100644 index fd878732..00000000 --- a/adapter/orm/db.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "github.com/astaxie/beego/client/orm" -) - -var ( - // ErrMissPK missing pk error - ErrMissPK = orm.ErrMissPK -) diff --git a/adapter/orm/db_alias.go b/adapter/orm/db_alias.go deleted file mode 100644 index 81a07207..00000000 --- a/adapter/orm/db_alias.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "context" - "database/sql" - "time" - - "github.com/astaxie/beego/client/orm" -) - -// DriverType database driver constant int. -type DriverType orm.DriverType - -// Enum the Database driver -const ( - DRMySQL = DriverType(orm.DRMySQL) - DRSqlite = DriverType(orm.DRSqlite) // sqlite - DROracle = DriverType(orm.DROracle) // oracle - DRPostgres = DriverType(orm.DRPostgres) // pgsql - DRTiDB = DriverType(orm.DRTiDB) // TiDB -) - -type DB orm.DB - -func (d *DB) Begin() (*sql.Tx, error) { - return (*orm.DB)(d).Begin() -} - -func (d *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) { - return (*orm.DB)(d).BeginTx(ctx, opts) -} - -func (d *DB) Prepare(query string) (*sql.Stmt, error) { - return (*orm.DB)(d).Prepare(query) -} - -func (d *DB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) { - return (*orm.DB)(d).PrepareContext(ctx, query) -} - -func (d *DB) Exec(query string, args ...interface{}) (sql.Result, error) { - return (*orm.DB)(d).Exec(query, args...) -} - -func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { - return (*orm.DB)(d).ExecContext(ctx, query, args...) -} - -func (d *DB) Query(query string, args ...interface{}) (*sql.Rows, error) { - return (*orm.DB)(d).Query(query, args...) -} - -func (d *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { - return (*orm.DB)(d).QueryContext(ctx, query, args...) -} - -func (d *DB) QueryRow(query string, args ...interface{}) *sql.Row { - return (*orm.DB)(d).QueryRow(query, args) -} - -func (d *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { - return (*orm.DB)(d).QueryRowContext(ctx, query, args...) -} - -// AddAliasWthDB add a aliasName for the drivename -func AddAliasWthDB(aliasName, driverName string, db *sql.DB) error { - return orm.AddAliasWthDB(aliasName, driverName, db) -} - -// RegisterDataBase Setting the database connect params. Use the database driver self dataSource args. -func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) error { - opts := make([]orm.DBOption, 0, 2) - if len(params) > 0 { - opts = append(opts, orm.MaxIdleConnections(params[0])) - } - - if len(params) > 1 { - opts = append(opts, orm.MaxOpenConnections(params[1])) - } - return orm.RegisterDataBase(aliasName, driverName, dataSource, opts...) -} - -// RegisterDriver Register a database driver use specify driver name, this can be definition the driver is which database type. -func RegisterDriver(driverName string, typ DriverType) error { - return orm.RegisterDriver(driverName, orm.DriverType(typ)) -} - -// SetDataBaseTZ Change the database default used timezone -func SetDataBaseTZ(aliasName string, tz *time.Location) error { - return orm.SetDataBaseTZ(aliasName, tz) -} - -// SetMaxIdleConns Change the max idle conns for *sql.DB, use specify database alias name -func SetMaxIdleConns(aliasName string, maxIdleConns int) { - orm.SetMaxIdleConns(aliasName, maxIdleConns) -} - -// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name -func SetMaxOpenConns(aliasName string, maxOpenConns int) { - orm.SetMaxOpenConns(aliasName, maxOpenConns) -} - -// GetDB Get *sql.DB from registered database by db alias name. -// Use "default" as alias name if you not set. -func GetDB(aliasNames ...string) (*sql.DB, error) { - return orm.GetDB(aliasNames...) -} diff --git a/adapter/orm/models.go b/adapter/orm/models.go deleted file mode 100644 index 5df64d6d..00000000 --- a/adapter/orm/models.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "github.com/astaxie/beego/client/orm" -) - -// ResetModelCache Clean model cache. Then you can re-RegisterModel. -// Common use this api for test case. -func ResetModelCache() { - orm.ResetModelCache() -} diff --git a/adapter/orm/models_boot.go b/adapter/orm/models_boot.go deleted file mode 100644 index 0b07de59..00000000 --- a/adapter/orm/models_boot.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "github.com/astaxie/beego/client/orm" -) - -// RegisterModel register models -func RegisterModel(models ...interface{}) { - orm.RegisterModel(models...) -} - -// RegisterModelWithPrefix register models with a prefix -func RegisterModelWithPrefix(prefix string, models ...interface{}) { - orm.RegisterModelWithPrefix(prefix, models) -} - -// RegisterModelWithSuffix register models with a suffix -func RegisterModelWithSuffix(suffix string, models ...interface{}) { - orm.RegisterModelWithSuffix(suffix, models...) -} - -// BootStrap bootstrap models. -// make all model parsed and can not add more models -func BootStrap() { - orm.BootStrap() -} diff --git a/adapter/orm/models_fields.go b/adapter/orm/models_fields.go deleted file mode 100644 index 6210567b..00000000 --- a/adapter/orm/models_fields.go +++ /dev/null @@ -1,625 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "time" - - "github.com/astaxie/beego/client/orm" -) - -// Define the Type enum -const ( - TypeBooleanField = orm.TypeBooleanField - TypeVarCharField = orm.TypeVarCharField - TypeCharField = orm.TypeCharField - TypeTextField = orm.TypeTextField - TypeTimeField = orm.TypeTimeField - TypeDateField = orm.TypeDateField - TypeDateTimeField = orm.TypeDateTimeField - TypeBitField = orm.TypeBitField - TypeSmallIntegerField = orm.TypeSmallIntegerField - TypeIntegerField = orm.TypeIntegerField - TypeBigIntegerField = orm.TypeBigIntegerField - TypePositiveBitField = orm.TypePositiveBitField - TypePositiveSmallIntegerField = orm.TypePositiveSmallIntegerField - TypePositiveIntegerField = orm.TypePositiveIntegerField - TypePositiveBigIntegerField = orm.TypePositiveBigIntegerField - TypeFloatField = orm.TypeFloatField - TypeDecimalField = orm.TypeDecimalField - TypeJSONField = orm.TypeJSONField - TypeJsonbField = orm.TypeJsonbField - RelForeignKey = orm.RelForeignKey - RelOneToOne = orm.RelOneToOne - RelManyToMany = orm.RelManyToMany - RelReverseOne = orm.RelReverseOne - RelReverseMany = orm.RelReverseMany -) - -// Define some logic enum -const ( - IsIntegerField = orm.IsIntegerField - IsPositiveIntegerField = orm.IsPositiveIntegerField - IsRelField = orm.IsRelField - IsFieldType = orm.IsFieldType -) - -// BooleanField A true/false field. -type BooleanField orm.BooleanField - -// Value return the BooleanField -func (e BooleanField) Value() bool { - return orm.BooleanField(e).Value() -} - -// Set will set the BooleanField -func (e *BooleanField) Set(d bool) { - (*orm.BooleanField)(e).Set(d) -} - -// String format the Bool to string -func (e *BooleanField) String() string { - return (*orm.BooleanField)(e).String() -} - -// FieldType return BooleanField the type -func (e *BooleanField) FieldType() int { - return (*orm.BooleanField)(e).FieldType() -} - -// SetRaw set the interface to bool -func (e *BooleanField) SetRaw(value interface{}) error { - return (*orm.BooleanField)(e).SetRaw(value) -} - -// RawValue return the current value -func (e *BooleanField) RawValue() interface{} { - return (*orm.BooleanField)(e).RawValue() -} - -// verify the BooleanField implement the Fielder interface -var _ Fielder = new(BooleanField) - -// CharField A string field -// required values tag: size -// The size is enforced at the database level and in models’s validation. -// eg: `orm:"size(120)"` -type CharField orm.CharField - -// Value return the CharField's Value -func (e CharField) Value() string { - return orm.CharField(e).Value() -} - -// Set CharField value -func (e *CharField) Set(d string) { - (*orm.CharField)(e).Set(d) -} - -// String return the CharField -func (e *CharField) String() string { - return (*orm.CharField)(e).String() -} - -// FieldType return the enum type -func (e *CharField) FieldType() int { - return (*orm.CharField)(e).FieldType() -} - -// SetRaw set the interface to string -func (e *CharField) SetRaw(value interface{}) error { - return (*orm.CharField)(e).SetRaw(value) -} - -// RawValue return the CharField value -func (e *CharField) RawValue() interface{} { - return (*orm.CharField)(e).RawValue() -} - -// verify CharField implement Fielder -var _ Fielder = new(CharField) - -// TimeField A time, represented in go by a time.Time instance. -// only time values like 10:00:00 -// Has a few extra, optional attr tag: -// -// auto_now: -// Automatically set the field to now every time the object is saved. Useful for “last-modified” timestamps. -// Note that the current date is always used; it’s not just a default value that you can override. -// -// auto_now_add: -// Automatically set the field to now when the object is first created. Useful for creation of timestamps. -// Note that the current date is always used; it’s not just a default value that you can override. -// -// eg: `orm:"auto_now"` or `orm:"auto_now_add"` -type TimeField orm.TimeField - -// Value return the time.Time -func (e TimeField) Value() time.Time { - return orm.TimeField(e).Value() -} - -// Set set the TimeField's value -func (e *TimeField) Set(d time.Time) { - (*orm.TimeField)(e).Set(d) -} - -// String convert time to string -func (e *TimeField) String() string { - return (*orm.TimeField)(e).String() -} - -// FieldType return enum type Date -func (e *TimeField) FieldType() int { - return (*orm.TimeField)(e).FieldType() -} - -// SetRaw convert the interface to time.Time. Allow string and time.Time -func (e *TimeField) SetRaw(value interface{}) error { - return (*orm.TimeField)(e).SetRaw(value) -} - -// RawValue return time value -func (e *TimeField) RawValue() interface{} { - return (*orm.TimeField)(e).RawValue() -} - -var _ Fielder = new(TimeField) - -// DateField A date, represented in go by a time.Time instance. -// only date values like 2006-01-02 -// Has a few extra, optional attr tag: -// -// auto_now: -// Automatically set the field to now every time the object is saved. Useful for “last-modified” timestamps. -// Note that the current date is always used; it’s not just a default value that you can override. -// -// auto_now_add: -// Automatically set the field to now when the object is first created. Useful for creation of timestamps. -// Note that the current date is always used; it’s not just a default value that you can override. -// -// eg: `orm:"auto_now"` or `orm:"auto_now_add"` -type DateField orm.DateField - -// Value return the time.Time -func (e DateField) Value() time.Time { - return orm.DateField(e).Value() -} - -// Set set the DateField's value -func (e *DateField) Set(d time.Time) { - (*orm.DateField)(e).Set(d) -} - -// String convert datetime to string -func (e *DateField) String() string { - return (*orm.DateField)(e).String() -} - -// FieldType return enum type Date -func (e *DateField) FieldType() int { - return (*orm.DateField)(e).FieldType() -} - -// SetRaw convert the interface to time.Time. Allow string and time.Time -func (e *DateField) SetRaw(value interface{}) error { - return (*orm.DateField)(e).SetRaw(value) -} - -// RawValue return Date value -func (e *DateField) RawValue() interface{} { - return (*orm.DateField)(e).RawValue() -} - -// verify DateField implement fielder interface -var _ Fielder = new(DateField) - -// DateTimeField A date, represented in go by a time.Time instance. -// datetime values like 2006-01-02 15:04:05 -// Takes the same extra arguments as DateField. -type DateTimeField orm.DateTimeField - -// Value return the datetime value -func (e DateTimeField) Value() time.Time { - return orm.DateTimeField(e).Value() -} - -// Set set the time.Time to datetime -func (e *DateTimeField) Set(d time.Time) { - (*orm.DateTimeField)(e).Set(d) -} - -// String return the time's String -func (e *DateTimeField) String() string { - return (*orm.DateTimeField)(e).String() -} - -// FieldType return the enum TypeDateTimeField -func (e *DateTimeField) FieldType() int { - return (*orm.DateTimeField)(e).FieldType() -} - -// SetRaw convert the string or time.Time to DateTimeField -func (e *DateTimeField) SetRaw(value interface{}) error { - return (*orm.DateTimeField)(e).SetRaw(value) -} - -// RawValue return the datetime value -func (e *DateTimeField) RawValue() interface{} { - return (*orm.DateTimeField)(e).RawValue() -} - -// verify datetime implement fielder -var _ Fielder = new(DateTimeField) - -// FloatField A floating-point number represented in go by a float32 value. -type FloatField orm.FloatField - -// Value return the FloatField value -func (e FloatField) Value() float64 { - return orm.FloatField(e).Value() -} - -// Set the Float64 -func (e *FloatField) Set(d float64) { - (*orm.FloatField)(e).Set(d) -} - -// String return the string -func (e *FloatField) String() string { - return (*orm.FloatField)(e).String() -} - -// FieldType return the enum type -func (e *FloatField) FieldType() int { - return (*orm.FloatField)(e).FieldType() -} - -// SetRaw converter interface Float64 float32 or string to FloatField -func (e *FloatField) SetRaw(value interface{}) error { - return (*orm.FloatField)(e).SetRaw(value) -} - -// RawValue return the FloatField value -func (e *FloatField) RawValue() interface{} { - return (*orm.FloatField)(e).RawValue() -} - -// verify FloatField implement Fielder -var _ Fielder = new(FloatField) - -// SmallIntegerField -32768 to 32767 -type SmallIntegerField orm.SmallIntegerField - -// Value return int16 value -func (e SmallIntegerField) Value() int16 { - return orm.SmallIntegerField(e).Value() -} - -// Set the SmallIntegerField value -func (e *SmallIntegerField) Set(d int16) { - (*orm.SmallIntegerField)(e).Set(d) -} - -// String convert smallint to string -func (e *SmallIntegerField) String() string { - return (*orm.SmallIntegerField)(e).String() -} - -// FieldType return enum type SmallIntegerField -func (e *SmallIntegerField) FieldType() int { - return (*orm.SmallIntegerField)(e).FieldType() -} - -// SetRaw convert interface int16/string to int16 -func (e *SmallIntegerField) SetRaw(value interface{}) error { - return (*orm.SmallIntegerField)(e).SetRaw(value) -} - -// RawValue return smallint value -func (e *SmallIntegerField) RawValue() interface{} { - return (*orm.SmallIntegerField)(e).RawValue() -} - -// verify SmallIntegerField implement Fielder -var _ Fielder = new(SmallIntegerField) - -// IntegerField -2147483648 to 2147483647 -type IntegerField orm.IntegerField - -// Value return the int32 -func (e IntegerField) Value() int32 { - return orm.IntegerField(e).Value() -} - -// Set IntegerField value -func (e *IntegerField) Set(d int32) { - (*orm.IntegerField)(e).Set(d) -} - -// String convert Int32 to string -func (e *IntegerField) String() string { - return (*orm.IntegerField)(e).String() -} - -// FieldType return the enum type -func (e *IntegerField) FieldType() int { - return (*orm.IntegerField)(e).FieldType() -} - -// SetRaw convert interface int32/string to int32 -func (e *IntegerField) SetRaw(value interface{}) error { - return (*orm.IntegerField)(e).SetRaw(value) -} - -// RawValue return IntegerField value -func (e *IntegerField) RawValue() interface{} { - return (*orm.IntegerField)(e).RawValue() -} - -// verify IntegerField implement Fielder -var _ Fielder = new(IntegerField) - -// BigIntegerField -9223372036854775808 to 9223372036854775807. -type BigIntegerField orm.BigIntegerField - -// Value return int64 -func (e BigIntegerField) Value() int64 { - return orm.BigIntegerField(e).Value() -} - -// Set the BigIntegerField value -func (e *BigIntegerField) Set(d int64) { - (*orm.BigIntegerField)(e).Set(d) -} - -// String convert BigIntegerField to string -func (e *BigIntegerField) String() string { - return (*orm.BigIntegerField)(e).String() -} - -// FieldType return enum type -func (e *BigIntegerField) FieldType() int { - return (*orm.BigIntegerField)(e).FieldType() -} - -// SetRaw convert interface int64/string to int64 -func (e *BigIntegerField) SetRaw(value interface{}) error { - return (*orm.BigIntegerField)(e).SetRaw(value) -} - -// RawValue return BigIntegerField value -func (e *BigIntegerField) RawValue() interface{} { - return (*orm.BigIntegerField)(e).RawValue() -} - -// verify BigIntegerField implement Fielder -var _ Fielder = new(BigIntegerField) - -// PositiveSmallIntegerField 0 to 65535 -type PositiveSmallIntegerField orm.PositiveSmallIntegerField - -// Value return uint16 -func (e PositiveSmallIntegerField) Value() uint16 { - return orm.PositiveSmallIntegerField(e).Value() -} - -// Set PositiveSmallIntegerField value -func (e *PositiveSmallIntegerField) Set(d uint16) { - (*orm.PositiveSmallIntegerField)(e).Set(d) -} - -// String convert uint16 to string -func (e *PositiveSmallIntegerField) String() string { - return (*orm.PositiveSmallIntegerField)(e).String() -} - -// FieldType return enum type -func (e *PositiveSmallIntegerField) FieldType() int { - return (*orm.PositiveSmallIntegerField)(e).FieldType() -} - -// SetRaw convert Interface uint16/string to uint16 -func (e *PositiveSmallIntegerField) SetRaw(value interface{}) error { - return (*orm.PositiveSmallIntegerField)(e).SetRaw(value) -} - -// RawValue returns PositiveSmallIntegerField value -func (e *PositiveSmallIntegerField) RawValue() interface{} { - return (*orm.PositiveSmallIntegerField)(e).RawValue() -} - -// verify PositiveSmallIntegerField implement Fielder -var _ Fielder = new(PositiveSmallIntegerField) - -// PositiveIntegerField 0 to 4294967295 -type PositiveIntegerField orm.PositiveIntegerField - -// Value return PositiveIntegerField value. Uint32 -func (e PositiveIntegerField) Value() uint32 { - return orm.PositiveIntegerField(e).Value() -} - -// Set the PositiveIntegerField value -func (e *PositiveIntegerField) Set(d uint32) { - (*orm.PositiveIntegerField)(e).Set(d) -} - -// String convert PositiveIntegerField to string -func (e *PositiveIntegerField) String() string { - return (*orm.PositiveIntegerField)(e).String() -} - -// FieldType return enum type -func (e *PositiveIntegerField) FieldType() int { - return (*orm.PositiveIntegerField)(e).FieldType() -} - -// SetRaw convert interface uint32/string to Uint32 -func (e *PositiveIntegerField) SetRaw(value interface{}) error { - return (*orm.PositiveIntegerField)(e).SetRaw(value) -} - -// RawValue return the PositiveIntegerField Value -func (e *PositiveIntegerField) RawValue() interface{} { - return (*orm.PositiveIntegerField)(e).RawValue() -} - -// verify PositiveIntegerField implement Fielder -var _ Fielder = new(PositiveIntegerField) - -// PositiveBigIntegerField 0 to 18446744073709551615 -type PositiveBigIntegerField orm.PositiveBigIntegerField - -// Value return uint64 -func (e PositiveBigIntegerField) Value() uint64 { - return orm.PositiveBigIntegerField(e).Value() -} - -// Set PositiveBigIntegerField value -func (e *PositiveBigIntegerField) Set(d uint64) { - (*orm.PositiveBigIntegerField)(e).Set(d) -} - -// String convert PositiveBigIntegerField to string -func (e *PositiveBigIntegerField) String() string { - return (*orm.PositiveBigIntegerField)(e).String() -} - -// FieldType return enum type -func (e *PositiveBigIntegerField) FieldType() int { - return (*orm.PositiveBigIntegerField)(e).FieldType() -} - -// SetRaw convert interface uint64/string to Uint64 -func (e *PositiveBigIntegerField) SetRaw(value interface{}) error { - return (*orm.PositiveBigIntegerField)(e).SetRaw(value) -} - -// RawValue return PositiveBigIntegerField value -func (e *PositiveBigIntegerField) RawValue() interface{} { - return (*orm.PositiveBigIntegerField)(e).RawValue() -} - -// verify PositiveBigIntegerField implement Fielder -var _ Fielder = new(PositiveBigIntegerField) - -// TextField A large text field. -type TextField orm.TextField - -// Value return TextField value -func (e TextField) Value() string { - return orm.TextField(e).Value() -} - -// Set the TextField value -func (e *TextField) Set(d string) { - (*orm.TextField)(e).Set(d) -} - -// String convert TextField to string -func (e *TextField) String() string { - return (*orm.TextField)(e).String() -} - -// FieldType return enum type -func (e *TextField) FieldType() int { - return (*orm.TextField)(e).FieldType() -} - -// SetRaw convert interface string to string -func (e *TextField) SetRaw(value interface{}) error { - return (*orm.TextField)(e).SetRaw(value) -} - -// RawValue return TextField value -func (e *TextField) RawValue() interface{} { - return (*orm.TextField)(e).RawValue() -} - -// verify TextField implement Fielder -var _ Fielder = new(TextField) - -// JSONField postgres json field. -type JSONField orm.JSONField - -// Value return JSONField value -func (j JSONField) Value() string { - return orm.JSONField(j).Value() -} - -// Set the JSONField value -func (j *JSONField) Set(d string) { - (*orm.JSONField)(j).Set(d) -} - -// String convert JSONField to string -func (j *JSONField) String() string { - return (*orm.JSONField)(j).String() -} - -// FieldType return enum type -func (j *JSONField) FieldType() int { - return (*orm.JSONField)(j).FieldType() -} - -// SetRaw convert interface string to string -func (j *JSONField) SetRaw(value interface{}) error { - return (*orm.JSONField)(j).SetRaw(value) -} - -// RawValue return JSONField value -func (j *JSONField) RawValue() interface{} { - return (*orm.JSONField)(j).RawValue() -} - -// verify JSONField implement Fielder -var _ Fielder = new(JSONField) - -// JsonbField postgres json field. -type JsonbField orm.JsonbField - -// Value return JsonbField value -func (j JsonbField) Value() string { - return orm.JsonbField(j).Value() -} - -// Set the JsonbField value -func (j *JsonbField) Set(d string) { - (*orm.JsonbField)(j).Set(d) -} - -// String convert JsonbField to string -func (j *JsonbField) String() string { - return (*orm.JsonbField)(j).String() -} - -// FieldType return enum type -func (j *JsonbField) FieldType() int { - return (*orm.JsonbField)(j).FieldType() -} - -// SetRaw convert interface string to string -func (j *JsonbField) SetRaw(value interface{}) error { - return (*orm.JsonbField)(j).SetRaw(value) -} - -// RawValue return JsonbField value -func (j *JsonbField) RawValue() interface{} { - return (*orm.JsonbField)(j).RawValue() -} - -// verify JsonbField implement Fielder -var _ Fielder = new(JsonbField) diff --git a/adapter/orm/orm.go b/adapter/orm/orm.go deleted file mode 100644 index 15df76ed..00000000 --- a/adapter/orm/orm.go +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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. - -// +build go1.8 - -// Package orm provide ORM for MySQL/PostgreSQL/sqlite -// Simple Usage -// -// package main -// -// import ( -// "fmt" -// "github.com/astaxie/beego/orm" -// _ "github.com/go-sql-driver/mysql" // import your used driver -// ) -// -// // Model Struct -// type User struct { -// Id int `orm:"auto"` -// Name string `orm:"size(100)"` -// } -// -// func init() { -// orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30) -// } -// -// func main() { -// o := orm.NewOrm() -// user := User{Name: "slene"} -// // insert -// id, err := o.Insert(&user) -// // update -// user.Name = "astaxie" -// num, err := o.Update(&user) -// // read one -// u := User{Id: user.Id} -// err = o.Read(&u) -// // delete -// num, err = o.Delete(&u) -// } -// -// more docs: http://beego.me/docs/mvc/model/overview.md -package orm - -import ( - "context" - "database/sql" - "errors" - - "github.com/astaxie/beego/client/orm" - "github.com/astaxie/beego/client/orm/hints" - "github.com/astaxie/beego/core/utils" -) - -// DebugQueries define the debug -const ( - DebugQueries = iota -) - -// Define common vars -var ( - Debug = orm.Debug - DebugLog = orm.DebugLog - DefaultRowsLimit = orm.DefaultRowsLimit - DefaultRelsDepth = orm.DefaultRelsDepth - DefaultTimeLoc = orm.DefaultTimeLoc - ErrTxHasBegan = errors.New(" transaction already begin") - ErrTxDone = errors.New(" transaction not begin") - ErrMultiRows = errors.New(" return multi rows") - ErrNoRows = errors.New(" no row found") - ErrStmtClosed = errors.New(" stmt already closed") - ErrArgs = errors.New(" args error may be empty") - ErrNotImplement = errors.New("have not implement") -) - -type ormer struct { - delegate orm.Ormer - txDelegate orm.TxOrmer - isTx bool -} - -var _ Ormer = new(ormer) - -// read data to model -func (o *ormer) Read(md interface{}, cols ...string) error { - if o.isTx { - return o.txDelegate.Read(md, cols...) - } - return o.delegate.Read(md, cols...) -} - -// read data to model, like Read(), but use "SELECT FOR UPDATE" form -func (o *ormer) ReadForUpdate(md interface{}, cols ...string) error { - if o.isTx { - return o.txDelegate.ReadForUpdate(md, cols...) - } - return o.delegate.ReadForUpdate(md, cols...) -} - -// Try to read a row from the database, or insert one if it doesn't exist -func (o *ormer) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) { - if o.isTx { - return o.txDelegate.ReadOrCreate(md, col1, cols...) - } - return o.delegate.ReadOrCreate(md, col1, cols...) -} - -// insert model data to database -func (o *ormer) Insert(md interface{}) (int64, error) { - if o.isTx { - return o.txDelegate.Insert(md) - } - return o.delegate.Insert(md) -} - -// insert some models to database -func (o *ormer) InsertMulti(bulk int, mds interface{}) (int64, error) { - if o.isTx { - return o.txDelegate.InsertMulti(bulk, mds) - } - return o.delegate.InsertMulti(bulk, mds) -} - -// InsertOrUpdate data to database -func (o *ormer) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) { - if o.isTx { - return o.txDelegate.InsertOrUpdate(md, colConflitAndArgs...) - } - return o.delegate.InsertOrUpdate(md, colConflitAndArgs...) -} - -// update model to database. -// cols set the columns those want to update. -func (o *ormer) Update(md interface{}, cols ...string) (int64, error) { - if o.isTx { - return o.txDelegate.Update(md, cols...) - } - return o.delegate.Update(md, cols...) -} - -// delete model in database -// cols shows the delete conditions values read from. default is pk -func (o *ormer) Delete(md interface{}, cols ...string) (int64, error) { - if o.isTx { - return o.txDelegate.Delete(md, cols...) - } - return o.delegate.Delete(md, cols...) -} - -// create a models to models queryer -func (o *ormer) QueryM2M(md interface{}, name string) QueryM2Mer { - if o.isTx { - return o.txDelegate.QueryM2M(md, name) - } - return o.delegate.QueryM2M(md, name) -} - -// load related models to md model. -// args are limit, offset int and order string. -// -// example: -// orm.LoadRelated(post,"Tags") -// for _,tag := range post.Tags{...} -// -// make sure the relation is defined in model struct tags. -func (o *ormer) LoadRelated(md interface{}, name string, args ...interface{}) (int64, error) { - kvs := make([]utils.KV, 0, 4) - for i, arg := range args { - switch i { - case 0: - if v, ok := arg.(bool); ok { - if v { - kvs = append(kvs, hints.DefaultRelDepth()) - } - } else if v, ok := arg.(int); ok { - kvs = append(kvs, hints.RelDepth(v)) - } - case 1: - kvs = append(kvs, hints.Limit(orm.ToInt64(arg))) - case 2: - kvs = append(kvs, hints.Offset(orm.ToInt64(arg))) - case 3: - kvs = append(kvs, hints.Offset(orm.ToInt64(arg))) - } - } - if o.isTx { - return o.txDelegate.LoadRelated(md, name, kvs...) - } - return o.delegate.LoadRelated(md, name, kvs...) -} - -// return a QuerySeter for table operations. -// table name can be string or struct. -// e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)), -func (o *ormer) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) { - if o.isTx { - return o.txDelegate.QueryTable(ptrStructOrTableName) - } - return o.delegate.QueryTable(ptrStructOrTableName) -} - -// switch to another registered database driver by given name. -func (o *ormer) Using(name string) error { - if o.isTx { - return ErrTxHasBegan - } - o.delegate = orm.NewOrmUsingDB(name) - return nil -} - -// begin transaction -func (o *ormer) Begin() error { - if o.isTx { - return ErrTxHasBegan - } - return o.BeginTx(context.Background(), nil) -} - -func (o *ormer) BeginTx(ctx context.Context, opts *sql.TxOptions) error { - if o.isTx { - return ErrTxHasBegan - } - txOrmer, err := o.delegate.BeginWithCtxAndOpts(ctx, opts) - if err != nil { - return err - } - o.txDelegate = txOrmer - o.isTx = true - return nil -} - -// commit transaction -func (o *ormer) Commit() error { - if !o.isTx { - return ErrTxDone - } - err := o.txDelegate.Commit() - if err == nil { - o.isTx = false - o.txDelegate = nil - } else if err == sql.ErrTxDone { - return ErrTxDone - } - return err -} - -// rollback transaction -func (o *ormer) Rollback() error { - if !o.isTx { - return ErrTxDone - } - err := o.txDelegate.Rollback() - if err == nil { - o.isTx = false - o.txDelegate = nil - } else if err == sql.ErrTxDone { - return ErrTxDone - } - return err -} - -// return a raw query seter for raw sql string. -func (o *ormer) Raw(query string, args ...interface{}) RawSeter { - if o.isTx { - return o.txDelegate.Raw(query, args...) - } - return o.delegate.Raw(query, args...) -} - -// return current using database Driver -func (o *ormer) Driver() Driver { - if o.isTx { - return o.txDelegate.Driver() - } - return o.delegate.Driver() -} - -// return sql.DBStats for current database -func (o *ormer) DBStats() *sql.DBStats { - if o.isTx { - return o.txDelegate.DBStats() - } - return o.delegate.DBStats() -} - -// NewOrm create new orm -func NewOrm() Ormer { - o := orm.NewOrm() - return &ormer{ - delegate: o, - } -} - -// NewOrmWithDB create a new ormer object with specify *sql.DB for query -func NewOrmWithDB(driverName, aliasName string, db *sql.DB) (Ormer, error) { - o, err := orm.NewOrmWithDB(driverName, aliasName, db) - if err != nil { - return nil, err - } - return &ormer{ - delegate: o, - }, nil -} diff --git a/adapter/orm/orm_conds.go b/adapter/orm/orm_conds.go deleted file mode 100644 index f70f0f5b..00000000 --- a/adapter/orm/orm_conds.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "github.com/astaxie/beego/client/orm" -) - -// ExprSep define the expression separation -const ( - ExprSep = "__" -) - -// Condition struct. -// work for WHERE conditions. -type Condition orm.Condition - -// NewCondition return new condition struct -func NewCondition() *Condition { - return (*Condition)(orm.NewCondition()) -} - -// Raw add raw sql to condition -func (c Condition) Raw(expr string, sql string) *Condition { - return (*Condition)((orm.Condition)(c).Raw(expr, sql)) -} - -// And add expression to condition -func (c Condition) And(expr string, args ...interface{}) *Condition { - return (*Condition)((orm.Condition)(c).And(expr, args...)) -} - -// AndNot add NOT expression to condition -func (c Condition) AndNot(expr string, args ...interface{}) *Condition { - return (*Condition)((orm.Condition)(c).AndNot(expr, args...)) -} - -// AndCond combine a condition to current condition -func (c *Condition) AndCond(cond *Condition) *Condition { - return (*Condition)((*orm.Condition)(c).AndCond((*orm.Condition)(cond))) -} - -// AndNotCond combine a AND NOT condition to current condition -func (c *Condition) AndNotCond(cond *Condition) *Condition { - return (*Condition)((*orm.Condition)(c).AndNotCond((*orm.Condition)(cond))) -} - -// Or add OR expression to condition -func (c Condition) Or(expr string, args ...interface{}) *Condition { - return (*Condition)((orm.Condition)(c).Or(expr, args...)) -} - -// OrNot add OR NOT expression to condition -func (c Condition) OrNot(expr string, args ...interface{}) *Condition { - return (*Condition)((orm.Condition)(c).OrNot(expr, args...)) -} - -// OrCond combine a OR condition to current condition -func (c *Condition) OrCond(cond *Condition) *Condition { - return (*Condition)((*orm.Condition)(c).OrCond((*orm.Condition)(cond))) -} - -// OrNotCond combine a OR NOT condition to current condition -func (c *Condition) OrNotCond(cond *Condition) *Condition { - return (*Condition)((*orm.Condition)(c).OrNotCond((*orm.Condition)(cond))) -} - -// IsEmpty check the condition arguments are empty or not. -func (c *Condition) IsEmpty() bool { - return (*orm.Condition)(c).IsEmpty() -} diff --git a/adapter/orm/orm_log.go b/adapter/orm/orm_log.go deleted file mode 100644 index 3ff7f01c..00000000 --- a/adapter/orm/orm_log.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "io" - - "github.com/astaxie/beego/client/orm" -) - -// Log implement the log.Logger -type Log orm.Log - -// costomer log func -var LogFunc = orm.LogFunc - -// NewLog set io.Writer to create a Logger. -func NewLog(out io.Writer) *Log { - return (*Log)(orm.NewLog(out)) -} diff --git a/adapter/orm/orm_queryset.go b/adapter/orm/orm_queryset.go deleted file mode 100644 index 1926a6c0..00000000 --- a/adapter/orm/orm_queryset.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "github.com/astaxie/beego/client/orm" -) - -// define Col operations -const ( - ColAdd = orm.ColAdd - ColMinus = orm.ColMinus - ColMultiply = orm.ColMultiply - ColExcept = orm.ColExcept - ColBitAnd = orm.ColBitAnd - ColBitRShift = orm.ColBitRShift - ColBitLShift = orm.ColBitLShift - ColBitXOR = orm.ColBitXOR - ColBitOr = orm.ColBitOr -) diff --git a/adapter/orm/qb.go b/adapter/orm/qb.go deleted file mode 100644 index 63eaed8a..00000000 --- a/adapter/orm/qb.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "github.com/astaxie/beego/client/orm" -) - -// QueryBuilder is the Query builder interface -type QueryBuilder orm.QueryBuilder - -// NewQueryBuilder return the QueryBuilder -func NewQueryBuilder(driver string) (qb QueryBuilder, err error) { - return orm.NewQueryBuilder(driver) -} diff --git a/adapter/orm/qb_mysql.go b/adapter/orm/qb_mysql.go deleted file mode 100644 index ef87ebab..00000000 --- a/adapter/orm/qb_mysql.go +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "github.com/astaxie/beego/client/orm" -) - -// CommaSpace is the separation -const CommaSpace = orm.CommaSpace - -// MySQLQueryBuilder is the SQL build -type MySQLQueryBuilder orm.MySQLQueryBuilder - -// Select will join the fields -func (qb *MySQLQueryBuilder) Select(fields ...string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).Select(fields...) -} - -// ForUpdate add the FOR UPDATE clause -func (qb *MySQLQueryBuilder) ForUpdate() QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).ForUpdate() -} - -// From join the tables -func (qb *MySQLQueryBuilder) From(tables ...string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).From(tables...) -} - -// InnerJoin INNER JOIN the table -func (qb *MySQLQueryBuilder) InnerJoin(table string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).InnerJoin(table) -} - -// LeftJoin LEFT JOIN the table -func (qb *MySQLQueryBuilder) LeftJoin(table string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).LeftJoin(table) -} - -// RightJoin RIGHT JOIN the table -func (qb *MySQLQueryBuilder) RightJoin(table string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).RightJoin(table) -} - -// On join with on cond -func (qb *MySQLQueryBuilder) On(cond string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).On(cond) -} - -// Where join the Where cond -func (qb *MySQLQueryBuilder) Where(cond string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).Where(cond) -} - -// And join the and cond -func (qb *MySQLQueryBuilder) And(cond string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).And(cond) -} - -// Or join the or cond -func (qb *MySQLQueryBuilder) Or(cond string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).Or(cond) -} - -// In join the IN (vals) -func (qb *MySQLQueryBuilder) In(vals ...string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).In(vals...) -} - -// OrderBy join the Order by fields -func (qb *MySQLQueryBuilder) OrderBy(fields ...string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).OrderBy(fields...) -} - -// Asc join the asc -func (qb *MySQLQueryBuilder) Asc() QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).Asc() -} - -// Desc join the desc -func (qb *MySQLQueryBuilder) Desc() QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).Desc() -} - -// Limit join the limit num -func (qb *MySQLQueryBuilder) Limit(limit int) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).Limit(limit) -} - -// Offset join the offset num -func (qb *MySQLQueryBuilder) Offset(offset int) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).Offset(offset) -} - -// GroupBy join the Group by fields -func (qb *MySQLQueryBuilder) GroupBy(fields ...string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).GroupBy(fields...) -} - -// Having join the Having cond -func (qb *MySQLQueryBuilder) Having(cond string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).Having(cond) -} - -// Update join the update table -func (qb *MySQLQueryBuilder) Update(tables ...string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).Update(tables...) -} - -// Set join the set kv -func (qb *MySQLQueryBuilder) Set(kv ...string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).Set(kv...) -} - -// Delete join the Delete tables -func (qb *MySQLQueryBuilder) Delete(tables ...string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).Delete(tables...) -} - -// InsertInto join the insert SQL -func (qb *MySQLQueryBuilder) InsertInto(table string, fields ...string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).InsertInto(table, fields...) -} - -// Values join the Values(vals) -func (qb *MySQLQueryBuilder) Values(vals ...string) QueryBuilder { - return (*orm.MySQLQueryBuilder)(qb).Values(vals...) -} - -// Subquery join the sub as alias -func (qb *MySQLQueryBuilder) Subquery(sub string, alias string) string { - return (*orm.MySQLQueryBuilder)(qb).Subquery(sub, alias) -} - -// String join all Tokens -func (qb *MySQLQueryBuilder) String() string { - return (*orm.MySQLQueryBuilder)(qb).String() -} diff --git a/adapter/orm/query_setter_adapter.go b/adapter/orm/query_setter_adapter.go deleted file mode 100644 index d6c268b6..00000000 --- a/adapter/orm/query_setter_adapter.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 orm - -import ( - "github.com/astaxie/beego/client/orm" -) - -type baseQuerySetter struct { -} - -func (b *baseQuerySetter) ForceIndex(indexes ...string) orm.QuerySeter { - panic("you should not invoke this method.") -} - -func (b *baseQuerySetter) UseIndex(indexes ...string) orm.QuerySeter { - panic("you should not invoke this method.") -} - -func (b *baseQuerySetter) IgnoreIndex(indexes ...string) orm.QuerySeter { - panic("you should not invoke this method.") -} diff --git a/adapter/orm/types.go b/adapter/orm/types.go deleted file mode 100644 index 6db5066c..00000000 --- a/adapter/orm/types.go +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "context" - "database/sql" - - "github.com/astaxie/beego/client/orm" -) - -// Params stores the Params -type Params orm.Params - -// ParamsList stores paramslist -type ParamsList orm.ParamsList - -// Driver define database driver -type Driver orm.Driver - -// Fielder define field info -type Fielder orm.Fielder - -// Ormer define the orm interface -type Ormer interface { - // read data to model - // for example: - // this will find User by Id field - // u = &User{Id: user.Id} - // err = Ormer.Read(u) - // this will find User by UserName field - // u = &User{UserName: "astaxie", Password: "pass"} - // err = Ormer.Read(u, "UserName") - Read(md interface{}, cols ...string) error - // Like Read(), but with "FOR UPDATE" clause, useful in transaction. - // Some databases are not support this feature. - ReadForUpdate(md interface{}, cols ...string) error - // Try to read a row from the database, or insert one if it doesn't exist - ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) - // insert model data to database - // for example: - // user := new(User) - // id, err = Ormer.Insert(user) - // user must be a pointer and Insert will set user's pk field - Insert(interface{}) (int64, error) - // mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value") - // if colu type is integer : can use(+-*/), string : convert(colu,"value") - // postgres: InsertOrUpdate(model,"conflictColumnName") or InsertOrUpdate(model,"conflictColumnName","colu=colu+value") - // if colu type is integer : can use(+-*/), string : colu || "value" - InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) - // insert some models to database - InsertMulti(bulk int, mds interface{}) (int64, error) - // update model to database. - // cols set the columns those want to update. - // find model by Id(pk) field and update columns specified by fields, if cols is null then update all columns - // for example: - // user := User{Id: 2} - // user.Langs = append(user.Langs, "zh-CN", "en-US") - // user.Extra.Name = "beego" - // user.Extra.Data = "orm" - // num, err = Ormer.Update(&user, "Langs", "Extra") - Update(md interface{}, cols ...string) (int64, error) - // delete model in database - Delete(md interface{}, cols ...string) (int64, error) - // load related models to md model. - // args are limit, offset int and order string. - // - // example: - // Ormer.LoadRelated(post,"Tags") - // for _,tag := range post.Tags{...} - // args[0] bool true useDefaultRelsDepth ; false depth 0 - // args[0] int loadRelationDepth - // args[1] int limit default limit 1000 - // args[2] int offset default offset 0 - // args[3] string order for example : "-Id" - // make sure the relation is defined in model struct tags. - LoadRelated(md interface{}, name string, args ...interface{}) (int64, error) - // create a models to models queryer - // for example: - // post := Post{Id: 4} - // m2m := Ormer.QueryM2M(&post, "Tags") - QueryM2M(md interface{}, name string) QueryM2Mer - // return a QuerySeter for table operations. - // table name can be string or struct. - // e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)), - QueryTable(ptrStructOrTableName interface{}) QuerySeter - // switch to another registered database driver by given name. - Using(name string) error - // begin transaction - // for example: - // o := NewOrm() - // err := o.Begin() - // ... - // err = o.Rollback() - Begin() error - // begin transaction with provided context and option - // the provided context is used until the transaction is committed or rolled back. - // if the context is canceled, the transaction will be rolled back. - // the provided TxOptions is optional and may be nil if defaults should be used. - // if a non-default isolation level is used that the driver doesn't support, an error will be returned. - // for example: - // o := NewOrm() - // err := o.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead}) - // ... - // err = o.Rollback() - BeginTx(ctx context.Context, opts *sql.TxOptions) error - // commit transaction - Commit() error - // rollback transaction - Rollback() error - // return a raw query seter for raw sql string. - // for example: - // ormer.Raw("UPDATE `user` SET `user_name` = ? WHERE `user_name` = ?", "slene", "testing").Exec() - // // update user testing's name to slene - Raw(query string, args ...interface{}) RawSeter - Driver() Driver - DBStats() *sql.DBStats -} - -// Inserter insert prepared statement -type Inserter orm.Inserter - -// QuerySeter query seter -type QuerySeter orm.QuerySeter - -// QueryM2Mer model to model query struct -// all operations are on the m2m table only, will not affect the origin model table -type QueryM2Mer orm.QueryM2Mer - -// RawPreparer raw query statement -type RawPreparer orm.RawPreparer - -// RawSeter raw query seter -// create From Ormer.Raw -// for example: -// sql := fmt.Sprintf("SELECT %sid%s,%sname%s FROM %suser%s WHERE id = ?",Q,Q,Q,Q,Q,Q) -// rs := Ormer.Raw(sql, 1) -type RawSeter orm.RawSeter diff --git a/adapter/orm/utils.go b/adapter/orm/utils.go deleted file mode 100644 index 37ba86d8..00000000 --- a/adapter/orm/utils.go +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "fmt" - "reflect" - "strconv" - "strings" - "time" - - "github.com/astaxie/beego/client/orm" -) - -type fn func(string) string - -var ( - nameStrategyMap = map[string]fn{ - defaultNameStrategy: snakeString, - SnakeAcronymNameStrategy: snakeStringWithAcronym, - } - defaultNameStrategy = "snakeString" - SnakeAcronymNameStrategy = "snakeStringWithAcronym" - nameStrategy = defaultNameStrategy -) - -// StrTo is the target string -type StrTo orm.StrTo - -// Set string -func (f *StrTo) Set(v string) { - (*orm.StrTo)(f).Set(v) -} - -// Clear string -func (f *StrTo) Clear() { - (*orm.StrTo)(f).Clear() -} - -// Exist check string exist -func (f StrTo) Exist() bool { - return orm.StrTo(f).Exist() -} - -// Bool string to bool -func (f StrTo) Bool() (bool, error) { - return orm.StrTo(f).Bool() -} - -// Float32 string to float32 -func (f StrTo) Float32() (float32, error) { - return orm.StrTo(f).Float32() -} - -// Float64 string to float64 -func (f StrTo) Float64() (float64, error) { - return orm.StrTo(f).Float64() -} - -// Int string to int -func (f StrTo) Int() (int, error) { - return orm.StrTo(f).Int() -} - -// Int8 string to int8 -func (f StrTo) Int8() (int8, error) { - return orm.StrTo(f).Int8() -} - -// Int16 string to int16 -func (f StrTo) Int16() (int16, error) { - return orm.StrTo(f).Int16() -} - -// Int32 string to int32 -func (f StrTo) Int32() (int32, error) { - return orm.StrTo(f).Int32() -} - -// Int64 string to int64 -func (f StrTo) Int64() (int64, error) { - return orm.StrTo(f).Int64() -} - -// Uint string to uint -func (f StrTo) Uint() (uint, error) { - return orm.StrTo(f).Uint() -} - -// Uint8 string to uint8 -func (f StrTo) Uint8() (uint8, error) { - return orm.StrTo(f).Uint8() -} - -// Uint16 string to uint16 -func (f StrTo) Uint16() (uint16, error) { - return orm.StrTo(f).Uint16() -} - -// Uint32 string to uint32 -func (f StrTo) Uint32() (uint32, error) { - return orm.StrTo(f).Uint32() -} - -// Uint64 string to uint64 -func (f StrTo) Uint64() (uint64, error) { - return orm.StrTo(f).Uint64() -} - -// String string to string -func (f StrTo) String() string { - return orm.StrTo(f).String() -} - -// ToStr interface to string -func ToStr(value interface{}, args ...int) (s string) { - switch v := value.(type) { - case bool: - s = strconv.FormatBool(v) - case float32: - s = strconv.FormatFloat(float64(v), 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 32)) - case float64: - s = strconv.FormatFloat(v, 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 64)) - case int: - s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) - case int8: - s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) - case int16: - s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) - case int32: - s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) - case int64: - s = strconv.FormatInt(v, argInt(args).Get(0, 10)) - case uint: - s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) - case uint8: - s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) - case uint16: - s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) - case uint32: - s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) - case uint64: - s = strconv.FormatUint(v, argInt(args).Get(0, 10)) - case string: - s = v - case []byte: - s = string(v) - default: - s = fmt.Sprintf("%v", v) - } - return s -} - -// ToInt64 interface to int64 -func ToInt64(value interface{}) (d int64) { - val := reflect.ValueOf(value) - switch value.(type) { - case int, int8, int16, int32, int64: - d = val.Int() - case uint, uint8, uint16, uint32, uint64: - d = int64(val.Uint()) - default: - panic(fmt.Errorf("ToInt64 need numeric not `%T`", value)) - } - return -} - -func snakeStringWithAcronym(s string) string { - data := make([]byte, 0, len(s)*2) - num := len(s) - for i := 0; i < num; i++ { - d := s[i] - before := false - after := false - if i > 0 { - before = s[i-1] >= 'a' && s[i-1] <= 'z' - } - if i+1 < num { - after = s[i+1] >= 'a' && s[i+1] <= 'z' - } - if i > 0 && d >= 'A' && d <= 'Z' && (before || after) { - data = append(data, '_') - } - data = append(data, d) - } - return strings.ToLower(string(data[:])) -} - -// snake string, XxYy to xx_yy , XxYY to xx_y_y -func snakeString(s string) string { - data := make([]byte, 0, len(s)*2) - j := false - num := len(s) - for i := 0; i < num; i++ { - d := s[i] - if i > 0 && d >= 'A' && d <= 'Z' && j { - data = append(data, '_') - } - if d != '_' { - j = true - } - data = append(data, d) - } - return strings.ToLower(string(data[:])) -} - -// SetNameStrategy set different name strategy -func SetNameStrategy(s string) { - if SnakeAcronymNameStrategy != s { - nameStrategy = defaultNameStrategy - } - nameStrategy = s -} - -// camel string, xx_yy to XxYy -func camelString(s string) string { - data := make([]byte, 0, len(s)) - flag, num := true, len(s)-1 - for i := 0; i <= num; i++ { - d := s[i] - if d == '_' { - flag = true - continue - } else if flag { - if d >= 'a' && d <= 'z' { - d = d - 32 - } - flag = false - } - data = append(data, d) - } - return string(data[:]) -} - -type argString []string - -// get string by index from string slice -func (a argString) Get(i int, args ...string) (r string) { - if i >= 0 && i < len(a) { - r = a[i] - } else if len(args) > 0 { - r = args[0] - } - return -} - -type argInt []int - -// get int by index from int slice -func (a argInt) Get(i int, args ...int) (r int) { - if i >= 0 && i < len(a) { - r = a[i] - } - if len(args) > 0 { - r = args[0] - } - return -} - -// parse time to string with location -func timeParse(dateString, format string) (time.Time, error) { - tp, err := time.ParseInLocation(format, dateString, DefaultTimeLoc) - return tp, err -} - -// get pointer indirect type -func indirectType(v reflect.Type) reflect.Type { - switch v.Kind() { - case reflect.Ptr: - return indirectType(v.Elem()) - default: - return v - } -} diff --git a/adapter/plugins/apiauth/apiauth.go b/adapter/plugins/apiauth/apiauth.go deleted file mode 100644 index 90311d8f..00000000 --- a/adapter/plugins/apiauth/apiauth.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 apiauth provides handlers to enable apiauth support. -// -// Simple Usage: -// import( -// "github.com/astaxie/beego" -// "github.com/astaxie/beego/plugins/apiauth" -// ) -// -// func main(){ -// // apiauth every request -// beego.InsertFilter("*", beego.BeforeRouter,apiauth.APIBaiscAuth("appid","appkey")) -// beego.Run() -// } -// -// Advanced Usage: -// -// func getAppSecret(appid string) string { -// // get appsecret by appid -// // maybe store in configure, maybe in database -// } -// -// beego.InsertFilter("*", beego.BeforeRouter,apiauth.APISecretAuth(getAppSecret, 360)) -// -// Information: -// -// In the request user should include these params in the query -// -// 1. appid -// -// appid is assigned to the application -// -// 2. signature -// -// get the signature use apiauth.Signature() -// -// when you send to server remember use url.QueryEscape() -// -// 3. timestamp: -// -// send the request time, the format is yyyy-mm-dd HH:ii:ss -// -package apiauth - -import ( - "net/url" - - beego "github.com/astaxie/beego/adapter" - "github.com/astaxie/beego/adapter/context" - beecontext "github.com/astaxie/beego/server/web/context" - "github.com/astaxie/beego/server/web/filter/apiauth" -) - -// AppIDToAppSecret is used to get appsecret throw appid -type AppIDToAppSecret apiauth.AppIDToAppSecret - -// APIBasicAuth use the basic appid/appkey as the AppIdToAppSecret -func APIBasicAuth(appid, appkey string) beego.FilterFunc { - f := apiauth.APIBasicAuth(appid, appkey) - return func(c *context.Context) { - f((*beecontext.Context)(c)) - } -} - -// APIBaiscAuth calls APIBasicAuth for previous callers -func APIBaiscAuth(appid, appkey string) beego.FilterFunc { - return APIBasicAuth(appid, appkey) -} - -// APISecretAuth use AppIdToAppSecret verify and -func APISecretAuth(f AppIDToAppSecret, timeout int) beego.FilterFunc { - ft := apiauth.APISecretAuth(apiauth.AppIDToAppSecret(f), timeout) - return func(ctx *context.Context) { - ft((*beecontext.Context)(ctx)) - } -} - -// Signature used to generate signature with the appsecret/method/params/RequestURI -func Signature(appsecret, method string, params url.Values, requestURL string) string { - return apiauth.Signature(appsecret, method, params, requestURL) -} diff --git a/adapter/plugins/auth/basic.go b/adapter/plugins/auth/basic.go deleted file mode 100644 index 578a16d9..00000000 --- a/adapter/plugins/auth/basic.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 auth provides handlers to enable basic auth support. -// Simple Usage: -// import( -// "github.com/astaxie/beego" -// "github.com/astaxie/beego/plugins/auth" -// ) -// -// func main(){ -// // authenticate every request -// beego.InsertFilter("*", beego.BeforeRouter,auth.Basic("username","secretpassword")) -// beego.Run() -// } -// -// -// Advanced Usage: -// -// func SecretAuth(username, password string) bool { -// return username == "astaxie" && password == "helloBeego" -// } -// authPlugin := auth.NewBasicAuthenticator(SecretAuth, "Authorization Required") -// beego.InsertFilter("*", beego.BeforeRouter,authPlugin) -package auth - -import ( - "net/http" - - beego "github.com/astaxie/beego/adapter" - "github.com/astaxie/beego/adapter/context" - beecontext "github.com/astaxie/beego/server/web/context" - "github.com/astaxie/beego/server/web/filter/auth" -) - -// Basic is the http basic auth -func Basic(username string, password string) beego.FilterFunc { - return func(c *context.Context) { - f := auth.Basic(username, password) - f((*beecontext.Context)(c)) - } -} - -// NewBasicAuthenticator return the BasicAuth -func NewBasicAuthenticator(secrets SecretProvider, realm string) beego.FilterFunc { - f := auth.NewBasicAuthenticator(auth.SecretProvider(secrets), realm) - return func(c *context.Context) { - f((*beecontext.Context)(c)) - } -} - -// SecretProvider is the SecretProvider function -type SecretProvider auth.SecretProvider - -// BasicAuth store the SecretProvider and Realm -type BasicAuth auth.BasicAuth - -// CheckAuth Checks the username/password combination from the request. Returns -// either an empty string (authentication failed) or the name of the -// authenticated user. -// Supports MD5 and SHA1 password entries -func (a *BasicAuth) CheckAuth(r *http.Request) string { - return (*auth.BasicAuth)(a).CheckAuth(r) -} - -// RequireAuth http.Handler for BasicAuth which initiates the authentication process -// (or requires reauthentication). -func (a *BasicAuth) RequireAuth(w http.ResponseWriter, r *http.Request) { - (*auth.BasicAuth)(a).RequireAuth(w, r) -} diff --git a/adapter/plugins/authz/authz.go b/adapter/plugins/authz/authz.go deleted file mode 100644 index 3f84467e..00000000 --- a/adapter/plugins/authz/authz.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 authz provides handlers to enable ACL, RBAC, ABAC authorization support. -// Simple Usage: -// import( -// "github.com/astaxie/beego" -// "github.com/astaxie/beego/plugins/authz" -// "github.com/casbin/casbin" -// ) -// -// func main(){ -// // mediate the access for every request -// beego.InsertFilter("*", beego.BeforeRouter, authz.NewAuthorizer(casbin.NewEnforcer("authz_model.conf", "authz_policy.csv"))) -// beego.Run() -// } -// -// -// Advanced Usage: -// -// func main(){ -// e := casbin.NewEnforcer("authz_model.conf", "") -// e.AddRoleForUser("alice", "admin") -// e.AddPolicy(...) -// -// beego.InsertFilter("*", beego.BeforeRouter, authz.NewAuthorizer(e)) -// beego.Run() -// } -package authz - -import ( - "net/http" - - "github.com/casbin/casbin" - - beego "github.com/astaxie/beego/adapter" - "github.com/astaxie/beego/adapter/context" - beecontext "github.com/astaxie/beego/server/web/context" - "github.com/astaxie/beego/server/web/filter/authz" -) - -// NewAuthorizer returns the authorizer. -// Use a casbin enforcer as input -func NewAuthorizer(e *casbin.Enforcer) beego.FilterFunc { - f := authz.NewAuthorizer(e) - return func(context *context.Context) { - f((*beecontext.Context)(context)) - } -} - -// BasicAuthorizer stores the casbin handler -type BasicAuthorizer authz.BasicAuthorizer - -// GetUserName gets the user name from the request. -// Currently, only HTTP basic authentication is supported -func (a *BasicAuthorizer) GetUserName(r *http.Request) string { - return (*authz.BasicAuthorizer)(a).GetUserName(r) -} - -// CheckPermission checks the user/method/path combination from the request. -// Returns true (permission granted) or false (permission forbidden) -func (a *BasicAuthorizer) CheckPermission(r *http.Request) bool { - return (*authz.BasicAuthorizer)(a).CheckPermission(r) -} - -// RequirePermission returns the 403 Forbidden to the client -func (a *BasicAuthorizer) RequirePermission(w http.ResponseWriter) { - (*authz.BasicAuthorizer)(a).RequirePermission(w) -} diff --git a/adapter/plugins/cors/cors.go b/adapter/plugins/cors/cors.go deleted file mode 100644 index a15d5417..00000000 --- a/adapter/plugins/cors/cors.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 cors provides handlers to enable CORS support. -// Usage -// import ( -// "github.com/astaxie/beego" -// "github.com/astaxie/beego/plugins/cors" -// ) -// -// func main() { -// // CORS for https://foo.* origins, allowing: -// // - PUT and PATCH methods -// // - Origin header -// // - Credentials share -// beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{ -// AllowOrigins: []string{"https://*.foo.com"}, -// AllowMethods: []string{"PUT", "PATCH"}, -// AllowHeaders: []string{"Origin"}, -// ExposeHeaders: []string{"Content-Length"}, -// AllowCredentials: true, -// })) -// beego.Run() -// } -package cors - -import ( - beego "github.com/astaxie/beego/adapter" - beecontext "github.com/astaxie/beego/server/web/context" - "github.com/astaxie/beego/server/web/filter/cors" - - "github.com/astaxie/beego/adapter/context" -) - -// Options represents Access Control options. -type Options cors.Options - -// Header converts options into CORS headers. -func (o *Options) Header(origin string) (headers map[string]string) { - return (*cors.Options)(o).Header(origin) -} - -// PreflightHeader converts options into CORS headers for a preflight response. -func (o *Options) PreflightHeader(origin, rMethod, rHeaders string) (headers map[string]string) { - return (*cors.Options)(o).PreflightHeader(origin, rMethod, rHeaders) -} - -// IsOriginAllowed looks up if the origin matches one of the patterns -// generated from Options.AllowOrigins patterns. -func (o *Options) IsOriginAllowed(origin string) bool { - return (*cors.Options)(o).IsOriginAllowed(origin) -} - -// Allow enables CORS for requests those match the provided options. -func Allow(opts *Options) beego.FilterFunc { - f := cors.Allow((*cors.Options)(opts)) - return func(c *context.Context) { - f((*beecontext.Context)(c)) - } -} diff --git a/adapter/policy.go b/adapter/policy.go deleted file mode 100644 index 6f334d2d..00000000 --- a/adapter/policy.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2016 beego authors. All Rights Reserved. -// -// Licensed 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 adapter - -import ( - "github.com/astaxie/beego/adapter/context" - "github.com/astaxie/beego/server/web" - beecontext "github.com/astaxie/beego/server/web/context" -) - -// PolicyFunc defines a policy function which is invoked before the controller handler is executed. -type PolicyFunc func(*context.Context) - -// FindPolicy Find Router info for URL -func (p *ControllerRegister) FindPolicy(cont *context.Context) []PolicyFunc { - pf := (*web.ControllerRegister)(p).FindPolicy((*beecontext.Context)(cont)) - npf := newToOldPolicyFunc(pf) - return npf -} - -func newToOldPolicyFunc(pf []web.PolicyFunc) []PolicyFunc { - npf := make([]PolicyFunc, 0, len(pf)) - for _, f := range pf { - npf = append(npf, func(c *context.Context) { - f((*beecontext.Context)(c)) - }) - } - return npf -} - -func oldToNewPolicyFunc(pf []PolicyFunc) []web.PolicyFunc { - npf := make([]web.PolicyFunc, 0, len(pf)) - for _, f := range pf { - npf = append(npf, func(c *beecontext.Context) { - f((*context.Context)(c)) - }) - } - return npf -} - -// Policy Register new policy in beego -func Policy(pattern, method string, policy ...PolicyFunc) { - pf := oldToNewPolicyFunc(policy) - web.Policy(pattern, method, pf...) -} diff --git a/adapter/router.go b/adapter/router.go deleted file mode 100644 index c91a09f1..00000000 --- a/adapter/router.go +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 adapter - -import ( - "net/http" - "time" - - beecontext "github.com/astaxie/beego/adapter/context" - "github.com/astaxie/beego/server/web/context" - - "github.com/astaxie/beego/server/web" -) - -// default filter execution points -const ( - BeforeStatic = web.BeforeStatic - BeforeRouter = web.BeforeRouter - BeforeExec = web.BeforeExec - AfterExec = web.AfterExec - FinishRouter = web.FinishRouter -) - -var ( - // HTTPMETHOD list the supported http methods. - HTTPMETHOD = web.HTTPMETHOD - - // DefaultAccessLogFilter will skip the accesslog if return true - DefaultAccessLogFilter FilterHandler = &newToOldFtHdlAdapter{ - delegate: web.DefaultAccessLogFilter, - } -) - -// FilterHandler is an interface for -type FilterHandler interface { - Filter(*beecontext.Context) bool -} - -type newToOldFtHdlAdapter struct { - delegate web.FilterHandler -} - -func (n *newToOldFtHdlAdapter) Filter(ctx *beecontext.Context) bool { - return n.delegate.Filter((*context.Context)(ctx)) -} - -// ExceptMethodAppend to append a slice's value into "exceptMethod", for controller's methods shouldn't reflect to AutoRouter -func ExceptMethodAppend(action string) { - web.ExceptMethodAppend(action) -} - -// ControllerInfo holds information about the controller. -type ControllerInfo web.ControllerInfo - -func (c *ControllerInfo) GetPattern() string { - return (*web.ControllerInfo)(c).GetPattern() -} - -// ControllerRegister containers registered router rules, controller handlers and filters. -type ControllerRegister web.ControllerRegister - -// NewControllerRegister returns a new ControllerRegister. -func NewControllerRegister() *ControllerRegister { - return (*ControllerRegister)(web.NewControllerRegister()) -} - -// Add controller handler and pattern rules to ControllerRegister. -// usage: -// default methods is the same name as method -// Add("/user",&UserController{}) -// Add("/api/list",&RestController{},"*:ListFood") -// Add("/api/create",&RestController{},"post:CreateFood") -// Add("/api/update",&RestController{},"put:UpdateFood") -// Add("/api/delete",&RestController{},"delete:DeleteFood") -// Add("/api",&RestController{},"get,post:ApiFunc" -// Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc") -func (p *ControllerRegister) Add(pattern string, c ControllerInterface, mappingMethods ...string) { - (*web.ControllerRegister)(p).Add(pattern, c, mappingMethods...) -} - -// Include only when the Runmode is dev will generate router file in the router/auto.go from the controller -// Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) -func (p *ControllerRegister) Include(cList ...ControllerInterface) { - nls := oldToNewCtrlIntfs(cList) - (*web.ControllerRegister)(p).Include(nls...) -} - -// GetContext returns a context from pool, so usually you should remember to call Reset function to clean the context -// And don't forget to give back context to pool -// example: -// ctx := p.GetContext() -// ctx.Reset(w, q) -// defer p.GiveBackContext(ctx) -func (p *ControllerRegister) GetContext() *beecontext.Context { - return (*beecontext.Context)((*web.ControllerRegister)(p).GetContext()) -} - -// GiveBackContext put the ctx into pool so that it could be reuse -func (p *ControllerRegister) GiveBackContext(ctx *beecontext.Context) { - (*web.ControllerRegister)(p).GiveBackContext((*context.Context)(ctx)) -} - -// Get add get method -// usage: -// Get("/", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (p *ControllerRegister) Get(pattern string, f FilterFunc) { - (*web.ControllerRegister)(p).Get(pattern, func(ctx *context.Context) { - f((*beecontext.Context)(ctx)) - }) -} - -// Post add post method -// usage: -// Post("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (p *ControllerRegister) Post(pattern string, f FilterFunc) { - (*web.ControllerRegister)(p).Post(pattern, func(ctx *context.Context) { - f((*beecontext.Context)(ctx)) - }) -} - -// Put add put method -// usage: -// Put("/api/:id", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (p *ControllerRegister) Put(pattern string, f FilterFunc) { - (*web.ControllerRegister)(p).Put(pattern, func(ctx *context.Context) { - f((*beecontext.Context)(ctx)) - }) -} - -// Delete add delete method -// usage: -// Delete("/api/:id", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (p *ControllerRegister) Delete(pattern string, f FilterFunc) { - (*web.ControllerRegister)(p).Delete(pattern, func(ctx *context.Context) { - f((*beecontext.Context)(ctx)) - }) -} - -// Head add head method -// usage: -// Head("/api/:id", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (p *ControllerRegister) Head(pattern string, f FilterFunc) { - (*web.ControllerRegister)(p).Head(pattern, func(ctx *context.Context) { - f((*beecontext.Context)(ctx)) - }) -} - -// Patch add patch method -// usage: -// Patch("/api/:id", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (p *ControllerRegister) Patch(pattern string, f FilterFunc) { - (*web.ControllerRegister)(p).Patch(pattern, func(ctx *context.Context) { - f((*beecontext.Context)(ctx)) - }) -} - -// Options add options method -// usage: -// Options("/api/:id", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (p *ControllerRegister) Options(pattern string, f FilterFunc) { - (*web.ControllerRegister)(p).Options(pattern, func(ctx *context.Context) { - f((*beecontext.Context)(ctx)) - }) -} - -// Any add all method -// usage: -// Any("/api/:id", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (p *ControllerRegister) Any(pattern string, f FilterFunc) { - (*web.ControllerRegister)(p).Any(pattern, func(ctx *context.Context) { - f((*beecontext.Context)(ctx)) - }) -} - -// AddMethod add http method router -// usage: -// AddMethod("get","/api/:id", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) { - (*web.ControllerRegister)(p).AddMethod(method, pattern, func(ctx *context.Context) { - f((*beecontext.Context)(ctx)) - }) -} - -// Handler add user defined Handler -func (p *ControllerRegister) Handler(pattern string, h http.Handler, options ...interface{}) { - (*web.ControllerRegister)(p).Handler(pattern, h, options) -} - -// AddAuto router to ControllerRegister. -// example beego.AddAuto(&MainContorlller{}), -// MainController has method List and Page. -// visit the url /main/list to execute List function -// /main/page to execute Page function. -func (p *ControllerRegister) AddAuto(c ControllerInterface) { - (*web.ControllerRegister)(p).AddAuto(c) -} - -// AddAutoPrefix Add auto router to ControllerRegister with prefix. -// example beego.AddAutoPrefix("/admin",&MainContorlller{}), -// MainController has method List and Page. -// visit the url /admin/main/list to execute List function -// /admin/main/page to execute Page function. -func (p *ControllerRegister) AddAutoPrefix(prefix string, c ControllerInterface) { - (*web.ControllerRegister)(p).AddAutoPrefix(prefix, c) -} - -// InsertFilter Add a FilterFunc with pattern rule and action constant. -// params is for: -// 1. setting the returnOnOutput value (false allows multiple filters to execute) -// 2. determining whether or not params need to be reset. -func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) error { - opts := oldToNewFilterOpts(params) - return (*web.ControllerRegister)(p).InsertFilter(pattern, pos, func(ctx *context.Context) { - filter((*beecontext.Context)(ctx)) - }, opts...) -} - -func oldToNewFilterOpts(params []bool) []web.FilterOpt { - opts := make([]web.FilterOpt, 0, 4) - if len(params) > 0 { - opts = append(opts, web.WithReturnOnOutput(params[0])) - } else { - // the default value should be true - opts = append(opts, web.WithReturnOnOutput(true)) - } - if len(params) > 1 { - opts = append(opts, web.WithResetParams(params[1])) - } - return opts -} - -// URLFor does another controller handler in this request function. -// it can access any controller method. -func (p *ControllerRegister) URLFor(endpoint string, values ...interface{}) string { - return (*web.ControllerRegister)(p).URLFor(endpoint, values...) -} - -// Implement http.Handler interface. -func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - (*web.ControllerRegister)(p).ServeHTTP(rw, r) -} - -// FindRouter Find Router info for URL -func (p *ControllerRegister) FindRouter(ctx *beecontext.Context) (routerInfo *ControllerInfo, isFind bool) { - r, ok := (*web.ControllerRegister)(p).FindRouter((*context.Context)(ctx)) - return (*ControllerInfo)(r), ok -} - -// LogAccess logging info HTTP Access -func LogAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) { - web.LogAccess((*context.Context)(ctx), startTime, statusCode) -} diff --git a/adapter/session/couchbase/sess_couchbase.go b/adapter/session/couchbase/sess_couchbase.go deleted file mode 100644 index b6afb612..00000000 --- a/adapter/session/couchbase/sess_couchbase.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 couchbase for session provider -// -// depend on github.com/couchbaselabs/go-couchbasee -// -// go install github.com/couchbaselabs/go-couchbase -// -// Usage: -// import( -// _ "github.com/astaxie/beego/session/couchbase" -// "github.com/astaxie/beego/session" -// ) -// -// func init() { -// globalSessions, _ = session.NewManager("couchbase", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"http://host:port/, Pool, Bucket"}``) -// go globalSessions.GC() -// } -// -// more docs: http://beego.me/docs/module/session.md -package couchbase - -import ( - "context" - "net/http" - - "github.com/astaxie/beego/adapter/session" - beecb "github.com/astaxie/beego/server/web/session/couchbase" -) - -// SessionStore store each session -type SessionStore beecb.SessionStore - -// Provider couchabse provided -type Provider beecb.Provider - -// Set value to couchabse session -func (cs *SessionStore) Set(key, value interface{}) error { - return (*beecb.SessionStore)(cs).Set(context.Background(), key, value) -} - -// Get value from couchabse session -func (cs *SessionStore) Get(key interface{}) interface{} { - return (*beecb.SessionStore)(cs).Get(context.Background(), key) -} - -// Delete value in couchbase session by given key -func (cs *SessionStore) Delete(key interface{}) error { - return (*beecb.SessionStore)(cs).Delete(context.Background(), key) -} - -// Flush Clean all values in couchbase session -func (cs *SessionStore) Flush() error { - return (*beecb.SessionStore)(cs).Flush(context.Background()) -} - -// SessionID Get couchbase session store id -func (cs *SessionStore) SessionID() string { - return (*beecb.SessionStore)(cs).SessionID(context.Background()) -} - -// SessionRelease Write couchbase session with Gob string -func (cs *SessionStore) SessionRelease(w http.ResponseWriter) { - (*beecb.SessionStore)(cs).SessionRelease(context.Background(), w) -} - -// SessionInit init couchbase session -// savepath like couchbase server REST/JSON URL -// e.g. http://host:port/, Pool, Bucket -func (cp *Provider) SessionInit(maxlifetime int64, savePath string) error { - return (*beecb.Provider)(cp).SessionInit(context.Background(), maxlifetime, savePath) -} - -// SessionRead read couchbase session by sid -func (cp *Provider) SessionRead(sid string) (session.Store, error) { - s, err := (*beecb.Provider)(cp).SessionRead(context.Background(), sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionExist Check couchbase session exist. -// it checkes sid exist or not. -func (cp *Provider) SessionExist(sid string) bool { - res, _ := (*beecb.Provider)(cp).SessionExist(context.Background(), sid) - return res -} - -// SessionRegenerate remove oldsid and use sid to generate new session -func (cp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - s, err := (*beecb.Provider)(cp).SessionRegenerate(context.Background(), oldsid, sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionDestroy Remove bucket in this couchbase -func (cp *Provider) SessionDestroy(sid string) error { - return (*beecb.Provider)(cp).SessionDestroy(context.Background(), sid) -} - -// SessionGC Recycle -func (cp *Provider) SessionGC() { - (*beecb.Provider)(cp).SessionGC(context.Background()) -} - -// SessionAll return all active session -func (cp *Provider) SessionAll() int { - return (*beecb.Provider)(cp).SessionAll(context.Background()) -} diff --git a/adapter/session/ledis/ledis_session.go b/adapter/session/ledis/ledis_session.go deleted file mode 100644 index 350cbdaa..00000000 --- a/adapter/session/ledis/ledis_session.go +++ /dev/null @@ -1,86 +0,0 @@ -// Package ledis provide session Provider -package ledis - -import ( - "context" - "net/http" - - "github.com/astaxie/beego/adapter/session" - beeLedis "github.com/astaxie/beego/server/web/session/ledis" -) - -// SessionStore ledis session store -type SessionStore beeLedis.SessionStore - -// Set value in ledis session -func (ls *SessionStore) Set(key, value interface{}) error { - return (*beeLedis.SessionStore)(ls).Set(context.Background(), key, value) -} - -// Get value in ledis session -func (ls *SessionStore) Get(key interface{}) interface{} { - return (*beeLedis.SessionStore)(ls).Get(context.Background(), key) -} - -// Delete value in ledis session -func (ls *SessionStore) Delete(key interface{}) error { - return (*beeLedis.SessionStore)(ls).Delete(context.Background(), key) -} - -// Flush clear all values in ledis session -func (ls *SessionStore) Flush() error { - return (*beeLedis.SessionStore)(ls).Flush(context.Background()) -} - -// SessionID get ledis session id -func (ls *SessionStore) SessionID() string { - return (*beeLedis.SessionStore)(ls).SessionID(context.Background()) -} - -// SessionRelease save session values to ledis -func (ls *SessionStore) SessionRelease(w http.ResponseWriter) { - (*beeLedis.SessionStore)(ls).SessionRelease(context.Background(), w) -} - -// Provider ledis session provider -type Provider beeLedis.Provider - -// SessionInit init ledis session -// savepath like ledis server saveDataPath,pool size -// e.g. 127.0.0.1:6379,100,astaxie -func (lp *Provider) SessionInit(maxlifetime int64, savePath string) error { - return (*beeLedis.Provider)(lp).SessionInit(context.Background(), maxlifetime, savePath) -} - -// SessionRead read ledis session by sid -func (lp *Provider) SessionRead(sid string) (session.Store, error) { - s, err := (*beeLedis.Provider)(lp).SessionRead(context.Background(), sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionExist check ledis session exist by sid -func (lp *Provider) SessionExist(sid string) bool { - res, _ := (*beeLedis.Provider)(lp).SessionExist(context.Background(), sid) - return res -} - -// SessionRegenerate generate new sid for ledis session -func (lp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - s, err := (*beeLedis.Provider)(lp).SessionRegenerate(context.Background(), oldsid, sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionDestroy delete ledis session by id -func (lp *Provider) SessionDestroy(sid string) error { - return (*beeLedis.Provider)(lp).SessionDestroy(context.Background(), sid) -} - -// SessionGC Impelment method, no used. -func (lp *Provider) SessionGC() { - (*beeLedis.Provider)(lp).SessionGC(context.Background()) -} - -// SessionAll return all active session -func (lp *Provider) SessionAll() int { - return (*beeLedis.Provider)(lp).SessionAll(context.Background()) -} diff --git a/adapter/session/memcache/sess_memcache.go b/adapter/session/memcache/sess_memcache.go deleted file mode 100644 index 772839cd..00000000 --- a/adapter/session/memcache/sess_memcache.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 memcache for session provider -// -// depend on github.com/bradfitz/gomemcache/memcache -// -// go install github.com/bradfitz/gomemcache/memcache -// -// Usage: -// import( -// _ "github.com/astaxie/beego/session/memcache" -// "github.com/astaxie/beego/session" -// ) -// -// func init() { -// globalSessions, _ = session.NewManager("memcache", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:11211"}``) -// go globalSessions.GC() -// } -// -// more docs: http://beego.me/docs/module/session.md -package memcache - -import ( - "context" - "net/http" - - "github.com/astaxie/beego/adapter/session" - - beemem "github.com/astaxie/beego/server/web/session/memcache" -) - -// SessionStore memcache session store -type SessionStore beemem.SessionStore - -// Set value in memcache session -func (rs *SessionStore) Set(key, value interface{}) error { - return (*beemem.SessionStore)(rs).Set(context.Background(), key, value) -} - -// Get value in memcache session -func (rs *SessionStore) Get(key interface{}) interface{} { - return (*beemem.SessionStore)(rs).Get(context.Background(), key) -} - -// Delete value in memcache session -func (rs *SessionStore) Delete(key interface{}) error { - return (*beemem.SessionStore)(rs).Delete(context.Background(), key) -} - -// Flush clear all values in memcache session -func (rs *SessionStore) Flush() error { - return (*beemem.SessionStore)(rs).Flush(context.Background()) -} - -// SessionID get memcache session id -func (rs *SessionStore) SessionID() string { - return (*beemem.SessionStore)(rs).SessionID(context.Background()) -} - -// SessionRelease save session values to memcache -func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { - (*beemem.SessionStore)(rs).SessionRelease(context.Background(), w) -} - -// MemProvider memcache session provider -type MemProvider beemem.MemProvider - -// SessionInit init memcache session -// savepath like -// e.g. 127.0.0.1:9090 -func (rp *MemProvider) SessionInit(maxlifetime int64, savePath string) error { - return (*beemem.MemProvider)(rp).SessionInit(context.Background(), maxlifetime, savePath) -} - -// SessionRead read memcache session by sid -func (rp *MemProvider) SessionRead(sid string) (session.Store, error) { - s, err := (*beemem.MemProvider)(rp).SessionRead(context.Background(), sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionExist check memcache session exist by sid -func (rp *MemProvider) SessionExist(sid string) bool { - res, _ := (*beemem.MemProvider)(rp).SessionExist(context.Background(), sid) - return res -} - -// SessionRegenerate generate new sid for memcache session -func (rp *MemProvider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - s, err := (*beemem.MemProvider)(rp).SessionRegenerate(context.Background(), oldsid, sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionDestroy delete memcache session by id -func (rp *MemProvider) SessionDestroy(sid string) error { - return (*beemem.MemProvider)(rp).SessionDestroy(context.Background(), sid) -} - -// SessionGC Impelment method, no used. -func (rp *MemProvider) SessionGC() { - (*beemem.MemProvider)(rp).SessionGC(context.Background()) -} - -// SessionAll return all activeSession -func (rp *MemProvider) SessionAll() int { - return (*beemem.MemProvider)(rp).SessionAll(context.Background()) -} diff --git a/adapter/session/mysql/sess_mysql.go b/adapter/session/mysql/sess_mysql.go deleted file mode 100644 index 5d7e1dac..00000000 --- a/adapter/session/mysql/sess_mysql.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 mysql for session provider -// -// depends on github.com/go-sql-driver/mysql: -// -// go install github.com/go-sql-driver/mysql -// -// mysql session support need create table as sql: -// CREATE TABLE `session` ( -// `session_key` char(64) NOT NULL, -// `session_data` blob, -// `session_expiry` int(11) unsigned NOT NULL, -// PRIMARY KEY (`session_key`) -// ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -// -// Usage: -// import( -// _ "github.com/astaxie/beego/session/mysql" -// "github.com/astaxie/beego/session" -// ) -// -// func init() { -// globalSessions, _ = session.NewManager("mysql", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]"}``) -// go globalSessions.GC() -// } -// -// more docs: http://beego.me/docs/module/session.md -package mysql - -import ( - "context" - "net/http" - - "github.com/astaxie/beego/adapter/session" - "github.com/astaxie/beego/server/web/session/mysql" - - // import mysql driver - _ "github.com/go-sql-driver/mysql" -) - -var ( - // TableName store the session in MySQL - TableName = mysql.TableName - mysqlpder = &Provider{} -) - -// SessionStore mysql session store -type SessionStore mysql.SessionStore - -// Set value in mysql session. -// it is temp value in map. -func (st *SessionStore) Set(key, value interface{}) error { - return (*mysql.SessionStore)(st).Set(context.Background(), key, value) -} - -// Get value from mysql session -func (st *SessionStore) Get(key interface{}) interface{} { - return (*mysql.SessionStore)(st).Get(context.Background(), key) -} - -// Delete value in mysql session -func (st *SessionStore) Delete(key interface{}) error { - return (*mysql.SessionStore)(st).Delete(context.Background(), key) -} - -// Flush clear all values in mysql session -func (st *SessionStore) Flush() error { - return (*mysql.SessionStore)(st).Flush(context.Background()) -} - -// SessionID get session id of this mysql session store -func (st *SessionStore) SessionID() string { - return (*mysql.SessionStore)(st).SessionID(context.Background()) -} - -// SessionRelease save mysql session values to database. -// must call this method to save values to database. -func (st *SessionStore) SessionRelease(w http.ResponseWriter) { - (*mysql.SessionStore)(st).SessionRelease(context.Background(), w) -} - -// Provider mysql session provider -type Provider mysql.Provider - -// SessionInit init mysql session. -// savepath is the connection string of mysql. -func (mp *Provider) SessionInit(maxlifetime int64, savePath string) error { - return (*mysql.Provider)(mp).SessionInit(context.Background(), maxlifetime, savePath) -} - -// SessionRead get mysql session by sid -func (mp *Provider) SessionRead(sid string) (session.Store, error) { - s, err := (*mysql.Provider)(mp).SessionRead(context.Background(), sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionExist check mysql session exist -func (mp *Provider) SessionExist(sid string) bool { - res, _ := (*mysql.Provider)(mp).SessionExist(context.Background(), sid) - return res -} - -// SessionRegenerate generate new sid for mysql session -func (mp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - s, err := (*mysql.Provider)(mp).SessionRegenerate(context.Background(), oldsid, sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionDestroy delete mysql session by sid -func (mp *Provider) SessionDestroy(sid string) error { - return (*mysql.Provider)(mp).SessionDestroy(context.Background(), sid) -} - -// SessionGC delete expired values in mysql session -func (mp *Provider) SessionGC() { - (*mysql.Provider)(mp).SessionGC(context.Background()) -} - -// SessionAll count values in mysql session -func (mp *Provider) SessionAll() int { - return (*mysql.Provider)(mp).SessionAll(context.Background()) -} diff --git a/adapter/session/postgres/sess_postgresql.go b/adapter/session/postgres/sess_postgresql.go deleted file mode 100644 index 879b2b83..00000000 --- a/adapter/session/postgres/sess_postgresql.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 postgres for session provider -// -// depends on github.com/lib/pq: -// -// go install github.com/lib/pq -// -// -// needs this table in your database: -// -// CREATE TABLE session ( -// session_key char(64) NOT NULL, -// session_data bytea, -// session_expiry timestamp NOT NULL, -// CONSTRAINT session_key PRIMARY KEY(session_key) -// ); -// -// will be activated with these settings in app.conf: -// -// SessionOn = true -// SessionProvider = postgresql -// SessionSavePath = "user=a password=b dbname=c sslmode=disable" -// SessionName = session -// -// -// Usage: -// import( -// _ "github.com/astaxie/beego/session/postgresql" -// "github.com/astaxie/beego/session" -// ) -// -// func init() { -// globalSessions, _ = session.NewManager("postgresql", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"user=pqgotest dbname=pqgotest sslmode=verify-full"}``) -// go globalSessions.GC() -// } -// -// more docs: http://beego.me/docs/module/session.md -package postgres - -import ( - "context" - "net/http" - - "github.com/astaxie/beego/adapter/session" - // import postgresql Driver - _ "github.com/lib/pq" - - "github.com/astaxie/beego/server/web/session/postgres" -) - -// SessionStore postgresql session store -type SessionStore postgres.SessionStore - -// Set value in postgresql session. -// it is temp value in map. -func (st *SessionStore) Set(key, value interface{}) error { - return (*postgres.SessionStore)(st).Set(context.Background(), key, value) -} - -// Get value from postgresql session -func (st *SessionStore) Get(key interface{}) interface{} { - return (*postgres.SessionStore)(st).Get(context.Background(), key) -} - -// Delete value in postgresql session -func (st *SessionStore) Delete(key interface{}) error { - return (*postgres.SessionStore)(st).Delete(context.Background(), key) -} - -// Flush clear all values in postgresql session -func (st *SessionStore) Flush() error { - return (*postgres.SessionStore)(st).Flush(context.Background()) -} - -// SessionID get session id of this postgresql session store -func (st *SessionStore) SessionID() string { - return (*postgres.SessionStore)(st).SessionID(context.Background()) -} - -// SessionRelease save postgresql session values to database. -// must call this method to save values to database. -func (st *SessionStore) SessionRelease(w http.ResponseWriter) { - (*postgres.SessionStore)(st).SessionRelease(context.Background(), w) -} - -// Provider postgresql session provider -type Provider postgres.Provider - -// SessionInit init postgresql session. -// savepath is the connection string of postgresql. -func (mp *Provider) SessionInit(maxlifetime int64, savePath string) error { - return (*postgres.Provider)(mp).SessionInit(context.Background(), maxlifetime, savePath) -} - -// SessionRead get postgresql session by sid -func (mp *Provider) SessionRead(sid string) (session.Store, error) { - s, err := (*postgres.Provider)(mp).SessionRead(context.Background(), sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionExist check postgresql session exist -func (mp *Provider) SessionExist(sid string) bool { - res, _ := (*postgres.Provider)(mp).SessionExist(context.Background(), sid) - return res -} - -// SessionRegenerate generate new sid for postgresql session -func (mp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - s, err := (*postgres.Provider)(mp).SessionRegenerate(context.Background(), oldsid, sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionDestroy delete postgresql session by sid -func (mp *Provider) SessionDestroy(sid string) error { - return (*postgres.Provider)(mp).SessionDestroy(context.Background(), sid) -} - -// SessionGC delete expired values in postgresql session -func (mp *Provider) SessionGC() { - (*postgres.Provider)(mp).SessionGC(context.Background()) -} - -// SessionAll count values in postgresql session -func (mp *Provider) SessionAll() int { - return (*postgres.Provider)(mp).SessionAll(context.Background()) -} diff --git a/adapter/session/provider_adapter.go b/adapter/session/provider_adapter.go deleted file mode 100644 index 596bc6a6..00000000 --- a/adapter/session/provider_adapter.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 session - -import ( - "context" - - "github.com/astaxie/beego/server/web/session" -) - -type oldToNewProviderAdapter struct { - delegate Provider -} - -func (o *oldToNewProviderAdapter) SessionInit(ctx context.Context, gclifetime int64, config string) error { - return o.delegate.SessionInit(gclifetime, config) -} - -func (o *oldToNewProviderAdapter) SessionRead(ctx context.Context, sid string) (session.Store, error) { - store, err := o.delegate.SessionRead(sid) - return &oldToNewStoreAdapter{ - delegate: store, - }, err -} - -func (o *oldToNewProviderAdapter) SessionExist(ctx context.Context, sid string) (bool, error) { - return o.delegate.SessionExist(sid), nil -} - -func (o *oldToNewProviderAdapter) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { - s, err := o.delegate.SessionRegenerate(oldsid, sid) - return &oldToNewStoreAdapter{ - delegate: s, - }, err -} - -func (o *oldToNewProviderAdapter) SessionDestroy(ctx context.Context, sid string) error { - return o.delegate.SessionDestroy(sid) -} - -func (o *oldToNewProviderAdapter) SessionAll(ctx context.Context) int { - return o.delegate.SessionAll() -} - -func (o *oldToNewProviderAdapter) SessionGC(ctx context.Context) { - o.delegate.SessionGC() -} - -type newToOldProviderAdapter struct { - delegate session.Provider -} - -func (n *newToOldProviderAdapter) SessionInit(gclifetime int64, config string) error { - return n.delegate.SessionInit(context.Background(), gclifetime, config) -} - -func (n *newToOldProviderAdapter) SessionRead(sid string) (Store, error) { - s, err := n.delegate.SessionRead(context.Background(), sid) - if adt, ok := s.(*oldToNewStoreAdapter); err == nil && ok { - return adt.delegate, err - } - return &NewToOldStoreAdapter{ - delegate: s, - }, err -} - -func (n *newToOldProviderAdapter) SessionExist(sid string) bool { - res, _ := n.delegate.SessionExist(context.Background(), sid) - return res -} - -func (n *newToOldProviderAdapter) SessionRegenerate(oldsid, sid string) (Store, error) { - s, err := n.delegate.SessionRegenerate(context.Background(), oldsid, sid) - if adt, ok := s.(*oldToNewStoreAdapter); err == nil && ok { - return adt.delegate, err - } - return &NewToOldStoreAdapter{ - delegate: s, - }, err -} - -func (n *newToOldProviderAdapter) SessionDestroy(sid string) error { - return n.delegate.SessionDestroy(context.Background(), sid) -} - -func (n *newToOldProviderAdapter) SessionAll() int { - return n.delegate.SessionAll(context.Background()) -} - -func (n *newToOldProviderAdapter) SessionGC() { - n.delegate.SessionGC(context.Background()) -} diff --git a/adapter/session/redis/sess_redis.go b/adapter/session/redis/sess_redis.go deleted file mode 100644 index bb8e8be4..00000000 --- a/adapter/session/redis/sess_redis.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 redis for session provider -// -// depend on github.com/gomodule/redigo/redis -// -// go install github.com/gomodule/redigo/redis -// -// Usage: -// import( -// _ "github.com/astaxie/beego/session/redis" -// "github.com/astaxie/beego/session" -// ) -// -// func init() { -// globalSessions, _ = session.NewManager("redis", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:7070"}``) -// go globalSessions.GC() -// } -// -// more docs: http://beego.me/docs/module/session.md -package redis - -import ( - "context" - "net/http" - - "github.com/astaxie/beego/adapter/session" - - beeRedis "github.com/astaxie/beego/server/web/session/redis" -) - -// MaxPoolSize redis max pool size -var MaxPoolSize = beeRedis.MaxPoolSize - -// SessionStore redis session store -type SessionStore beeRedis.SessionStore - -// Set value in redis session -func (rs *SessionStore) Set(key, value interface{}) error { - return (*beeRedis.SessionStore)(rs).Set(context.Background(), key, value) -} - -// Get value in redis session -func (rs *SessionStore) Get(key interface{}) interface{} { - return (*beeRedis.SessionStore)(rs).Get(context.Background(), key) -} - -// Delete value in redis session -func (rs *SessionStore) Delete(key interface{}) error { - return (*beeRedis.SessionStore)(rs).Delete(context.Background(), key) -} - -// Flush clear all values in redis session -func (rs *SessionStore) Flush() error { - return (*beeRedis.SessionStore)(rs).Flush(context.Background()) -} - -// SessionID get redis session id -func (rs *SessionStore) SessionID() string { - return (*beeRedis.SessionStore)(rs).SessionID(context.Background()) -} - -// SessionRelease save session values to redis -func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { - (*beeRedis.SessionStore)(rs).SessionRelease(context.Background(), w) -} - -// Provider redis session provider -type Provider beeRedis.Provider - -// SessionInit init redis session -// savepath like redis server addr,pool size,password,dbnum,IdleTimeout second -// e.g. 127.0.0.1:6379,100,astaxie,0,30 -func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error { - return (*beeRedis.Provider)(rp).SessionInit(context.Background(), maxlifetime, savePath) -} - -// SessionRead read redis session by sid -func (rp *Provider) SessionRead(sid string) (session.Store, error) { - s, err := (*beeRedis.Provider)(rp).SessionRead(context.Background(), sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionExist check redis session exist by sid -func (rp *Provider) SessionExist(sid string) bool { - res, _ := (*beeRedis.Provider)(rp).SessionExist(context.Background(), sid) - return res -} - -// SessionRegenerate generate new sid for redis session -func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - s, err := (*beeRedis.Provider)(rp).SessionRegenerate(context.Background(), oldsid, sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionDestroy delete redis session by id -func (rp *Provider) SessionDestroy(sid string) error { - return (*beeRedis.Provider)(rp).SessionDestroy(context.Background(), sid) -} - -// SessionGC Impelment method, no used. -func (rp *Provider) SessionGC() { - (*beeRedis.Provider)(rp).SessionGC(context.Background()) -} - -// SessionAll return all activeSession -func (rp *Provider) SessionAll() int { - return (*beeRedis.Provider)(rp).SessionAll(context.Background()) -} diff --git a/adapter/session/redis_cluster/redis_cluster.go b/adapter/session/redis_cluster/redis_cluster.go deleted file mode 100644 index 1be22cd4..00000000 --- a/adapter/session/redis_cluster/redis_cluster.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 redis for session provider -// -// depend on github.com/go-redis/redis -// -// go install github.com/go-redis/redis -// -// Usage: -// import( -// _ "github.com/astaxie/beego/session/redis_cluster" -// "github.com/astaxie/beego/session" -// ) -// -// func init() { -// globalSessions, _ = session.NewManager("redis_cluster", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:7070;127.0.0.1:7071"}``) -// go globalSessions.GC() -// } -// -// more docs: http://beego.me/docs/module/session.md -package redis_cluster - -import ( - "context" - "net/http" - - "github.com/astaxie/beego/adapter/session" - cluster "github.com/astaxie/beego/server/web/session/redis_cluster" -) - -// MaxPoolSize redis_cluster max pool size -var MaxPoolSize = cluster.MaxPoolSize - -// SessionStore redis_cluster session store -type SessionStore cluster.SessionStore - -// Set value in redis_cluster session -func (rs *SessionStore) Set(key, value interface{}) error { - return (*cluster.SessionStore)(rs).Set(context.Background(), key, value) -} - -// Get value in redis_cluster session -func (rs *SessionStore) Get(key interface{}) interface{} { - return (*cluster.SessionStore)(rs).Get(context.Background(), key) -} - -// Delete value in redis_cluster session -func (rs *SessionStore) Delete(key interface{}) error { - return (*cluster.SessionStore)(rs).Delete(context.Background(), key) -} - -// Flush clear all values in redis_cluster session -func (rs *SessionStore) Flush() error { - return (*cluster.SessionStore)(rs).Flush(context.Background()) -} - -// SessionID get redis_cluster session id -func (rs *SessionStore) SessionID() string { - return (*cluster.SessionStore)(rs).SessionID(context.Background()) -} - -// SessionRelease save session values to redis_cluster -func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { - (*cluster.SessionStore)(rs).SessionRelease(context.Background(), w) -} - -// Provider redis_cluster session provider -type Provider cluster.Provider - -// SessionInit init redis_cluster session -// savepath like redis server addr,pool size,password,dbnum -// e.g. 127.0.0.1:6379;127.0.0.1:6380,100,test,0 -func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error { - return (*cluster.Provider)(rp).SessionInit(context.Background(), maxlifetime, savePath) -} - -// SessionRead read redis_cluster session by sid -func (rp *Provider) SessionRead(sid string) (session.Store, error) { - s, err := (*cluster.Provider)(rp).SessionRead(context.Background(), sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionExist check redis_cluster session exist by sid -func (rp *Provider) SessionExist(sid string) bool { - res, _ := (*cluster.Provider)(rp).SessionExist(context.Background(), sid) - return res -} - -// SessionRegenerate generate new sid for redis_cluster session -func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - s, err := (*cluster.Provider)(rp).SessionRegenerate(context.Background(), oldsid, sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionDestroy delete redis session by id -func (rp *Provider) SessionDestroy(sid string) error { - return (*cluster.Provider)(rp).SessionDestroy(context.Background(), sid) -} - -// SessionGC Impelment method, no used. -func (rp *Provider) SessionGC() { - (*cluster.Provider)(rp).SessionGC(context.Background()) -} - -// SessionAll return all activeSession -func (rp *Provider) SessionAll() int { - return (*cluster.Provider)(rp).SessionAll(context.Background()) -} diff --git a/adapter/session/redis_sentinel/sess_redis_sentinel.go b/adapter/session/redis_sentinel/sess_redis_sentinel.go deleted file mode 100644 index 7ab9e7c5..00000000 --- a/adapter/session/redis_sentinel/sess_redis_sentinel.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 redis for session provider -// -// depend on github.com/go-redis/redis -// -// go install github.com/go-redis/redis -// -// Usage: -// import( -// _ "github.com/astaxie/beego/session/redis_sentinel" -// "github.com/astaxie/beego/session" -// ) -// -// func init() { -// globalSessions, _ = session.NewManager("redis_sentinel", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:26379;127.0.0.2:26379"}``) -// go globalSessions.GC() -// } -// -// more detail about params: please check the notes on the function SessionInit in this package -package redis_sentinel - -import ( - "context" - "net/http" - - "github.com/astaxie/beego/adapter/session" - - sentinel "github.com/astaxie/beego/server/web/session/redis_sentinel" -) - -// DefaultPoolSize redis_sentinel default pool size -var DefaultPoolSize = sentinel.DefaultPoolSize - -// SessionStore redis_sentinel session store -type SessionStore sentinel.SessionStore - -// Set value in redis_sentinel session -func (rs *SessionStore) Set(key, value interface{}) error { - return (*sentinel.SessionStore)(rs).Set(context.Background(), key, value) -} - -// Get value in redis_sentinel session -func (rs *SessionStore) Get(key interface{}) interface{} { - return (*sentinel.SessionStore)(rs).Get(context.Background(), key) -} - -// Delete value in redis_sentinel session -func (rs *SessionStore) Delete(key interface{}) error { - return (*sentinel.SessionStore)(rs).Delete(context.Background(), key) -} - -// Flush clear all values in redis_sentinel session -func (rs *SessionStore) Flush() error { - return (*sentinel.SessionStore)(rs).Flush(context.Background()) -} - -// SessionID get redis_sentinel session id -func (rs *SessionStore) SessionID() string { - return (*sentinel.SessionStore)(rs).SessionID(context.Background()) -} - -// SessionRelease save session values to redis_sentinel -func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { - (*sentinel.SessionStore)(rs).SessionRelease(context.Background(), w) -} - -// Provider redis_sentinel session provider -type Provider sentinel.Provider - -// SessionInit init redis_sentinel session -// savepath like redis sentinel addr,pool size,password,dbnum,masterName -// e.g. 127.0.0.1:26379;127.0.0.2:26379,100,1qaz2wsx,0,mymaster -func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error { - return (*sentinel.Provider)(rp).SessionInit(context.Background(), maxlifetime, savePath) -} - -// SessionRead read redis_sentinel session by sid -func (rp *Provider) SessionRead(sid string) (session.Store, error) { - s, err := (*sentinel.Provider)(rp).SessionRead(context.Background(), sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionExist check redis_sentinel session exist by sid -func (rp *Provider) SessionExist(sid string) bool { - res, _ := (*sentinel.Provider)(rp).SessionExist(context.Background(), sid) - return res -} - -// SessionRegenerate generate new sid for redis_sentinel session -func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - s, err := (*sentinel.Provider)(rp).SessionRegenerate(context.Background(), oldsid, sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionDestroy delete redis session by id -func (rp *Provider) SessionDestroy(sid string) error { - return (*sentinel.Provider)(rp).SessionDestroy(context.Background(), sid) -} - -// SessionGC Impelment method, no used. -func (rp *Provider) SessionGC() { - (*sentinel.Provider)(rp).SessionGC(context.Background()) -} - -// SessionAll return all activeSession -func (rp *Provider) SessionAll() int { - return (*sentinel.Provider)(rp).SessionAll(context.Background()) -} diff --git a/adapter/session/sess_cookie.go b/adapter/session/sess_cookie.go deleted file mode 100644 index 3fcbd28e..00000000 --- a/adapter/session/sess_cookie.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 session - -import ( - "context" - "net/http" - - "github.com/astaxie/beego/server/web/session" -) - -// CookieSessionStore Cookie SessionStore -type CookieSessionStore session.CookieSessionStore - -// Set value to cookie session. -// the value are encoded as gob with hash block string. -func (st *CookieSessionStore) Set(key, value interface{}) error { - return (*session.CookieSessionStore)(st).Set(context.Background(), key, value) -} - -// Get value from cookie session -func (st *CookieSessionStore) Get(key interface{}) interface{} { - return (*session.CookieSessionStore)(st).Get(context.Background(), key) -} - -// Delete value in cookie session -func (st *CookieSessionStore) Delete(key interface{}) error { - return (*session.CookieSessionStore)(st).Delete(context.Background(), key) -} - -// Flush Clean all values in cookie session -func (st *CookieSessionStore) Flush() error { - return (*session.CookieSessionStore)(st).Flush(context.Background()) -} - -// SessionID Return id of this cookie session -func (st *CookieSessionStore) SessionID() string { - return (*session.CookieSessionStore)(st).SessionID(context.Background()) -} - -// SessionRelease Write cookie session to http response cookie -func (st *CookieSessionStore) SessionRelease(w http.ResponseWriter) { - (*session.CookieSessionStore)(st).SessionRelease(context.Background(), w) -} - -// CookieProvider Cookie session provider -type CookieProvider session.CookieProvider - -// SessionInit Init cookie session provider with max lifetime and config json. -// maxlifetime is ignored. -// json config: -// securityKey - hash string -// blockKey - gob encode hash string. it's saved as aes crypto. -// securityName - recognized name in encoded cookie string -// cookieName - cookie name -// maxage - cookie max life time. -func (pder *CookieProvider) SessionInit(maxlifetime int64, config string) error { - return (*session.CookieProvider)(pder).SessionInit(context.Background(), maxlifetime, config) -} - -// SessionRead Get SessionStore in cooke. -// decode cooke string to map and put into SessionStore with sid. -func (pder *CookieProvider) SessionRead(sid string) (Store, error) { - s, err := (*session.CookieProvider)(pder).SessionRead(context.Background(), sid) - return &NewToOldStoreAdapter{ - delegate: s, - }, err -} - -// SessionExist Cookie session is always existed -func (pder *CookieProvider) SessionExist(sid string) bool { - res, _ := (*session.CookieProvider)(pder).SessionExist(context.Background(), sid) - return res -} - -// SessionRegenerate Implement method, no used. -func (pder *CookieProvider) SessionRegenerate(oldsid, sid string) (Store, error) { - s, err := (*session.CookieProvider)(pder).SessionRegenerate(context.Background(), oldsid, sid) - return &NewToOldStoreAdapter{ - delegate: s, - }, err -} - -// SessionDestroy Implement method, no used. -func (pder *CookieProvider) SessionDestroy(sid string) error { - return (*session.CookieProvider)(pder).SessionDestroy(context.Background(), sid) -} - -// SessionGC Implement method, no used. -func (pder *CookieProvider) SessionGC() { - (*session.CookieProvider)(pder).SessionGC(context.Background()) -} - -// SessionAll Implement method, return 0. -func (pder *CookieProvider) SessionAll() int { - return (*session.CookieProvider)(pder).SessionAll(context.Background()) -} - -// SessionUpdate Implement method, no used. -func (pder *CookieProvider) SessionUpdate(sid string) error { - return (*session.CookieProvider)(pder).SessionUpdate(context.Background(), sid) -} diff --git a/adapter/session/sess_file.go b/adapter/session/sess_file.go deleted file mode 100644 index 2ba33e6d..00000000 --- a/adapter/session/sess_file.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 session - -import ( - "context" - "net/http" - - "github.com/astaxie/beego/server/web/session" -) - -// FileSessionStore File session store -type FileSessionStore session.FileSessionStore - -// Set value to file session -func (fs *FileSessionStore) Set(key, value interface{}) error { - return (*session.FileSessionStore)(fs).Set(context.Background(), key, value) -} - -// Get value from file session -func (fs *FileSessionStore) Get(key interface{}) interface{} { - return (*session.FileSessionStore)(fs).Get(context.Background(), key) -} - -// Delete value in file session by given key -func (fs *FileSessionStore) Delete(key interface{}) error { - return (*session.FileSessionStore)(fs).Delete(context.Background(), key) -} - -// Flush Clean all values in file session -func (fs *FileSessionStore) Flush() error { - return (*session.FileSessionStore)(fs).Flush(context.Background()) -} - -// SessionID Get file session store id -func (fs *FileSessionStore) SessionID() string { - return (*session.FileSessionStore)(fs).SessionID(context.Background()) -} - -// SessionRelease Write file session to local file with Gob string -func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) { - (*session.FileSessionStore)(fs).SessionRelease(context.Background(), w) -} - -// FileProvider File session provider -type FileProvider session.FileProvider - -// SessionInit Init file session provider. -// savePath sets the session files path. -func (fp *FileProvider) SessionInit(maxlifetime int64, savePath string) error { - return (*session.FileProvider)(fp).SessionInit(context.Background(), maxlifetime, savePath) -} - -// SessionRead Read file session by sid. -// if file is not exist, create it. -// the file path is generated from sid string. -func (fp *FileProvider) SessionRead(sid string) (Store, error) { - s, err := (*session.FileProvider)(fp).SessionRead(context.Background(), sid) - return &NewToOldStoreAdapter{ - delegate: s, - }, err -} - -// SessionExist Check file session exist. -// it checks the file named from sid exist or not. -func (fp *FileProvider) SessionExist(sid string) bool { - res, _ := (*session.FileProvider)(fp).SessionExist(context.Background(), sid) - return res -} - -// SessionDestroy Remove all files in this save path -func (fp *FileProvider) SessionDestroy(sid string) error { - return (*session.FileProvider)(fp).SessionDestroy(context.Background(), sid) -} - -// SessionGC Recycle files in save path -func (fp *FileProvider) SessionGC() { - (*session.FileProvider)(fp).SessionGC(context.Background()) -} - -// SessionAll Get active file session number. -// it walks save path to count files. -func (fp *FileProvider) SessionAll() int { - return (*session.FileProvider)(fp).SessionAll(context.Background()) -} - -// SessionRegenerate Generate new sid for file session. -// it delete old file and create new file named from new sid. -func (fp *FileProvider) SessionRegenerate(oldsid, sid string) (Store, error) { - s, err := (*session.FileProvider)(fp).SessionRegenerate(context.Background(), oldsid, sid) - return &NewToOldStoreAdapter{ - delegate: s, - }, err -} diff --git a/adapter/session/sess_file_test.go b/adapter/session/sess_file_test.go deleted file mode 100644 index 4c90a3ac..00000000 --- a/adapter/session/sess_file_test.go +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 session - -import ( - "fmt" - "os" - "sync" - "testing" - "time" -) - -const sid = "Session_id" -const sidNew = "Session_id_new" -const sessionPath = "./_session_runtime" - -var ( - mutex sync.Mutex -) - -func TestFileProvider_SessionExist(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(180, sessionPath) - - if fp.SessionExist(sid) { - t.Error() - } - - _, err := fp.SessionRead(sid) - if err != nil { - t.Error(err) - } - - if !fp.SessionExist(sid) { - t.Error() - } -} - -func TestFileProvider_SessionExist2(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(180, sessionPath) - - if fp.SessionExist(sid) { - t.Error() - } - - if fp.SessionExist("") { - t.Error() - } - - if fp.SessionExist("1") { - t.Error() - } -} - -func TestFileProvider_SessionRead(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(180, sessionPath) - - s, err := fp.SessionRead(sid) - if err != nil { - t.Error(err) - } - - _ = s.Set("sessionValue", 18975) - v := s.Get("sessionValue") - - if v.(int) != 18975 { - t.Error() - } -} - -func TestFileProvider_SessionRead1(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(180, sessionPath) - - _, err := fp.SessionRead("") - if err == nil { - t.Error(err) - } - - _, err = fp.SessionRead("1") - if err == nil { - t.Error(err) - } -} - -func TestFileProvider_SessionAll(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(180, sessionPath) - - sessionCount := 546 - - for i := 1; i <= sessionCount; i++ { - _, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i)) - if err != nil { - t.Error(err) - } - } - - if fp.SessionAll() != sessionCount { - t.Error() - } -} - -func TestFileProvider_SessionRegenerate(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(180, sessionPath) - - _, err := fp.SessionRead(sid) - if err != nil { - t.Error(err) - } - - if !fp.SessionExist(sid) { - t.Error() - } - - _, err = fp.SessionRegenerate(sid, sidNew) - if err != nil { - t.Error(err) - } - - if fp.SessionExist(sid) { - t.Error() - } - - if !fp.SessionExist(sidNew) { - t.Error() - } -} - -func TestFileProvider_SessionDestroy(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(180, sessionPath) - - _, err := fp.SessionRead(sid) - if err != nil { - t.Error(err) - } - - if !fp.SessionExist(sid) { - t.Error() - } - - err = fp.SessionDestroy(sid) - if err != nil { - t.Error(err) - } - - if fp.SessionExist(sid) { - t.Error() - } -} - -func TestFileProvider_SessionGC(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(1, sessionPath) - - sessionCount := 412 - - for i := 1; i <= sessionCount; i++ { - _, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i)) - if err != nil { - t.Error(err) - } - } - - time.Sleep(2 * time.Second) - - fp.SessionGC() - if fp.SessionAll() != 0 { - t.Error() - } -} - -func TestFileSessionStore_Set(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(180, sessionPath) - - sessionCount := 100 - s, _ := fp.SessionRead(sid) - for i := 1; i <= sessionCount; i++ { - err := s.Set(i, i) - if err != nil { - t.Error(err) - } - } -} - -func TestFileSessionStore_Get(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(180, sessionPath) - - sessionCount := 100 - s, _ := fp.SessionRead(sid) - for i := 1; i <= sessionCount; i++ { - _ = s.Set(i, i) - - v := s.Get(i) - if v.(int) != i { - t.Error() - } - } -} - -func TestFileSessionStore_Delete(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(180, sessionPath) - - s, _ := fp.SessionRead(sid) - s.Set("1", 1) - - if s.Get("1") == nil { - t.Error() - } - - s.Delete("1") - - if s.Get("1") != nil { - t.Error() - } -} - -func TestFileSessionStore_Flush(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(180, sessionPath) - - sessionCount := 100 - s, _ := fp.SessionRead(sid) - for i := 1; i <= sessionCount; i++ { - _ = s.Set(i, i) - } - - _ = s.Flush() - - for i := 1; i <= sessionCount; i++ { - if s.Get(i) != nil { - t.Error() - } - } -} - -func TestFileSessionStore_SessionID(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(180, sessionPath) - - sessionCount := 85 - - for i := 1; i <= sessionCount; i++ { - s, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i)) - if err != nil { - t.Error(err) - } - if s.SessionID() != fmt.Sprintf("%s_%d", sid, i) { - t.Error(err) - } - } -} diff --git a/adapter/session/sess_mem.go b/adapter/session/sess_mem.go deleted file mode 100644 index febed719..00000000 --- a/adapter/session/sess_mem.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 session - -import ( - "context" - "net/http" - - "github.com/astaxie/beego/server/web/session" -) - -// MemSessionStore memory session store. -// it saved sessions in a map in memory. -type MemSessionStore session.MemSessionStore - -// Set value to memory session -func (st *MemSessionStore) Set(key, value interface{}) error { - return (*session.MemSessionStore)(st).Set(context.Background(), key, value) -} - -// Get value from memory session by key -func (st *MemSessionStore) Get(key interface{}) interface{} { - return (*session.MemSessionStore)(st).Get(context.Background(), key) -} - -// Delete in memory session by key -func (st *MemSessionStore) Delete(key interface{}) error { - return (*session.MemSessionStore)(st).Delete(context.Background(), key) -} - -// Flush clear all values in memory session -func (st *MemSessionStore) Flush() error { - return (*session.MemSessionStore)(st).Flush(context.Background()) -} - -// SessionID get this id of memory session store -func (st *MemSessionStore) SessionID() string { - return (*session.MemSessionStore)(st).SessionID(context.Background()) -} - -// SessionRelease Implement method, no used. -func (st *MemSessionStore) SessionRelease(w http.ResponseWriter) { - (*session.MemSessionStore)(st).SessionRelease(context.Background(), w) -} - -// MemProvider Implement the provider interface -type MemProvider session.MemProvider - -// SessionInit init memory session -func (pder *MemProvider) SessionInit(maxlifetime int64, savePath string) error { - return (*session.MemProvider)(pder).SessionInit(context.Background(), maxlifetime, savePath) -} - -// SessionRead get memory session store by sid -func (pder *MemProvider) SessionRead(sid string) (Store, error) { - s, err := (*session.MemProvider)(pder).SessionRead(context.Background(), sid) - return &NewToOldStoreAdapter{ - delegate: s, - }, err -} - -// SessionExist check session store exist in memory session by sid -func (pder *MemProvider) SessionExist(sid string) bool { - res, _ := (*session.MemProvider)(pder).SessionExist(context.Background(), sid) - return res -} - -// SessionRegenerate generate new sid for session store in memory session -func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (Store, error) { - s, err := (*session.MemProvider)(pder).SessionRegenerate(context.Background(), oldsid, sid) - return &NewToOldStoreAdapter{ - delegate: s, - }, err -} - -// SessionDestroy delete session store in memory session by id -func (pder *MemProvider) SessionDestroy(sid string) error { - return (*session.MemProvider)(pder).SessionDestroy(context.Background(), sid) -} - -// SessionGC clean expired session stores in memory session -func (pder *MemProvider) SessionGC() { - (*session.MemProvider)(pder).SessionGC(context.Background()) -} - -// SessionAll get count number of memory session -func (pder *MemProvider) SessionAll() int { - return (*session.MemProvider)(pder).SessionAll(context.Background()) -} - -// SessionUpdate expand time of session store by id in memory session -func (pder *MemProvider) SessionUpdate(sid string) error { - return (*session.MemProvider)(pder).SessionUpdate(context.Background(), sid) -} diff --git a/adapter/session/sess_test.go b/adapter/session/sess_test.go deleted file mode 100644 index aba702ca..00000000 --- a/adapter/session/sess_test.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 session - -import ( - "testing" -) - -func Test_gob(t *testing.T) { - a := make(map[interface{}]interface{}) - a["username"] = "astaxie" - a[12] = 234 - a["user"] = User{"asta", "xie"} - b, err := EncodeGob(a) - if err != nil { - t.Error(err) - } - c, err := DecodeGob(b) - if err != nil { - t.Error(err) - } - if len(c) == 0 { - t.Error("decodeGob empty") - } - if c["username"] != "astaxie" { - t.Error("decode string error") - } - if c[12] != 234 { - t.Error("decode int error") - } - if c["user"].(User).Username != "asta" { - t.Error("decode struct error") - } -} - -type User struct { - Username string - NickName string -} diff --git a/adapter/session/sess_utils.go b/adapter/session/sess_utils.go deleted file mode 100644 index 4cfdc760..00000000 --- a/adapter/session/sess_utils.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 session - -import ( - "github.com/astaxie/beego/server/web/session" -) - -// EncodeGob encode the obj to gob -func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) { - return session.EncodeGob(obj) -} - -// DecodeGob decode data to map -func DecodeGob(encoded []byte) (map[interface{}]interface{}, error) { - return session.DecodeGob(encoded) -} diff --git a/adapter/session/session.go b/adapter/session/session.go deleted file mode 100644 index d8b151b7..00000000 --- a/adapter/session/session.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 session provider -// -// Usage: -// import( -// "github.com/astaxie/beego/session" -// ) -// -// func init() { -// globalSessions, _ = session.NewManager("memory", `{"cookieName":"gosessionid", "enableSetCookie,omitempty": true, "gclifetime":3600, "maxLifetime": 3600, "secure": false, "cookieLifeTime": 3600, "providerConfig": ""}`) -// go globalSessions.GC() -// } -// -// more docs: http://beego.me/docs/module/session.md -package session - -import ( - "io" - "net/http" - "os" - - "github.com/astaxie/beego/server/web/session" -) - -// Store contains all data for one session process with specific id. -type Store interface { - Set(key, value interface{}) error // set session value - Get(key interface{}) interface{} // get session value - Delete(key interface{}) error // delete session value - SessionID() string // back current sessionID - SessionRelease(w http.ResponseWriter) // release the resource & save data to provider & return the data - Flush() error // delete all data -} - -// Provider contains global session methods and saved SessionStores. -// it can operate a SessionStore by its id. -type Provider interface { - SessionInit(gclifetime int64, config string) error - SessionRead(sid string) (Store, error) - SessionExist(sid string) bool - SessionRegenerate(oldsid, sid string) (Store, error) - SessionDestroy(sid string) error - SessionAll() int // get all active session - SessionGC() -} - -// SLogger a helpful variable to log information about session -var SLogger = NewSessionLog(os.Stderr) - -// Register makes a session provide available by the provided name. -// If Register is called twice with the same name or if driver is nil, -// it panics. -func Register(name string, provide Provider) { - session.Register(name, &oldToNewProviderAdapter{ - delegate: provide, - }) -} - -// GetProvider -func GetProvider(name string) (Provider, error) { - res, err := session.GetProvider(name) - if adt, ok := res.(*oldToNewProviderAdapter); err == nil && ok { - return adt.delegate, err - } - - return &newToOldProviderAdapter{ - delegate: res, - }, err -} - -// ManagerConfig define the session config -type ManagerConfig session.ManagerConfig - -// Manager contains Provider and its configuration. -type Manager session.Manager - -// NewManager Create new Manager with provider name and json config string. -// provider name: -// 1. cookie -// 2. file -// 3. memory -// 4. redis -// 5. mysql -// json config: -// 1. is https default false -// 2. hashfunc default sha1 -// 3. hashkey default beegosessionkey -// 4. maxage default is none -func NewManager(provideName string, cf *ManagerConfig) (*Manager, error) { - m, err := session.NewManager(provideName, (*session.ManagerConfig)(cf)) - return (*Manager)(m), err -} - -// GetProvider return current manager's provider -func (manager *Manager) GetProvider() Provider { - return &newToOldProviderAdapter{ - delegate: (*session.Manager)(manager).GetProvider(), - } -} - -// SessionStart generate or read the session id from http request. -// if session id exists, return SessionStore with this id. -func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (Store, error) { - s, err := (*session.Manager)(manager).SessionStart(w, r) - return &NewToOldStoreAdapter{ - delegate: s, - }, err -} - -// SessionDestroy Destroy session by its id in http request cookie. -func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) { - (*session.Manager)(manager).SessionDestroy(w, r) -} - -// GetSessionStore Get SessionStore by its id. -func (manager *Manager) GetSessionStore(sid string) (Store, error) { - s, err := (*session.Manager)(manager).GetSessionStore(sid) - return &NewToOldStoreAdapter{ - delegate: s, - }, err -} - -// GC Start session gc process. -// it can do gc in times after gc lifetime. -func (manager *Manager) GC() { - (*session.Manager)(manager).GC() -} - -// SessionRegenerateID Regenerate a session id for this SessionStore who's id is saving in http request. -func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Request) Store { - s := (*session.Manager)(manager).SessionRegenerateID(w, r) - return &NewToOldStoreAdapter{ - delegate: s, - } -} - -// GetActiveSession Get all active sessions count number. -func (manager *Manager) GetActiveSession() int { - return (*session.Manager)(manager).GetActiveSession() -} - -// SetSecure Set cookie with https. -func (manager *Manager) SetSecure(secure bool) { - (*session.Manager)(manager).SetSecure(secure) -} - -// Log implement the log.Logger -type Log session.Log - -// NewSessionLog set io.Writer to create a Logger for session. -func NewSessionLog(out io.Writer) *Log { - return (*Log)(session.NewSessionLog(out)) -} diff --git a/adapter/session/ssdb/sess_ssdb.go b/adapter/session/ssdb/sess_ssdb.go deleted file mode 100644 index cd9c4a24..00000000 --- a/adapter/session/ssdb/sess_ssdb.go +++ /dev/null @@ -1,84 +0,0 @@ -package ssdb - -import ( - "context" - "net/http" - - "github.com/astaxie/beego/adapter/session" - - beeSsdb "github.com/astaxie/beego/server/web/session/ssdb" -) - -// Provider holds ssdb client and configs -type Provider beeSsdb.Provider - -// SessionInit init the ssdb with the config -func (p *Provider) SessionInit(maxLifetime int64, savePath string) error { - return (*beeSsdb.Provider)(p).SessionInit(context.Background(), maxLifetime, savePath) -} - -// SessionRead return a ssdb client session Store -func (p *Provider) SessionRead(sid string) (session.Store, error) { - s, err := (*beeSsdb.Provider)(p).SessionRead(context.Background(), sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionExist judged whether sid is exist in session -func (p *Provider) SessionExist(sid string) bool { - res, _ := (*beeSsdb.Provider)(p).SessionExist(context.Background(), sid) - return res -} - -// SessionRegenerate regenerate session with new sid and delete oldsid -func (p *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - s, err := (*beeSsdb.Provider)(p).SessionRegenerate(context.Background(), oldsid, sid) - return session.CreateNewToOldStoreAdapter(s), err -} - -// SessionDestroy destroy the sid -func (p *Provider) SessionDestroy(sid string) error { - return (*beeSsdb.Provider)(p).SessionDestroy(context.Background(), sid) -} - -// SessionGC not implemented -func (p *Provider) SessionGC() { - (*beeSsdb.Provider)(p).SessionGC(context.Background()) -} - -// SessionAll not implemented -func (p *Provider) SessionAll() int { - return (*beeSsdb.Provider)(p).SessionAll(context.Background()) -} - -// SessionStore holds the session information which stored in ssdb -type SessionStore beeSsdb.SessionStore - -// Set the key and value -func (s *SessionStore) Set(key, value interface{}) error { - return (*beeSsdb.SessionStore)(s).Set(context.Background(), key, value) -} - -// Get return the value by the key -func (s *SessionStore) Get(key interface{}) interface{} { - return (*beeSsdb.SessionStore)(s).Get(context.Background(), key) -} - -// Delete the key in session store -func (s *SessionStore) Delete(key interface{}) error { - return (*beeSsdb.SessionStore)(s).Delete(context.Background(), key) -} - -// Flush delete all keys and values -func (s *SessionStore) Flush() error { - return (*beeSsdb.SessionStore)(s).Flush(context.Background()) -} - -// SessionID return the sessionID -func (s *SessionStore) SessionID() string { - return (*beeSsdb.SessionStore)(s).SessionID(context.Background()) -} - -// SessionRelease Store the keyvalues into ssdb -func (s *SessionStore) SessionRelease(w http.ResponseWriter) { - (*beeSsdb.SessionStore)(s).SessionRelease(context.Background(), w) -} diff --git a/adapter/session/store_adapter.go b/adapter/session/store_adapter.go deleted file mode 100644 index 70ad83e2..00000000 --- a/adapter/session/store_adapter.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 session - -import ( - "context" - "net/http" - - "github.com/astaxie/beego/server/web/session" -) - -type NewToOldStoreAdapter struct { - delegate session.Store -} - -func CreateNewToOldStoreAdapter(s session.Store) Store { - return &NewToOldStoreAdapter{ - delegate: s, - } -} - -func (n *NewToOldStoreAdapter) Set(key, value interface{}) error { - return n.delegate.Set(context.Background(), key, value) -} - -func (n *NewToOldStoreAdapter) Get(key interface{}) interface{} { - return n.delegate.Get(context.Background(), key) -} - -func (n *NewToOldStoreAdapter) Delete(key interface{}) error { - return n.delegate.Delete(context.Background(), key) -} - -func (n *NewToOldStoreAdapter) SessionID() string { - return n.delegate.SessionID(context.Background()) -} - -func (n *NewToOldStoreAdapter) SessionRelease(w http.ResponseWriter) { - n.delegate.SessionRelease(context.Background(), w) -} - -func (n *NewToOldStoreAdapter) Flush() error { - return n.delegate.Flush(context.Background()) -} - -type oldToNewStoreAdapter struct { - delegate Store -} - -func (o *oldToNewStoreAdapter) Set(ctx context.Context, key, value interface{}) error { - return o.delegate.Set(key, value) -} - -func (o *oldToNewStoreAdapter) Get(ctx context.Context, key interface{}) interface{} { - return o.delegate.Get(key) -} - -func (o *oldToNewStoreAdapter) Delete(ctx context.Context, key interface{}) error { - return o.delegate.Delete(key) -} - -func (o *oldToNewStoreAdapter) SessionID(ctx context.Context) string { - return o.delegate.SessionID() -} - -func (o *oldToNewStoreAdapter) SessionRelease(ctx context.Context, w http.ResponseWriter) { - o.delegate.SessionRelease(w) -} - -func (o *oldToNewStoreAdapter) Flush(ctx context.Context) error { - return o.delegate.Flush() -} diff --git a/adapter/swagger/swagger.go b/adapter/swagger/swagger.go deleted file mode 100644 index 7a44b770..00000000 --- a/adapter/swagger/swagger.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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. -// -// Swagger™ is a project used to describe and document RESTful APIs. -// -// The Swagger specification defines a set of files required to describe such an API. These files can then be used by the Swagger-UI project to display the API and Swagger-Codegen to generate clients in various languages. Additional utilities can also take advantage of the resulting files, such as testing tools. -// Now in version 2.0, Swagger is more enabling than ever. And it's 100% open source software. - -// Package swagger struct definition -package swagger - -import ( - "github.com/astaxie/beego/server/web/swagger" -) - -// Swagger list the resource -type Swagger swagger.Swagger - -// Information Provides metadata about the API. The metadata can be used by the clients if needed. -type Information swagger.Information - -// Contact information for the exposed API. -type Contact swagger.Contact - -// License information for the exposed API. -type License swagger.License - -// Item Describes the operations available on a single path. -type Item swagger.Item - -// Operation Describes a single API operation on a path. -type Operation swagger.Operation - -// Parameter Describes a single operation parameter. -type Parameter swagger.Parameter - -// ParameterItems A limited subset of JSON-Schema's items object. It is used by parameter definitions that are not located in "body". -// http://swagger.io/specification/#itemsObject -type ParameterItems swagger.ParameterItems - -// Schema Object allows the definition of input and output data types. -type Schema swagger.Schema - -// Propertie are taken from the JSON Schema definition but their definitions were adjusted to the Swagger Specification -type Propertie swagger.Propertie - -// Response as they are returned from executing this operation. -type Response swagger.Response - -// Security Allows the definition of a security scheme that can be used by the operations -type Security swagger.Security - -// Tag Allows adding meta data to a single tag that is used by the Operation Object -type Tag swagger.Tag - -// ExternalDocs include Additional external documentation -type ExternalDocs swagger.ExternalDocs diff --git a/adapter/template.go b/adapter/template.go deleted file mode 100644 index 67f5a33b..00000000 --- a/adapter/template.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 adapter - -import ( - "html/template" - "io" - "net/http" - - "github.com/astaxie/beego/server/web" -) - -// ExecuteTemplate applies the template with name to the specified data object, -// writing the output to wr. -// A template will be executed safely in parallel. -func ExecuteTemplate(wr io.Writer, name string, data interface{}) error { - return web.ExecuteTemplate(wr, name, data) -} - -// ExecuteViewPathTemplate applies the template with name and from specific viewPath to the specified data object, -// writing the output to wr. -// A template will be executed safely in parallel. -func ExecuteViewPathTemplate(wr io.Writer, name string, viewPath string, data interface{}) error { - return web.ExecuteViewPathTemplate(wr, name, viewPath, data) -} - -// AddFuncMap let user to register a func in the template. -func AddFuncMap(key string, fn interface{}) error { - return web.AddFuncMap(key, fn) -} - -type templatePreProcessor func(root, path string, funcs template.FuncMap) (*template.Template, error) - -type templateFile struct { - root string - files map[string][]string -} - -// HasTemplateExt return this path contains supported template extension of beego or not. -func HasTemplateExt(paths string) bool { - return web.HasTemplateExt(paths) -} - -// AddTemplateExt add new extension for template. -func AddTemplateExt(ext string) { - web.AddTemplateExt(ext) -} - -// AddViewPath adds a new path to the supported view paths. -// Can later be used by setting a controller ViewPath to this folder -// will panic if called after beego.Run() -func AddViewPath(viewPath string) error { - return web.AddViewPath(viewPath) -} - -// BuildTemplate will build all template files in a directory. -// it makes beego can render any template file in view directory. -func BuildTemplate(dir string, files ...string) error { - return web.BuildTemplate(dir, files...) -} - -type templateFSFunc func() http.FileSystem - -func defaultFSFunc() http.FileSystem { - return FileSystem{} -} - -// SetTemplateFSFunc set default filesystem function -func SetTemplateFSFunc(fnt templateFSFunc) { - web.SetTemplateFSFunc(func() http.FileSystem { - return fnt() - }) -} - -// SetViewsPath sets view directory path in beego application. -func SetViewsPath(path string) *App { - return (*App)(web.SetViewsPath(path)) -} - -// SetStaticPath sets static directory path and proper url pattern in beego application. -// if beego.SetStaticPath("static","public"), visit /static/* to load static file in folder "public". -func SetStaticPath(url string, path string) *App { - return (*App)(web.SetStaticPath(url, path)) -} - -// DelStaticPath removes the static folder setting in this url pattern in beego application. -func DelStaticPath(url string) *App { - return (*App)(web.DelStaticPath(url)) -} - -// AddTemplateEngine add a new templatePreProcessor which support extension -func AddTemplateEngine(extension string, fn templatePreProcessor) *App { - return (*App)(web.AddTemplateEngine(extension, func(root, path string, funcs template.FuncMap) (*template.Template, error) { - return fn(root, path, funcs) - })) -} diff --git a/adapter/templatefunc.go b/adapter/templatefunc.go deleted file mode 100644 index 0c805393..00000000 --- a/adapter/templatefunc.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 adapter - -import ( - "html/template" - "net/url" - "time" - - "github.com/astaxie/beego/server/web" -) - -const ( - formatTime = "15:04:05" - formatDate = "2006-01-02" - formatDateTime = "2006-01-02 15:04:05" - formatDateTimeT = "2006-01-02T15:04:05" -) - -// Substr returns the substr from start to length. -func Substr(s string, start, length int) string { - return web.Substr(s, start, length) -} - -// HTML2str returns escaping text convert from html. -func HTML2str(html string) string { - return web.HTML2str(html) -} - -// DateFormat takes a time and a layout string and returns a string with the formatted date. Used by the template parser as "dateformat" -func DateFormat(t time.Time, layout string) (datestring string) { - return web.DateFormat(t, layout) -} - -// DateParse Parse Date use PHP time format. -func DateParse(dateString, format string) (time.Time, error) { - return web.DateParse(dateString, format) -} - -// Date takes a PHP like date func to Go's time format. -func Date(t time.Time, format string) string { - return web.Date(t, format) -} - -// Compare is a quick and dirty comparison function. It will convert whatever you give it to strings and see if the two values are equal. -// Whitespace is trimmed. Used by the template parser as "eq". -func Compare(a, b interface{}) (equal bool) { - return web.Compare(a, b) -} - -// CompareNot !Compare -func CompareNot(a, b interface{}) (equal bool) { - return web.CompareNot(a, b) -} - -// NotNil the same as CompareNot -func NotNil(a interface{}) (isNil bool) { - return web.NotNil(a) -} - -// GetConfig get the Appconfig -func GetConfig(returnType, key string, defaultVal interface{}) (interface{}, error) { - return web.GetConfig(returnType, key, defaultVal) -} - -// Str2html Convert string to template.HTML type. -func Str2html(raw string) template.HTML { - return web.Str2html(raw) -} - -// Htmlquote returns quoted html string. -func Htmlquote(text string) string { - return web.Htmlquote(text) -} - -// Htmlunquote returns unquoted html string. -func Htmlunquote(text string) string { - return web.Htmlunquote(text) -} - -// URLFor returns url string with another registered controller handler with params. -// usage: -// -// URLFor(".index") -// print URLFor("index") -// router /login -// print URLFor("login") -// print URLFor("login", "next","/"") -// router /profile/:username -// print UrlFor("profile", ":username","John Doe") -// result: -// / -// /login -// /login?next=/ -// /user/John%20Doe -// -// more detail http://beego.me/docs/mvc/controller/urlbuilding.md -func URLFor(endpoint string, values ...interface{}) string { - return web.URLFor(endpoint, values...) -} - -// AssetsJs returns script tag with src string. -func AssetsJs(text string) template.HTML { - return web.AssetsJs(text) -} - -// AssetsCSS returns stylesheet link tag with src string. -func AssetsCSS(text string) template.HTML { - - text = "" - - return template.HTML(text) -} - -// ParseForm will parse form values to struct via tag. -func ParseForm(form url.Values, obj interface{}) error { - return web.ParseForm(form, obj) -} - -// RenderForm will render object to form html. -// obj must be a struct pointer. -func RenderForm(obj interface{}) template.HTML { - return web.RenderForm(obj) -} - -// MapGet getting value from map by keys -// usage: -// Data["m"] = M{ -// "a": 1, -// "1": map[string]float64{ -// "c": 4, -// }, -// } -// -// {{ map_get m "a" }} // return 1 -// {{ map_get m 1 "c" }} // return 4 -func MapGet(arg1 interface{}, arg2 ...interface{}) (interface{}, error) { - return web.MapGet(arg1, arg2...) -} diff --git a/adapter/templatefunc_test.go b/adapter/templatefunc_test.go deleted file mode 100644 index f5113606..00000000 --- a/adapter/templatefunc_test.go +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 adapter - -import ( - "html/template" - "net/url" - "testing" - "time" -) - -func TestSubstr(t *testing.T) { - s := `012345` - if Substr(s, 0, 2) != "01" { - t.Error("should be equal") - } - if Substr(s, 0, 100) != "012345" { - t.Error("should be equal") - } - if Substr(s, 12, 100) != "012345" { - t.Error("should be equal") - } -} - -func TestHtml2str(t *testing.T) { - h := `<123> 123\n - - - \n` - if HTML2str(h) != "123\\n\n\\n" { - t.Error("should be equal") - } -} - -func TestDateFormat(t *testing.T) { - ts := "Mon, 01 Jul 2013 13:27:42 CST" - tt, _ := time.Parse(time.RFC1123, ts) - - if ss := DateFormat(tt, "2006-01-02 15:04:05"); ss != "2013-07-01 13:27:42" { - t.Errorf("2013-07-01 13:27:42 does not equal %v", ss) - } -} - -func TestDate(t *testing.T) { - ts := "Mon, 01 Jul 2013 13:27:42 CST" - tt, _ := time.Parse(time.RFC1123, ts) - - if ss := Date(tt, "Y-m-d H:i:s"); ss != "2013-07-01 13:27:42" { - t.Errorf("2013-07-01 13:27:42 does not equal %v", ss) - } - if ss := Date(tt, "y-n-j h:i:s A"); ss != "13-7-1 01:27:42 PM" { - t.Errorf("13-7-1 01:27:42 PM does not equal %v", ss) - } - if ss := Date(tt, "D, d M Y g:i:s a"); ss != "Mon, 01 Jul 2013 1:27:42 pm" { - t.Errorf("Mon, 01 Jul 2013 1:27:42 pm does not equal %v", ss) - } - if ss := Date(tt, "l, d F Y G:i:s"); ss != "Monday, 01 July 2013 13:27:42" { - t.Errorf("Monday, 01 July 2013 13:27:42 does not equal %v", ss) - } -} - -func TestCompareRelated(t *testing.T) { - if !Compare("abc", "abc") { - t.Error("should be equal") - } - if Compare("abc", "aBc") { - t.Error("should be not equal") - } - if !Compare("1", 1) { - t.Error("should be equal") - } - if CompareNot("abc", "abc") { - t.Error("should be equal") - } - if !CompareNot("abc", "aBc") { - t.Error("should be not equal") - } - if !NotNil("a string") { - t.Error("should not be nil") - } -} - -func TestHtmlquote(t *testing.T) { - h := `<' ”“&">` - s := `<' ”“&">` - if Htmlquote(s) != h { - t.Error("should be equal") - } -} - -func TestHtmlunquote(t *testing.T) { - h := `<' ”“&">` - s := `<' ”“&">` - if Htmlunquote(h) != s { - t.Error("should be equal") - } -} - -func TestParseForm(t *testing.T) { - type ExtendInfo struct { - Hobby []string `form:"hobby"` - Memo string - } - - type OtherInfo struct { - Organization string `form:"organization"` - Title string `form:"title"` - ExtendInfo - } - - type user struct { - ID int `form:"-"` - tag string `form:"tag"` - Name interface{} `form:"username"` - Age int `form:"age,text"` - Email string - Intro string `form:",textarea"` - StrBool bool `form:"strbool"` - Date time.Time `form:"date,2006-01-02"` - OtherInfo - } - - u := user{} - form := url.Values{ - "ID": []string{"1"}, - "-": []string{"1"}, - "tag": []string{"no"}, - "username": []string{"test"}, - "age": []string{"40"}, - "Email": []string{"test@gmail.com"}, - "Intro": []string{"I am an engineer!"}, - "strbool": []string{"yes"}, - "date": []string{"2014-11-12"}, - "organization": []string{"beego"}, - "title": []string{"CXO"}, - "hobby": []string{"", "Basketball", "Football"}, - "memo": []string{"nothing"}, - } - if err := ParseForm(form, u); err == nil { - t.Fatal("nothing will be changed") - } - if err := ParseForm(form, &u); err != nil { - t.Fatal(err) - } - if u.ID != 0 { - t.Errorf("ID should equal 0 but got %v", u.ID) - } - if len(u.tag) != 0 { - t.Errorf("tag's length should equal 0 but got %v", len(u.tag)) - } - if u.Name.(string) != "test" { - t.Errorf("Name should equal `test` but got `%v`", u.Name.(string)) - } - if u.Age != 40 { - t.Errorf("Age should equal 40 but got %v", u.Age) - } - if u.Email != "test@gmail.com" { - t.Errorf("Email should equal `test@gmail.com` but got `%v`", u.Email) - } - if u.Intro != "I am an engineer!" { - t.Errorf("Intro should equal `I am an engineer!` but got `%v`", u.Intro) - } - if !u.StrBool { - t.Errorf("strboll should equal `true`, but got `%v`", u.StrBool) - } - y, m, d := u.Date.Date() - if y != 2014 || m.String() != "November" || d != 12 { - t.Errorf("Date should equal `2014-11-12`, but got `%v`", u.Date.String()) - } - if u.Organization != "beego" { - t.Errorf("Organization should equal `beego`, but got `%v`", u.Organization) - } - if u.Title != "CXO" { - t.Errorf("Title should equal `CXO`, but got `%v`", u.Title) - } - if u.Hobby[0] != "" { - t.Errorf("Hobby should equal ``, but got `%v`", u.Hobby[0]) - } - if u.Hobby[1] != "Basketball" { - t.Errorf("Hobby should equal `Basketball`, but got `%v`", u.Hobby[1]) - } - if u.Hobby[2] != "Football" { - t.Errorf("Hobby should equal `Football`, but got `%v`", u.Hobby[2]) - } - if len(u.Memo) != 0 { - t.Errorf("Memo's length should equal 0 but got %v", len(u.Memo)) - } -} - -func TestRenderForm(t *testing.T) { - type user struct { - ID int `form:"-"` - Name interface{} `form:"username"` - Age int `form:"age,text,年龄:"` - Sex string - Email []string - Intro string `form:",textarea"` - Ignored string `form:"-"` - } - - u := user{Name: "test", Intro: "Some Text"} - output := RenderForm(u) - if output != template.HTML("") { - t.Errorf("output should be empty but got %v", output) - } - output = RenderForm(&u) - result := template.HTML( - `Name:
` + - `年龄:
` + - `Sex:
` + - `Intro: `) - if output != result { - t.Errorf("output should equal `%v` but got `%v`", result, output) - } -} - -func TestMapGet(t *testing.T) { - // test one level map - m1 := map[string]int64{ - "a": 1, - "1": 2, - } - - if res, err := MapGet(m1, "a"); err == nil { - if res.(int64) != 1 { - t.Errorf("Should return 1, but return %v", res) - } - } else { - t.Errorf("Error happens %v", err) - } - - if res, err := MapGet(m1, "1"); err == nil { - if res.(int64) != 2 { - t.Errorf("Should return 2, but return %v", res) - } - } else { - t.Errorf("Error happens %v", err) - } - - if res, err := MapGet(m1, 1); err == nil { - if res.(int64) != 2 { - t.Errorf("Should return 2, but return %v", res) - } - } else { - t.Errorf("Error happens %v", err) - } - - // test 2 level map - m2 := M{ - "1": map[string]float64{ - "2": 3.5, - }, - } - - if res, err := MapGet(m2, 1, 2); err == nil { - if res.(float64) != 3.5 { - t.Errorf("Should return 3.5, but return %v", res) - } - } else { - t.Errorf("Error happens %v", err) - } - - // test 5 level map - m5 := M{ - "1": M{ - "2": M{ - "3": M{ - "4": M{ - "5": 1.2, - }, - }, - }, - }, - } - - if res, err := MapGet(m5, 1, 2, 3, 4, 5); err == nil { - if res.(float64) != 1.2 { - t.Errorf("Should return 1.2, but return %v", res) - } - } else { - t.Errorf("Error happens %v", err) - } - - // check whether element not exists in map - if res, err := MapGet(m5, 5, 4, 3, 2, 1); err == nil { - if res != nil { - t.Errorf("Should return nil, but return %v", res) - } - } else { - t.Errorf("Error happens %v", err) - } -} diff --git a/adapter/testing/client.go b/adapter/testing/client.go deleted file mode 100644 index 5c138167..00000000 --- a/adapter/testing/client.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 testing - -import ( - "github.com/astaxie/beego/client/httplib/testing" -) - -var port = "" -var baseURL = "http://localhost:" - -// TestHTTPRequest beego test request client -type TestHTTPRequest testing.TestHTTPRequest - -// Get returns test client in GET method -func Get(path string) *TestHTTPRequest { - return (*TestHTTPRequest)(testing.Get(path)) -} - -// Post returns test client in POST method -func Post(path string) *TestHTTPRequest { - return (*TestHTTPRequest)(testing.Post(path)) -} - -// Put returns test client in PUT method -func Put(path string) *TestHTTPRequest { - return (*TestHTTPRequest)(testing.Put(path)) -} - -// Delete returns test client in DELETE method -func Delete(path string) *TestHTTPRequest { - return (*TestHTTPRequest)(testing.Delete(path)) -} - -// Head returns test client in HEAD method -func Head(path string) *TestHTTPRequest { - return (*TestHTTPRequest)(testing.Head(path)) -} diff --git a/adapter/toolbox/healthcheck.go b/adapter/toolbox/healthcheck.go deleted file mode 100644 index 7d89c2fb..00000000 --- a/adapter/toolbox/healthcheck.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 toolbox healthcheck -// -// type DatabaseCheck struct { -// } -// -// func (dc *DatabaseCheck) Check() error { -// if dc.isConnected() { -// return nil -// } else { -// return errors.New("can't connect database") -// } -// } -// -// AddHealthCheck("database",&DatabaseCheck{}) -// -// more docs: http://beego.me/docs/module/toolbox.md -package toolbox - -import ( - "github.com/astaxie/beego/core/governor" -) - -// AdminCheckList holds health checker map -// Deprecated using governor.AdminCheckList -var AdminCheckList map[string]HealthChecker - -// HealthChecker health checker interface -type HealthChecker governor.HealthChecker - -// AddHealthCheck add health checker with name string -func AddHealthCheck(name string, hc HealthChecker) { - governor.AddHealthCheck(name, hc) - AdminCheckList[name] = hc -} - -func init() { - AdminCheckList = make(map[string]HealthChecker) -} diff --git a/adapter/toolbox/profile.go b/adapter/toolbox/profile.go deleted file mode 100644 index a5434360..00000000 --- a/adapter/toolbox/profile.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 toolbox - -import ( - "io" - "os" - "time" - - "github.com/astaxie/beego/core/governor" -) - -var startTime = time.Now() -var pid int - -func init() { - pid = os.Getpid() -} - -// ProcessInput parse input command string -func ProcessInput(input string, w io.Writer) { - governor.ProcessInput(input, w) -} - -// MemProf record memory profile in pprof -func MemProf(w io.Writer) { - governor.MemProf(w) -} - -// GetCPUProfile start cpu profile monitor -func GetCPUProfile(w io.Writer) { - governor.GetCPUProfile(w) -} - -// PrintGCSummary print gc information to io.Writer -func PrintGCSummary(w io.Writer) { - governor.PrintGCSummary(w) -} diff --git a/adapter/toolbox/statistics.go b/adapter/toolbox/statistics.go deleted file mode 100644 index 7c8cd75e..00000000 --- a/adapter/toolbox/statistics.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 toolbox - -import ( - "time" - - "github.com/astaxie/beego/server/web" -) - -// Statistics struct -type Statistics web.Statistics - -// URLMap contains several statistics struct to log different data -type URLMap web.URLMap - -// AddStatistics add statistics task. -// it needs request method, request url, request controller and statistics time duration -func (m *URLMap) AddStatistics(requestMethod, requestURL, requestController string, requesttime time.Duration) { - (*web.URLMap)(m).AddStatistics(requestMethod, requestURL, requestController, requesttime) -} - -// GetMap put url statistics result in io.Writer -func (m *URLMap) GetMap() map[string]interface{} { - return (*web.URLMap)(m).GetMap() -} - -// GetMapData return all mapdata -func (m *URLMap) GetMapData() []map[string]interface{} { - return (*web.URLMap)(m).GetMapData() -} - -// StatisticsMap hosld global statistics data map -var StatisticsMap *URLMap - -func init() { - StatisticsMap = (*URLMap)(web.StatisticsMap) -} diff --git a/adapter/toolbox/task.go b/adapter/toolbox/task.go deleted file mode 100644 index 7f1bfc45..00000000 --- a/adapter/toolbox/task.go +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 toolbox - -import ( - "context" - "sort" - "time" - - "github.com/astaxie/beego/task" -) - -// The bounds for each field. -var ( - AdminTaskList map[string]Tasker -) - -const ( - // Set the top bit if a star was included in the expression. - starBit = 1 << 63 -) - -// Schedule time taks schedule -type Schedule task.Schedule - -// TaskFunc task func type -type TaskFunc func() error - -// Tasker task interface -type Tasker interface { - GetSpec() string - GetStatus() string - Run() error - SetNext(time.Time) - GetNext() time.Time - SetPrev(time.Time) - GetPrev() time.Time -} - -// task error -type taskerr struct { - t time.Time - errinfo string -} - -// Task task struct -// Deprecated -type Task struct { - // Deprecated - Taskname string - // Deprecated - Spec *Schedule - // Deprecated - SpecStr string - // Deprecated - DoFunc TaskFunc - // Deprecated - Prev time.Time - // Deprecated - Next time.Time - // Deprecated - Errlist []*taskerr // like errtime:errinfo - // Deprecated - ErrLimit int // max length for the errlist, 0 stand for no limit - - delegate *task.Task -} - -// NewTask add new task with name, time and func -func NewTask(tname string, spec string, f TaskFunc) *Task { - - task := task.NewTask(tname, spec, func(ctx context.Context) error { - return f() - }) - return &Task{ - delegate: task, - } -} - -// GetSpec get spec string -func (t *Task) GetSpec() string { - t.initDelegate() - - return t.delegate.GetSpec(context.Background()) -} - -// GetStatus get current task status -func (t *Task) GetStatus() string { - - t.initDelegate() - - return t.delegate.GetStatus(context.Background()) -} - -// Run run all tasks -func (t *Task) Run() error { - t.initDelegate() - return t.delegate.Run(context.Background()) -} - -// SetNext set next time for this task -func (t *Task) SetNext(now time.Time) { - t.initDelegate() - t.delegate.SetNext(context.Background(), now) -} - -// GetNext get the next call time of this task -func (t *Task) GetNext() time.Time { - t.initDelegate() - return t.delegate.GetNext(context.Background()) -} - -// SetPrev set prev time of this task -func (t *Task) SetPrev(now time.Time) { - t.initDelegate() - t.delegate.SetPrev(context.Background(), now) -} - -// GetPrev get prev time of this task -func (t *Task) GetPrev() time.Time { - t.initDelegate() - return t.delegate.GetPrev(context.Background()) -} - -// six columns mean: -// second:0-59 -// minute:0-59 -// hour:1-23 -// day:1-31 -// month:1-12 -// week:0-6(0 means Sunday) - -// SetCron some signals: -// *: any time -// ,:  separate signal -//    -:duration -// /n : do as n times of time duration -// /////////////////////////////////////////////////////// -// 0/30 * * * * * every 30s -// 0 43 21 * * * 21:43 -// 0 15 05 * * *    05:15 -// 0 0 17 * * * 17:00 -// 0 0 17 * * 1 17:00 in every Monday -// 0 0,10 17 * * 0,2,3 17:00 and 17:10 in every Sunday, Tuesday and Wednesday -// 0 0-10 17 1 * * 17:00 to 17:10 in 1 min duration each time on the first day of month -// 0 0 0 1,15 * 1 0:00 on the 1st day and 15th day of month -// 0 42 4 1 * *     4:42 on the 1st day of month -// 0 0 21 * * 1-6   21:00 from Monday to Saturday -// 0 0,10,20,30,40,50 * * * *  every 10 min duration -// 0 */10 * * * *        every 10 min duration -// 0 * 1 * * *         1:00 to 1:59 in 1 min duration each time -// 0 0 1 * * *         1:00 -// 0 0 */1 * * *        0 min of hour in 1 hour duration -// 0 0 * * * *         0 min of hour in 1 hour duration -// 0 2 8-20/3 * * *       8:02, 11:02, 14:02, 17:02, 20:02 -// 0 30 5 1,15 * *       5:30 on the 1st day and 15th day of month -func (t *Task) SetCron(spec string) { - t.initDelegate() - t.delegate.SetCron(spec) -} - -func (t *Task) initDelegate() { - if t.delegate == nil { - t.delegate = &task.Task{ - Taskname: t.Taskname, - Spec: (*task.Schedule)(t.Spec), - SpecStr: t.SpecStr, - DoFunc: func(ctx context.Context) error { - return t.DoFunc() - }, - Prev: t.Prev, - Next: t.Next, - ErrLimit: t.ErrLimit, - } - } -} - -// Next set schedule to next time -func (s *Schedule) Next(t time.Time) time.Time { - return (*task.Schedule)(s).Next(t) -} - -// StartTask start all tasks -func StartTask() { - task.StartTask() -} - -// StopTask stop all tasks -func StopTask() { - task.StopTask() -} - -// AddTask add task with name -func AddTask(taskname string, t Tasker) { - task.AddTask(taskname, &oldToNewAdapter{delegate: t}) -} - -// DeleteTask delete task with name -func DeleteTask(taskname string) { - task.DeleteTask(taskname) -} - -// ClearTask clear all tasks -func ClearTask() { - task.ClearTask() -} - -// MapSorter sort map for tasker -type MapSorter task.MapSorter - -// NewMapSorter create new tasker map -func NewMapSorter(m map[string]Tasker) *MapSorter { - - newTaskerMap := make(map[string]task.Tasker, len(m)) - - for key, value := range m { - newTaskerMap[key] = &oldToNewAdapter{ - delegate: value, - } - } - - return (*MapSorter)(task.NewMapSorter(newTaskerMap)) -} - -// Sort sort tasker map -func (ms *MapSorter) Sort() { - sort.Sort(ms) -} - -func (ms *MapSorter) Len() int { return len(ms.Keys) } -func (ms *MapSorter) Less(i, j int) bool { - if ms.Vals[i].GetNext(context.Background()).IsZero() { - return false - } - if ms.Vals[j].GetNext(context.Background()).IsZero() { - return true - } - return ms.Vals[i].GetNext(context.Background()).Before(ms.Vals[j].GetNext(context.Background())) -} -func (ms *MapSorter) Swap(i, j int) { - ms.Vals[i], ms.Vals[j] = ms.Vals[j], ms.Vals[i] - ms.Keys[i], ms.Keys[j] = ms.Keys[j], ms.Keys[i] -} - -func init() { - AdminTaskList = make(map[string]Tasker) -} - -type oldToNewAdapter struct { - delegate Tasker -} - -func (o *oldToNewAdapter) GetSpec(ctx context.Context) string { - return o.delegate.GetSpec() -} - -func (o *oldToNewAdapter) GetStatus(ctx context.Context) string { - return o.delegate.GetStatus() -} - -func (o *oldToNewAdapter) Run(ctx context.Context) error { - return o.delegate.Run() -} - -func (o *oldToNewAdapter) SetNext(ctx context.Context, t time.Time) { - o.delegate.SetNext(t) -} - -func (o *oldToNewAdapter) GetNext(ctx context.Context) time.Time { - return o.delegate.GetNext() -} - -func (o *oldToNewAdapter) SetPrev(ctx context.Context, t time.Time) { - o.delegate.SetPrev(t) -} - -func (o *oldToNewAdapter) GetPrev(ctx context.Context) time.Time { - return o.delegate.GetPrev() -} diff --git a/adapter/tree.go b/adapter/tree.go deleted file mode 100644 index 36f763ea..00000000 --- a/adapter/tree.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 adapter - -import ( - "github.com/astaxie/beego/adapter/context" - beecontext "github.com/astaxie/beego/server/web/context" - - "github.com/astaxie/beego/server/web" -) - -// Tree has three elements: FixRouter/wildcard/leaves -// fixRouter stores Fixed Router -// wildcard stores params -// leaves store the endpoint information -type Tree web.Tree - -// NewTree return a new Tree -func NewTree() *Tree { - return (*Tree)(web.NewTree()) -} - -// AddTree will add tree to the exist Tree -// prefix should has no params -func (t *Tree) AddTree(prefix string, tree *Tree) { - (*web.Tree)(t).AddTree(prefix, (*web.Tree)(tree)) -} - -// AddRouter call addseg function -func (t *Tree) AddRouter(pattern string, runObject interface{}) { - (*web.Tree)(t).AddRouter(pattern, runObject) -} - -// Match router to runObject & params -func (t *Tree) Match(pattern string, ctx *context.Context) (runObject interface{}) { - return (*web.Tree)(t).Match(pattern, (*beecontext.Context)(ctx)) -} diff --git a/adapter/tree_test.go b/adapter/tree_test.go deleted file mode 100644 index 2315d829..00000000 --- a/adapter/tree_test.go +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 adapter - -import ( - "testing" - - "github.com/astaxie/beego/adapter/context" - beecontext "github.com/astaxie/beego/server/web/context" -) - -type testinfo struct { - url string - requesturl string - params map[string]string -} - -var routers []testinfo - -func init() { - routers = make([]testinfo, 0) - routers = append(routers, testinfo{"/topic/?:auth:int", "/topic", nil}) - routers = append(routers, testinfo{"/topic/?:auth:int", "/topic/123", map[string]string{":auth": "123"}}) - routers = append(routers, testinfo{"/topic/:id/?:auth", "/topic/1", map[string]string{":id": "1"}}) - routers = append(routers, testinfo{"/topic/:id/?:auth", "/topic/1/2", map[string]string{":id": "1", ":auth": "2"}}) - routers = append(routers, testinfo{"/topic/:id/?:auth:int", "/topic/1", map[string]string{":id": "1"}}) - routers = append(routers, testinfo{"/topic/:id/?:auth:int", "/topic/1/123", map[string]string{":id": "1", ":auth": "123"}}) - routers = append(routers, testinfo{"/:id", "/123", map[string]string{":id": "123"}}) - routers = append(routers, testinfo{"/hello/?:id", "/hello", map[string]string{":id": ""}}) - routers = append(routers, testinfo{"/", "/", nil}) - routers = append(routers, testinfo{"/customer/login", "/customer/login", nil}) - routers = append(routers, testinfo{"/customer/login", "/customer/login.json", map[string]string{":ext": "json"}}) - routers = append(routers, testinfo{"/*", "/http://customer/123/", map[string]string{":splat": "http://customer/123/"}}) - routers = append(routers, testinfo{"/*", "/customer/2009/12/11", map[string]string{":splat": "customer/2009/12/11"}}) - routers = append(routers, testinfo{"/aa/*/bb", "/aa/2009/bb", map[string]string{":splat": "2009"}}) - routers = append(routers, testinfo{"/cc/*/dd", "/cc/2009/11/dd", map[string]string{":splat": "2009/11"}}) - routers = append(routers, testinfo{"/cc/:id/*", "/cc/2009/11/dd", map[string]string{":id": "2009", ":splat": "11/dd"}}) - routers = append(routers, testinfo{"/ee/:year/*/ff", "/ee/2009/11/ff", map[string]string{":year": "2009", ":splat": "11"}}) - routers = append(routers, testinfo{"/thumbnail/:size/uploads/*", - "/thumbnail/100x100/uploads/items/2014/04/20/dPRCdChkUd651t1Hvs18.jpg", - map[string]string{":size": "100x100", ":splat": "items/2014/04/20/dPRCdChkUd651t1Hvs18.jpg"}}) - routers = append(routers, testinfo{"/*.*", "/nice/api.json", map[string]string{":path": "nice/api", ":ext": "json"}}) - routers = append(routers, testinfo{"/:name/*.*", "/nice/api.json", map[string]string{":name": "nice", ":path": "api", ":ext": "json"}}) - routers = append(routers, testinfo{"/:name/test/*.*", "/nice/test/api.json", map[string]string{":name": "nice", ":path": "api", ":ext": "json"}}) - routers = append(routers, testinfo{"/dl/:width:int/:height:int/*.*", - "/dl/48/48/05ac66d9bda00a3acf948c43e306fc9a.jpg", - map[string]string{":width": "48", ":height": "48", ":ext": "jpg", ":path": "05ac66d9bda00a3acf948c43e306fc9a"}}) - routers = append(routers, testinfo{"/v1/shop/:id:int", "/v1/shop/123", map[string]string{":id": "123"}}) - routers = append(routers, testinfo{"/v1/shop/:id\\((a|b|c)\\)", "/v1/shop/123(a)", map[string]string{":id": "123"}}) - routers = append(routers, testinfo{"/v1/shop/:id\\((a|b|c)\\)", "/v1/shop/123(b)", map[string]string{":id": "123"}}) - routers = append(routers, testinfo{"/v1/shop/:id\\((a|b|c)\\)", "/v1/shop/123(c)", map[string]string{":id": "123"}}) - routers = append(routers, testinfo{"/:year:int/:month:int/:id/:endid", "/1111/111/aaa/aaa", map[string]string{":year": "1111", ":month": "111", ":id": "aaa", ":endid": "aaa"}}) - routers = append(routers, testinfo{"/v1/shop/:id/:name", "/v1/shop/123/nike", map[string]string{":id": "123", ":name": "nike"}}) - routers = append(routers, testinfo{"/v1/shop/:id/account", "/v1/shop/123/account", map[string]string{":id": "123"}}) - routers = append(routers, testinfo{"/v1/shop/:name:string", "/v1/shop/nike", map[string]string{":name": "nike"}}) - routers = append(routers, testinfo{"/v1/shop/:id([0-9]+)", "/v1/shop//123", map[string]string{":id": "123"}}) - routers = append(routers, testinfo{"/v1/shop/:id([0-9]+)_:name", "/v1/shop/123_nike", map[string]string{":id": "123", ":name": "nike"}}) - routers = append(routers, testinfo{"/v1/shop/:id(.+)_cms.html", "/v1/shop/123_cms.html", map[string]string{":id": "123"}}) - routers = append(routers, testinfo{"/v1/shop/cms_:id(.+)_:page(.+).html", "/v1/shop/cms_123_1.html", map[string]string{":id": "123", ":page": "1"}}) - routers = append(routers, testinfo{"/v1/:v/cms/aaa_:id(.+)_:page(.+).html", "/v1/2/cms/aaa_123_1.html", map[string]string{":v": "2", ":id": "123", ":page": "1"}}) - routers = append(routers, testinfo{"/v1/:v/cms_:id(.+)_:page(.+).html", "/v1/2/cms_123_1.html", map[string]string{":v": "2", ":id": "123", ":page": "1"}}) - routers = append(routers, testinfo{"/v1/:v(.+)_cms/ttt_:id(.+)_:page(.+).html", "/v1/2_cms/ttt_123_1.html", map[string]string{":v": "2", ":id": "123", ":page": "1"}}) - routers = append(routers, testinfo{"/api/projects/:pid/members/?:mid", "/api/projects/1/members", map[string]string{":pid": "1"}}) - routers = append(routers, testinfo{"/api/projects/:pid/members/?:mid", "/api/projects/1/members/2", map[string]string{":pid": "1", ":mid": "2"}}) -} - -func TestTreeRouters(t *testing.T) { - for _, r := range routers { - tr := NewTree() - tr.AddRouter(r.url, "astaxie") - ctx := context.NewContext() - obj := tr.Match(r.requesturl, ctx) - if obj == nil || obj.(string) != "astaxie" { - t.Fatal(r.url+" can't get obj, Expect ", r.requesturl) - } - if r.params != nil { - for k, v := range r.params { - if vv := ctx.Input.Param(k); vv != v { - t.Fatal("The Rule: " + r.url + "\nThe RequestURL:" + r.requesturl + "\nThe Key is " + k + ", The Value should be: " + v + ", but get: " + vv) - } else if vv == "" && v != "" { - t.Fatal(r.url + " " + r.requesturl + " get param empty:" + k) - } - } - } - } -} - -func TestStaticPath(t *testing.T) { - tr := NewTree() - tr.AddRouter("/topic/:id", "wildcard") - tr.AddRouter("/topic", "static") - ctx := context.NewContext() - obj := tr.Match("/topic", ctx) - if obj == nil || obj.(string) != "static" { - t.Fatal("/topic is a static route") - } - obj = tr.Match("/topic/1", ctx) - if obj == nil || obj.(string) != "wildcard" { - t.Fatal("/topic/1 is a wildcard route") - } -} - -func TestAddTree(t *testing.T) { - tr := NewTree() - tr.AddRouter("/shop/:id/account", "astaxie") - tr.AddRouter("/shop/:sd/ttt_:id(.+)_:page(.+).html", "astaxie") - t1 := NewTree() - t1.AddTree("/v1/zl", tr) - ctx := context.NewContext() - obj := t1.Match("/v1/zl/shop/123/account", ctx) - if obj == nil || obj.(string) != "astaxie" { - t.Fatal("/v1/zl/shop/:id/account can't get obj ") - } - if ctx.Input.ParamsLen() == 0 { - t.Fatal("get param error") - } - if ctx.Input.Param(":id") != "123" { - t.Fatal("get :id param error") - } - ctx.Input.Reset((*beecontext.Context)(ctx)) - obj = t1.Match("/v1/zl/shop/123/ttt_1_12.html", ctx) - if obj == nil || obj.(string) != "astaxie" { - t.Fatal("/v1/zl//shop/:sd/ttt_:id(.+)_:page(.+).html can't get obj ") - } - if ctx.Input.ParamsLen() == 0 { - t.Fatal("get param error") - } - if ctx.Input.Param(":sd") != "123" || ctx.Input.Param(":id") != "1" || ctx.Input.Param(":page") != "12" { - t.Fatal("get :sd :id :page param error") - } - - t2 := NewTree() - t2.AddTree("/v1/:shopid", tr) - ctx.Input.Reset((*beecontext.Context)(ctx)) - obj = t2.Match("/v1/zl/shop/123/account", ctx) - if obj == nil || obj.(string) != "astaxie" { - t.Fatal("/v1/:shopid/shop/:id/account can't get obj ") - } - if ctx.Input.ParamsLen() == 0 { - t.Fatal("get param error") - } - if ctx.Input.Param(":id") != "123" || ctx.Input.Param(":shopid") != "zl" { - t.Fatal("get :id :shopid param error") - } - ctx.Input.Reset((*beecontext.Context)(ctx)) - obj = t2.Match("/v1/zl/shop/123/ttt_1_12.html", ctx) - if obj == nil || obj.(string) != "astaxie" { - t.Fatal("/v1/:shopid/shop/:sd/ttt_:id(.+)_:page(.+).html can't get obj ") - } - if ctx.Input.ParamsLen() == 0 { - t.Fatal("get :shopid param error") - } - if ctx.Input.Param(":sd") != "123" || ctx.Input.Param(":id") != "1" || ctx.Input.Param(":page") != "12" || ctx.Input.Param(":shopid") != "zl" { - t.Fatal("get :sd :id :page :shopid param error") - } -} - -func TestAddTree2(t *testing.T) { - tr := NewTree() - tr.AddRouter("/shop/:id/account", "astaxie") - tr.AddRouter("/shop/:sd/ttt_:id(.+)_:page(.+).html", "astaxie") - t3 := NewTree() - t3.AddTree("/:version(v1|v2)/:prefix", tr) - ctx := context.NewContext() - obj := t3.Match("/v1/zl/shop/123/account", ctx) - if obj == nil || obj.(string) != "astaxie" { - t.Fatal("/:version(v1|v2)/:prefix/shop/:id/account can't get obj ") - } - if ctx.Input.ParamsLen() == 0 { - t.Fatal("get param error") - } - if ctx.Input.Param(":id") != "123" || ctx.Input.Param(":prefix") != "zl" || ctx.Input.Param(":version") != "v1" { - t.Fatal("get :id :prefix :version param error") - } -} - -func TestAddTree3(t *testing.T) { - tr := NewTree() - tr.AddRouter("/create", "astaxie") - tr.AddRouter("/shop/:sd/account", "astaxie") - t3 := NewTree() - t3.AddTree("/table/:num", tr) - ctx := context.NewContext() - obj := t3.Match("/table/123/shop/123/account", ctx) - if obj == nil || obj.(string) != "astaxie" { - t.Fatal("/table/:num/shop/:sd/account can't get obj ") - } - if ctx.Input.ParamsLen() == 0 { - t.Fatal("get param error") - } - if ctx.Input.Param(":num") != "123" || ctx.Input.Param(":sd") != "123" { - t.Fatal("get :num :sd param error") - } - ctx.Input.Reset((*beecontext.Context)(ctx)) - obj = t3.Match("/table/123/create", ctx) - if obj == nil || obj.(string) != "astaxie" { - t.Fatal("/table/:num/create can't get obj ") - } -} - -func TestAddTree4(t *testing.T) { - tr := NewTree() - tr.AddRouter("/create", "astaxie") - tr.AddRouter("/shop/:sd/:account", "astaxie") - t4 := NewTree() - t4.AddTree("/:info:int/:num/:id", tr) - ctx := context.NewContext() - obj := t4.Match("/12/123/456/shop/123/account", ctx) - if obj == nil || obj.(string) != "astaxie" { - t.Fatal("/:info:int/:num/:id/shop/:sd/:account can't get obj ") - } - if ctx.Input.ParamsLen() == 0 { - t.Fatal("get param error") - } - if ctx.Input.Param(":info") != "12" || ctx.Input.Param(":num") != "123" || - ctx.Input.Param(":id") != "456" || ctx.Input.Param(":sd") != "123" || - ctx.Input.Param(":account") != "account" { - t.Fatal("get :info :num :id :sd :account param error") - } - ctx.Input.Reset((*beecontext.Context)(ctx)) - obj = t4.Match("/12/123/456/create", ctx) - if obj == nil || obj.(string) != "astaxie" { - t.Fatal("/:info:int/:num/:id/create can't get obj ") - } -} - -// Test for issue #1595 -func TestAddTree5(t *testing.T) { - tr := NewTree() - tr.AddRouter("/v1/shop/:id", "shopdetail") - tr.AddRouter("/v1/shop/", "shophome") - ctx := context.NewContext() - obj := tr.Match("/v1/shop/", ctx) - if obj == nil || obj.(string) != "shophome" { - t.Fatal("url /v1/shop/ need match router /v1/shop/ ") - } -} diff --git a/adapter/utils/captcha/captcha.go b/adapter/utils/captcha/captcha.go deleted file mode 100644 index 71aad0f2..00000000 --- a/adapter/utils/captcha/captcha.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 captcha implements generation and verification of image CAPTCHAs. -// an example for use captcha -// -// ``` -// package controllers -// -// import ( -// "github.com/astaxie/beego" -// "github.com/astaxie/beego/cache" -// "github.com/astaxie/beego/utils/captcha" -// ) -// -// var cpt *captcha.Captcha -// -// func init() { -// // use beego cache system store the captcha data -// store := cache.NewMemoryCache() -// cpt = captcha.NewWithFilter("/captcha/", store) -// } -// -// type MainController struct { -// beego.Controller -// } -// -// func (this *MainController) Get() { -// this.TplName = "index.tpl" -// } -// -// func (this *MainController) Post() { -// this.TplName = "index.tpl" -// -// this.Data["Success"] = cpt.VerifyReq(this.Ctx.Request) -// } -// ``` -// -// template usage -// -// ``` -// {{.Success}} -//
-// {{create_captcha}} -// -//
-// ``` -package captcha - -import ( - "html/template" - "net/http" - "time" - - "github.com/astaxie/beego/server/web/captcha" - beecontext "github.com/astaxie/beego/server/web/context" - - "github.com/astaxie/beego/adapter/cache" - "github.com/astaxie/beego/adapter/context" -) - -var ( - defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} -) - -const ( - // default captcha attributes - challengeNums = 6 - expiration = 600 * time.Second - fieldIDName = "captcha_id" - fieldCaptchaName = "captcha" - cachePrefix = "captcha_" - defaultURLPrefix = "/captcha/" -) - -// Captcha struct -type Captcha captcha.Captcha - -// Handler beego filter handler for serve captcha image -func (c *Captcha) Handler(ctx *context.Context) { - (*captcha.Captcha)(c).Handler((*beecontext.Context)(ctx)) -} - -// CreateCaptchaHTML template func for output html -func (c *Captcha) CreateCaptchaHTML() template.HTML { - return (*captcha.Captcha)(c).CreateCaptchaHTML() -} - -// CreateCaptcha create a new captcha id -func (c *Captcha) CreateCaptcha() (string, error) { - return (*captcha.Captcha)(c).CreateCaptcha() -} - -// VerifyReq verify from a request -func (c *Captcha) VerifyReq(req *http.Request) bool { - return (*captcha.Captcha)(c).VerifyReq(req) -} - -// Verify direct verify id and challenge string -func (c *Captcha) Verify(id string, challenge string) (success bool) { - return (*captcha.Captcha)(c).Verify(id, challenge) -} - -// NewCaptcha create a new captcha.Captcha -func NewCaptcha(urlPrefix string, store cache.Cache) *Captcha { - return (*Captcha)(captcha.NewCaptcha(urlPrefix, cache.CreateOldToNewAdapter(store))) -} - -// NewWithFilter create a new captcha.Captcha and auto AddFilter for serve captacha image -// and add a template func for output html -func NewWithFilter(urlPrefix string, store cache.Cache) *Captcha { - return (*Captcha)(captcha.NewWithFilter(urlPrefix, cache.CreateOldToNewAdapter(store))) -} diff --git a/adapter/utils/captcha/image.go b/adapter/utils/captcha/image.go deleted file mode 100644 index 6a1b696b..00000000 --- a/adapter/utils/captcha/image.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 captcha - -import ( - "io" - - "github.com/astaxie/beego/server/web/captcha" -) - -// Image struct -type Image captcha.Image - -// NewImage returns a new captcha image of the given width and height with the -// given digits, where each digit must be in range 0-9. -func NewImage(digits []byte, width, height int) *Image { - return (*Image)(captcha.NewImage(digits, width, height)) -} - -// WriteTo writes captcha image in PNG format into the given writer. -func (m *Image) WriteTo(w io.Writer) (int64, error) { - return (*captcha.Image)(m).WriteTo(w) -} diff --git a/adapter/utils/captcha/image_test.go b/adapter/utils/captcha/image_test.go deleted file mode 100644 index 5d298573..00000000 --- a/adapter/utils/captcha/image_test.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 captcha - -import ( - "testing" - - "github.com/astaxie/beego/adapter/utils" -) - -const ( - // Standard width and height of a captcha image. - stdWidth = 240 - stdHeight = 80 -) - -type byteCounter struct { - n int64 -} - -func (bc *byteCounter) Write(b []byte) (int, error) { - bc.n += int64(len(b)) - return len(b), nil -} - -func BenchmarkNewImage(b *testing.B) { - b.StopTimer() - d := utils.RandomCreateBytes(challengeNums, defaultChars...) - b.StartTimer() - for i := 0; i < b.N; i++ { - NewImage(d, stdWidth, stdHeight) - } -} - -func BenchmarkImageWriteTo(b *testing.B) { - b.StopTimer() - d := utils.RandomCreateBytes(challengeNums, defaultChars...) - b.StartTimer() - counter := &byteCounter{} - for i := 0; i < b.N; i++ { - img := NewImage(d, stdWidth, stdHeight) - img.WriteTo(counter) - b.SetBytes(counter.n) - counter.n = 0 - } -} diff --git a/adapter/utils/debug.go b/adapter/utils/debug.go deleted file mode 100644 index 3f4d2759..00000000 --- a/adapter/utils/debug.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 utils - -import ( - "github.com/astaxie/beego/core/utils" -) - -// Display print the data in console -func Display(data ...interface{}) { - utils.Display(data...) -} - -// GetDisplayString return data print string -func GetDisplayString(data ...interface{}) string { - return utils.GetDisplayString(data...) -} - -// Stack get stack bytes -func Stack(skip int, indent string) []byte { - return utils.Stack(skip, indent) -} diff --git a/adapter/utils/file.go b/adapter/utils/file.go deleted file mode 100644 index aa9ac316..00000000 --- a/adapter/utils/file.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 utils - -import ( - "github.com/astaxie/beego/core/utils" -) - -// SelfPath gets compiled executable file absolute path -func SelfPath() string { - return utils.SelfPath() -} - -// SelfDir gets compiled executable file directory -func SelfDir() string { - return utils.SelfDir() -} - -// FileExists reports whether the named file or directory exists. -func FileExists(name string) bool { - return utils.FileExists(name) -} - -// SearchFile Search a file in paths. -// this is often used in search config file in /etc ~/ -func SearchFile(filename string, paths ...string) (fullpath string, err error) { - return utils.SearchFile(filename, paths...) -} - -// GrepFile like command grep -E -// for example: GrepFile(`^hello`, "hello.txt") -// \n is striped while read -func GrepFile(patten string, filename string) (lines []string, err error) { - return utils.GrepFile(patten, filename) -} diff --git a/adapter/utils/mail.go b/adapter/utils/mail.go deleted file mode 100644 index 74a8f403..00000000 --- a/adapter/utils/mail.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 utils - -import ( - "io" - - "github.com/astaxie/beego/core/utils" -) - -// Email is the type used for email messages -type Email utils.Email - -// Attachment is a struct representing an email attachment. -// Based on the mime/multipart.FileHeader struct, Attachment contains the name, MIMEHeader, and content of the attachment in question -type Attachment utils.Attachment - -// NewEMail create new Email struct with config json. -// config json is followed from Email struct fields. -func NewEMail(config string) *Email { - return (*Email)(utils.NewEMail(config)) -} - -// Bytes Make all send information to byte -func (e *Email) Bytes() ([]byte, error) { - return (*utils.Email)(e).Bytes() -} - -// AttachFile Add attach file to the send mail -func (e *Email) AttachFile(args ...string) (*Attachment, error) { - a, err := (*utils.Email)(e).AttachFile(args...) - if err != nil { - return nil, err - } - return (*Attachment)(a), err -} - -// Attach is used to attach content from an io.Reader to the email. -// Parameters include an io.Reader, the desired filename for the attachment, and the Content-Type. -func (e *Email) Attach(r io.Reader, filename string, args ...string) (*Attachment, error) { - a, err := (*utils.Email)(e).Attach(r, filename, args...) - if err != nil { - return nil, err - } - return (*Attachment)(a), err -} - -// Send will send out the mail -func (e *Email) Send() error { - return (*utils.Email)(e).Send() -} diff --git a/adapter/utils/pagination/controller.go b/adapter/utils/pagination/controller.go deleted file mode 100644 index c82c54f9..00000000 --- a/adapter/utils/pagination/controller.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 pagination - -import ( - "github.com/astaxie/beego/adapter/context" - beecontext "github.com/astaxie/beego/server/web/context" - "github.com/astaxie/beego/server/web/pagination" -) - -// SetPaginator Instantiates a Paginator and assigns it to context.Input.Data("paginator"). -func SetPaginator(ctx *context.Context, per int, nums int64) (paginator *Paginator) { - return (*Paginator)(pagination.SetPaginator((*beecontext.Context)(ctx), per, nums)) -} diff --git a/adapter/utils/pagination/paginator.go b/adapter/utils/pagination/paginator.go deleted file mode 100644 index 73d9157f..00000000 --- a/adapter/utils/pagination/paginator.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 pagination - -import ( - "net/http" - - "github.com/astaxie/beego/core/utils/pagination" -) - -// Paginator within the state of a http request. -type Paginator pagination.Paginator - -// PageNums Returns the total number of pages. -func (p *Paginator) PageNums() int { - return (*pagination.Paginator)(p).PageNums() -} - -// Nums Returns the total number of items (e.g. from doing SQL count). -func (p *Paginator) Nums() int64 { - return (*pagination.Paginator)(p).Nums() -} - -// SetNums Sets the total number of items. -func (p *Paginator) SetNums(nums interface{}) { - (*pagination.Paginator)(p).SetNums(nums) -} - -// Page Returns the current page. -func (p *Paginator) Page() int { - return (*pagination.Paginator)(p).Page() -} - -// Pages Returns a list of all pages. -// -// Usage (in a view template): -// -// {{range $index, $page := .paginator.Pages}} -// -// {{$page}} -// -// {{end}} -func (p *Paginator) Pages() []int { - return (*pagination.Paginator)(p).Pages() -} - -// PageLink Returns URL for a given page index. -func (p *Paginator) PageLink(page int) string { - return (*pagination.Paginator)(p).PageLink(page) -} - -// PageLinkPrev Returns URL to the previous page. -func (p *Paginator) PageLinkPrev() (link string) { - return (*pagination.Paginator)(p).PageLinkPrev() -} - -// PageLinkNext Returns URL to the next page. -func (p *Paginator) PageLinkNext() (link string) { - return (*pagination.Paginator)(p).PageLinkNext() -} - -// PageLinkFirst Returns URL to the first page. -func (p *Paginator) PageLinkFirst() (link string) { - return (*pagination.Paginator)(p).PageLinkFirst() -} - -// PageLinkLast Returns URL to the last page. -func (p *Paginator) PageLinkLast() (link string) { - return (*pagination.Paginator)(p).PageLinkLast() -} - -// HasPrev Returns true if the current page has a predecessor. -func (p *Paginator) HasPrev() bool { - return (*pagination.Paginator)(p).HasPrev() -} - -// HasNext Returns true if the current page has a successor. -func (p *Paginator) HasNext() bool { - return (*pagination.Paginator)(p).HasNext() -} - -// IsActive Returns true if the given page index points to the current page. -func (p *Paginator) IsActive(page int) bool { - return (*pagination.Paginator)(p).IsActive(page) -} - -// Offset Returns the current offset. -func (p *Paginator) Offset() int { - return (*pagination.Paginator)(p).Offset() -} - -// HasPages Returns true if there is more than one page. -func (p *Paginator) HasPages() bool { - return (*pagination.Paginator)(p).HasPages() -} - -// NewPaginator Instantiates a paginator struct for the current http request. -func NewPaginator(req *http.Request, per int, nums interface{}) *Paginator { - return (*Paginator)(pagination.NewPaginator(req, per, nums)) -} diff --git a/adapter/utils/rand.go b/adapter/utils/rand.go deleted file mode 100644 index 0fcca580..00000000 --- a/adapter/utils/rand.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 utils - -import ( - "github.com/astaxie/beego/core/utils" -) - -// RandomCreateBytes generate random []byte by specify chars. -func RandomCreateBytes(n int, alphabets ...byte) []byte { - return utils.RandomCreateBytes(n, alphabets...) -} diff --git a/adapter/utils/safemap.go b/adapter/utils/safemap.go deleted file mode 100644 index bb50f3cd..00000000 --- a/adapter/utils/safemap.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 utils - -import ( - "github.com/astaxie/beego/core/utils" -) - -// BeeMap is a map with lock -type BeeMap utils.BeeMap - -// NewBeeMap return new safemap -func NewBeeMap() *BeeMap { - return (*BeeMap)(utils.NewBeeMap()) -} - -// Get from maps return the k's value -func (m *BeeMap) Get(k interface{}) interface{} { - return (*utils.BeeMap)(m).Get(k) -} - -// Set Maps the given key and value. Returns false -// if the key is already in the map and changes nothing. -func (m *BeeMap) Set(k interface{}, v interface{}) bool { - return (*utils.BeeMap)(m).Set(k, v) -} - -// Check Returns true if k is exist in the map. -func (m *BeeMap) Check(k interface{}) bool { - return (*utils.BeeMap)(m).Check(k) -} - -// Delete the given key and value. -func (m *BeeMap) Delete(k interface{}) { - (*utils.BeeMap)(m).Delete(k) -} - -// Items returns all items in safemap. -func (m *BeeMap) Items() map[interface{}]interface{} { - return (*utils.BeeMap)(m).Items() -} - -// Count returns the number of items within the map. -func (m *BeeMap) Count() int { - return (*utils.BeeMap)(m).Count() -} diff --git a/adapter/utils/slice.go b/adapter/utils/slice.go deleted file mode 100644 index 44b782b4..00000000 --- a/adapter/utils/slice.go +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 utils - -import ( - "github.com/astaxie/beego/core/utils" -) - -type reducetype func(interface{}) interface{} -type filtertype func(interface{}) bool - -// InSlice checks given string in string slice or not. -func InSlice(v string, sl []string) bool { - return utils.InSlice(v, sl) -} - -// InSliceIface checks given interface in interface slice. -func InSliceIface(v interface{}, sl []interface{}) bool { - return utils.InSliceIface(v, sl) -} - -// SliceRandList generate an int slice from min to max. -func SliceRandList(min, max int) []int { - return utils.SliceRandList(min, max) -} - -// SliceMerge merges interface slices to one slice. -func SliceMerge(slice1, slice2 []interface{}) (c []interface{}) { - return utils.SliceMerge(slice1, slice2) -} - -// SliceReduce generates a new slice after parsing every value by reduce function -func SliceReduce(slice []interface{}, a reducetype) (dslice []interface{}) { - return utils.SliceReduce(slice, func(i interface{}) interface{} { - return a(i) - }) -} - -// SliceRand returns random one from slice. -func SliceRand(a []interface{}) (b interface{}) { - return utils.SliceRand(a) -} - -// SliceSum sums all values in int64 slice. -func SliceSum(intslice []int64) (sum int64) { - return utils.SliceSum(intslice) -} - -// SliceFilter generates a new slice after filter function. -func SliceFilter(slice []interface{}, a filtertype) (ftslice []interface{}) { - return utils.SliceFilter(slice, func(i interface{}) bool { - return a(i) - }) -} - -// SliceDiff returns diff slice of slice1 - slice2. -func SliceDiff(slice1, slice2 []interface{}) (diffslice []interface{}) { - return utils.SliceDiff(slice1, slice2) -} - -// SliceIntersect returns slice that are present in all the slice1 and slice2. -func SliceIntersect(slice1, slice2 []interface{}) (diffslice []interface{}) { - return utils.SliceIntersect(slice1, slice2) -} - -// SliceChunk separates one slice to some sized slice. -func SliceChunk(slice []interface{}, size int) (chunkslice [][]interface{}) { - return utils.SliceChunk(slice, size) -} - -// SliceRange generates a new slice from begin to end with step duration of int64 number. -func SliceRange(start, end, step int64) (intslice []int64) { - return utils.SliceRange(start, end, step) -} - -// SlicePad prepends size number of val into slice. -func SlicePad(slice []interface{}, size int, val interface{}) []interface{} { - return utils.SlicePad(slice, size, val) -} - -// SliceUnique cleans repeated values in slice. -func SliceUnique(slice []interface{}) (uniqueslice []interface{}) { - return utils.SliceUnique(slice) -} - -// SliceShuffle shuffles a slice. -func SliceShuffle(slice []interface{}) []interface{} { - return utils.SliceShuffle(slice) -} diff --git a/adapter/utils/utils.go b/adapter/utils/utils.go deleted file mode 100644 index 8ba21bc4..00000000 --- a/adapter/utils/utils.go +++ /dev/null @@ -1,10 +0,0 @@ -package utils - -import ( - "github.com/astaxie/beego/core/utils" -) - -// GetGOPATHs returns all paths in GOPATH variable. -func GetGOPATHs() []string { - return utils.GetGOPATHs() -} diff --git a/adapter/validation/util.go b/adapter/validation/util.go deleted file mode 100644 index 431ce80d..00000000 --- a/adapter/validation/util.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 validation - -import ( - "reflect" - - "github.com/astaxie/beego/core/validation" -) - -const ( - // ValidTag struct tag - ValidTag = validation.ValidTag - - LabelTag = validation.LabelTag -) - -var ( - ErrInt64On32 = validation.ErrInt64On32 -) - -// CustomFunc is for custom validate function -type CustomFunc func(v *Validation, obj interface{}, key string) - -// AddCustomFunc Add a custom function to validation -// The name can not be: -// Clear -// HasErrors -// ErrorMap -// Error -// Check -// Valid -// NoMatch -// If the name is same with exists function, it will replace the origin valid function -func AddCustomFunc(name string, f CustomFunc) error { - return validation.AddCustomFunc(name, func(v *validation.Validation, obj interface{}, key string) { - f((*Validation)(v), obj, key) - }) -} - -// ValidFunc Valid function type -type ValidFunc validation.ValidFunc - -// Funcs Validate function map -type Funcs validation.Funcs - -// Call validate values with named type string -func (f Funcs) Call(name string, params ...interface{}) (result []reflect.Value, err error) { - return (validation.Funcs(f)).Call(name, params...) -} diff --git a/adapter/validation/validation.go b/adapter/validation/validation.go deleted file mode 100644 index e90c9f5b..00000000 --- a/adapter/validation/validation.go +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 validation for validations -// -// import ( -// "github.com/astaxie/beego/validation" -// "log" -// ) -// -// type User struct { -// Name string -// Age int -// } -// -// func main() { -// u := User{"man", 40} -// valid := validation.Validation{} -// valid.Required(u.Name, "name") -// valid.MaxSize(u.Name, 15, "nameMax") -// valid.Range(u.Age, 0, 140, "age") -// if valid.HasErrors() { -// // validation does not pass -// // print invalid message -// for _, err := range valid.Errors { -// log.Println(err.Key, err.Message) -// } -// } -// // or use like this -// if v := valid.Max(u.Age, 140, "ageMax"); !v.Ok { -// log.Println(v.Error.Key, v.Error.Message) -// } -// } -// -// more info: http://beego.me/docs/mvc/controller/validation.md -package validation - -import ( - "fmt" - "regexp" - - "github.com/astaxie/beego/core/validation" -) - -// ValidFormer valid interface -type ValidFormer interface { - Valid(*Validation) -} - -// Error show the error -type Error validation.Error - -// String Returns the Message. -func (e *Error) String() string { - if e == nil { - return "" - } - return e.Message -} - -// Implement Error interface. -// Return e.String() -func (e *Error) Error() string { return e.String() } - -// Result is returned from every validation method. -// It provides an indication of success, and a pointer to the Error (if any). -type Result validation.Result - -// Key Get Result by given key string. -func (r *Result) Key(key string) *Result { - if r.Error != nil { - r.Error.Key = key - } - return r -} - -// Message Set Result message by string or format string with args -func (r *Result) Message(message string, args ...interface{}) *Result { - if r.Error != nil { - if len(args) == 0 { - r.Error.Message = message - } else { - r.Error.Message = fmt.Sprintf(message, args...) - } - } - return r -} - -// A Validation context manages data validation and error messages. -type Validation validation.Validation - -// Clear Clean all ValidationError. -func (v *Validation) Clear() { - (*validation.Validation)(v).Clear() -} - -// HasErrors Has ValidationError nor not. -func (v *Validation) HasErrors() bool { - return (*validation.Validation)(v).HasErrors() -} - -// ErrorMap Return the errors mapped by key. -// If there are multiple validation errors associated with a single key, the -// first one "wins". (Typically the first validation will be the more basic). -func (v *Validation) ErrorMap() map[string][]*Error { - newErrors := (*validation.Validation)(v).ErrorMap() - res := make(map[string][]*Error, len(newErrors)) - for n, es := range newErrors { - errs := make([]*Error, 0, len(es)) - - for _, e := range es { - errs = append(errs, (*Error)(e)) - } - - res[n] = errs - } - return res -} - -// Error Add an error to the validation context. -func (v *Validation) Error(message string, args ...interface{}) *Result { - return (*Result)((*validation.Validation)(v).Error(message, args...)) -} - -// Required Test that the argument is non-nil and non-empty (if string or list) -func (v *Validation) Required(obj interface{}, key string) *Result { - return (*Result)((*validation.Validation)(v).Required(obj, key)) -} - -// Min Test that the obj is greater than min if obj's type is int -func (v *Validation) Min(obj interface{}, min int, key string) *Result { - return (*Result)((*validation.Validation)(v).Min(obj, min, key)) -} - -// Max Test that the obj is less than max if obj's type is int -func (v *Validation) Max(obj interface{}, max int, key string) *Result { - return (*Result)((*validation.Validation)(v).Max(obj, max, key)) -} - -// Range Test that the obj is between mni and max if obj's type is int -func (v *Validation) Range(obj interface{}, min, max int, key string) *Result { - return (*Result)((*validation.Validation)(v).Range(obj, min, max, key)) -} - -// MinSize Test that the obj is longer than min size if type is string or slice -func (v *Validation) MinSize(obj interface{}, min int, key string) *Result { - return (*Result)((*validation.Validation)(v).MinSize(obj, min, key)) -} - -// MaxSize Test that the obj is shorter than max size if type is string or slice -func (v *Validation) MaxSize(obj interface{}, max int, key string) *Result { - return (*Result)((*validation.Validation)(v).MaxSize(obj, max, key)) -} - -// Length Test that the obj is same length to n if type is string or slice -func (v *Validation) Length(obj interface{}, n int, key string) *Result { - return (*Result)((*validation.Validation)(v).Length(obj, n, key)) -} - -// Alpha Test that the obj is [a-zA-Z] if type is string -func (v *Validation) Alpha(obj interface{}, key string) *Result { - return (*Result)((*validation.Validation)(v).Alpha(obj, key)) -} - -// Numeric Test that the obj is [0-9] if type is string -func (v *Validation) Numeric(obj interface{}, key string) *Result { - return (*Result)((*validation.Validation)(v).Numeric(obj, key)) -} - -// AlphaNumeric Test that the obj is [0-9a-zA-Z] if type is string -func (v *Validation) AlphaNumeric(obj interface{}, key string) *Result { - return (*Result)((*validation.Validation)(v).AlphaNumeric(obj, key)) -} - -// Match Test that the obj matches regexp if type is string -func (v *Validation) Match(obj interface{}, regex *regexp.Regexp, key string) *Result { - return (*Result)((*validation.Validation)(v).Match(obj, regex, key)) -} - -// NoMatch Test that the obj doesn't match regexp if type is string -func (v *Validation) NoMatch(obj interface{}, regex *regexp.Regexp, key string) *Result { - return (*Result)((*validation.Validation)(v).NoMatch(obj, regex, key)) -} - -// AlphaDash Test that the obj is [0-9a-zA-Z_-] if type is string -func (v *Validation) AlphaDash(obj interface{}, key string) *Result { - return (*Result)((*validation.Validation)(v).AlphaDash(obj, key)) -} - -// Email Test that the obj is email address if type is string -func (v *Validation) Email(obj interface{}, key string) *Result { - return (*Result)((*validation.Validation)(v).Email(obj, key)) -} - -// IP Test that the obj is IP address if type is string -func (v *Validation) IP(obj interface{}, key string) *Result { - return (*Result)((*validation.Validation)(v).IP(obj, key)) -} - -// Base64 Test that the obj is base64 encoded if type is string -func (v *Validation) Base64(obj interface{}, key string) *Result { - return (*Result)((*validation.Validation)(v).Base64(obj, key)) -} - -// Mobile Test that the obj is chinese mobile number if type is string -func (v *Validation) Mobile(obj interface{}, key string) *Result { - return (*Result)((*validation.Validation)(v).Mobile(obj, key)) -} - -// Tel Test that the obj is chinese telephone number if type is string -func (v *Validation) Tel(obj interface{}, key string) *Result { - return (*Result)((*validation.Validation)(v).Tel(obj, key)) -} - -// Phone Test that the obj is chinese mobile or telephone number if type is string -func (v *Validation) Phone(obj interface{}, key string) *Result { - return (*Result)((*validation.Validation)(v).Phone(obj, key)) -} - -// ZipCode Test that the obj is chinese zip code if type is string -func (v *Validation) ZipCode(obj interface{}, key string) *Result { - return (*Result)((*validation.Validation)(v).ZipCode(obj, key)) -} - -// key must like aa.bb.cc or aa.bb. -// AddError adds independent error message for the provided key -func (v *Validation) AddError(key, message string) { - (*validation.Validation)(v).AddError(key, message) -} - -// SetError Set error message for one field in ValidationError -func (v *Validation) SetError(fieldName string, errMsg string) *Error { - return (*Error)((*validation.Validation)(v).SetError(fieldName, errMsg)) -} - -// Check Apply a group of validators to a field, in order, and return the -// ValidationResult from the first one that fails, or the last one that -// succeeds. -func (v *Validation) Check(obj interface{}, checks ...Validator) *Result { - vldts := make([]validation.Validator, 0, len(checks)) - for _, v := range checks { - vldts = append(vldts, validation.Validator(v)) - } - return (*Result)((*validation.Validation)(v).Check(obj, vldts...)) -} - -// Valid Validate a struct. -// the obj parameter must be a struct or a struct pointer -func (v *Validation) Valid(obj interface{}) (b bool, err error) { - return (*validation.Validation)(v).Valid(obj) -} - -// RecursiveValid Recursively validate a struct. -// Step1: Validate by v.Valid -// Step2: If pass on step1, then reflect obj's fields -// Step3: Do the Recursively validation to all struct or struct pointer fields -func (v *Validation) RecursiveValid(objc interface{}) (bool, error) { - return (*validation.Validation)(v).RecursiveValid(objc) -} - -func (v *Validation) CanSkipAlso(skipFunc string) { - (*validation.Validation)(v).CanSkipAlso(skipFunc) -} diff --git a/adapter/validation/validators.go b/adapter/validation/validators.go deleted file mode 100644 index 5cd5d286..00000000 --- a/adapter/validation/validators.go +++ /dev/null @@ -1,512 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 validation - -import ( - "sync" - - "github.com/astaxie/beego/core/validation" -) - -// CanSkipFuncs will skip valid if RequiredFirst is true and the struct field's value is empty -var CanSkipFuncs = validation.CanSkipFuncs - -// MessageTmpls store commond validate template -var MessageTmpls = map[string]string{ - "Required": "Can not be empty", - "Min": "Minimum is %d", - "Max": "Maximum is %d", - "Range": "Range is %d to %d", - "MinSize": "Minimum size is %d", - "MaxSize": "Maximum size is %d", - "Length": "Required length is %d", - "Alpha": "Must be valid alpha characters", - "Numeric": "Must be valid numeric characters", - "AlphaNumeric": "Must be valid alpha or numeric characters", - "Match": "Must match %s", - "NoMatch": "Must not match %s", - "AlphaDash": "Must be valid alpha or numeric or dash(-_) characters", - "Email": "Must be a valid email address", - "IP": "Must be a valid ip address", - "Base64": "Must be valid base64 characters", - "Mobile": "Must be valid mobile number", - "Tel": "Must be valid telephone number", - "Phone": "Must be valid telephone or mobile phone number", - "ZipCode": "Must be valid zipcode", -} - -var once sync.Once - -// SetDefaultMessage set default messages -// if not set, the default messages are -// "Required": "Can not be empty", -// "Min": "Minimum is %d", -// "Max": "Maximum is %d", -// "Range": "Range is %d to %d", -// "MinSize": "Minimum size is %d", -// "MaxSize": "Maximum size is %d", -// "Length": "Required length is %d", -// "Alpha": "Must be valid alpha characters", -// "Numeric": "Must be valid numeric characters", -// "AlphaNumeric": "Must be valid alpha or numeric characters", -// "Match": "Must match %s", -// "NoMatch": "Must not match %s", -// "AlphaDash": "Must be valid alpha or numeric or dash(-_) characters", -// "Email": "Must be a valid email address", -// "IP": "Must be a valid ip address", -// "Base64": "Must be valid base64 characters", -// "Mobile": "Must be valid mobile number", -// "Tel": "Must be valid telephone number", -// "Phone": "Must be valid telephone or mobile phone number", -// "ZipCode": "Must be valid zipcode", -func SetDefaultMessage(msg map[string]string) { - validation.SetDefaultMessage(msg) -} - -// Validator interface -type Validator interface { - IsSatisfied(interface{}) bool - DefaultMessage() string - GetKey() string - GetLimitValue() interface{} -} - -// Required struct -type Required validation.Required - -// IsSatisfied judge whether obj has value -func (r Required) IsSatisfied(obj interface{}) bool { - return validation.Required(r).IsSatisfied(obj) -} - -// DefaultMessage return the default error message -func (r Required) DefaultMessage() string { - return validation.Required(r).DefaultMessage() -} - -// GetKey return the r.Key -func (r Required) GetKey() string { - return validation.Required(r).GetKey() -} - -// GetLimitValue return nil now -func (r Required) GetLimitValue() interface{} { - return validation.Required(r).GetLimitValue() -} - -// Min check struct -type Min validation.Min - -// IsSatisfied judge whether obj is valid -// not support int64 on 32-bit platform -func (m Min) IsSatisfied(obj interface{}) bool { - return validation.Min(m).IsSatisfied(obj) -} - -// DefaultMessage return the default min error message -func (m Min) DefaultMessage() string { - return validation.Min(m).DefaultMessage() -} - -// GetKey return the m.Key -func (m Min) GetKey() string { - return validation.Min(m).GetKey() -} - -// GetLimitValue return the limit value, Min -func (m Min) GetLimitValue() interface{} { - return validation.Min(m).GetLimitValue() -} - -// Max validate struct -type Max validation.Max - -// IsSatisfied judge whether obj is valid -// not support int64 on 32-bit platform -func (m Max) IsSatisfied(obj interface{}) bool { - return validation.Max(m).IsSatisfied(obj) -} - -// DefaultMessage return the default max error message -func (m Max) DefaultMessage() string { - return validation.Max(m).DefaultMessage() -} - -// GetKey return the m.Key -func (m Max) GetKey() string { - return validation.Max(m).GetKey() -} - -// GetLimitValue return the limit value, Max -func (m Max) GetLimitValue() interface{} { - return validation.Max(m).GetLimitValue() -} - -// Range Requires an integer to be within Min, Max inclusive. -type Range validation.Range - -// IsSatisfied judge whether obj is valid -// not support int64 on 32-bit platform -func (r Range) IsSatisfied(obj interface{}) bool { - return validation.Range(r).IsSatisfied(obj) -} - -// DefaultMessage return the default Range error message -func (r Range) DefaultMessage() string { - return validation.Range(r).DefaultMessage() -} - -// GetKey return the m.Key -func (r Range) GetKey() string { - return validation.Range(r).GetKey() -} - -// GetLimitValue return the limit value, Max -func (r Range) GetLimitValue() interface{} { - return validation.Range(r).GetLimitValue() -} - -// MinSize Requires an array or string to be at least a given length. -type MinSize validation.MinSize - -// IsSatisfied judge whether obj is valid -func (m MinSize) IsSatisfied(obj interface{}) bool { - return validation.MinSize(m).IsSatisfied(obj) -} - -// DefaultMessage return the default MinSize error message -func (m MinSize) DefaultMessage() string { - return validation.MinSize(m).DefaultMessage() -} - -// GetKey return the m.Key -func (m MinSize) GetKey() string { - return validation.MinSize(m).GetKey() -} - -// GetLimitValue return the limit value -func (m MinSize) GetLimitValue() interface{} { - return validation.MinSize(m).GetLimitValue() -} - -// MaxSize Requires an array or string to be at most a given length. -type MaxSize validation.MaxSize - -// IsSatisfied judge whether obj is valid -func (m MaxSize) IsSatisfied(obj interface{}) bool { - return validation.MaxSize(m).IsSatisfied(obj) -} - -// DefaultMessage return the default MaxSize error message -func (m MaxSize) DefaultMessage() string { - return validation.MaxSize(m).DefaultMessage() -} - -// GetKey return the m.Key -func (m MaxSize) GetKey() string { - return validation.MaxSize(m).GetKey() -} - -// GetLimitValue return the limit value -func (m MaxSize) GetLimitValue() interface{} { - return validation.MaxSize(m).GetLimitValue() -} - -// Length Requires an array or string to be exactly a given length. -type Length validation.Length - -// IsSatisfied judge whether obj is valid -func (l Length) IsSatisfied(obj interface{}) bool { - return validation.Length(l).IsSatisfied(obj) -} - -// DefaultMessage return the default Length error message -func (l Length) DefaultMessage() string { - return validation.Length(l).DefaultMessage() -} - -// GetKey return the m.Key -func (l Length) GetKey() string { - return validation.Length(l).GetKey() -} - -// GetLimitValue return the limit value -func (l Length) GetLimitValue() interface{} { - return validation.Length(l).GetLimitValue() -} - -// Alpha check the alpha -type Alpha validation.Alpha - -// IsSatisfied judge whether obj is valid -func (a Alpha) IsSatisfied(obj interface{}) bool { - return validation.Alpha(a).IsSatisfied(obj) -} - -// DefaultMessage return the default Length error message -func (a Alpha) DefaultMessage() string { - return validation.Alpha(a).DefaultMessage() -} - -// GetKey return the m.Key -func (a Alpha) GetKey() string { - return validation.Alpha(a).GetKey() -} - -// GetLimitValue return the limit value -func (a Alpha) GetLimitValue() interface{} { - return validation.Alpha(a).GetLimitValue() -} - -// Numeric check number -type Numeric validation.Numeric - -// IsSatisfied judge whether obj is valid -func (n Numeric) IsSatisfied(obj interface{}) bool { - return validation.Numeric(n).IsSatisfied(obj) -} - -// DefaultMessage return the default Length error message -func (n Numeric) DefaultMessage() string { - return validation.Numeric(n).DefaultMessage() -} - -// GetKey return the n.Key -func (n Numeric) GetKey() string { - return validation.Numeric(n).GetKey() -} - -// GetLimitValue return the limit value -func (n Numeric) GetLimitValue() interface{} { - return validation.Numeric(n).GetLimitValue() -} - -// AlphaNumeric check alpha and number -type AlphaNumeric validation.AlphaNumeric - -// IsSatisfied judge whether obj is valid -func (a AlphaNumeric) IsSatisfied(obj interface{}) bool { - return validation.AlphaNumeric(a).IsSatisfied(obj) -} - -// DefaultMessage return the default Length error message -func (a AlphaNumeric) DefaultMessage() string { - return validation.AlphaNumeric(a).DefaultMessage() -} - -// GetKey return the a.Key -func (a AlphaNumeric) GetKey() string { - return validation.AlphaNumeric(a).GetKey() -} - -// GetLimitValue return the limit value -func (a AlphaNumeric) GetLimitValue() interface{} { - return validation.AlphaNumeric(a).GetLimitValue() -} - -// Match Requires a string to match a given regex. -type Match validation.Match - -// IsSatisfied judge whether obj is valid -func (m Match) IsSatisfied(obj interface{}) bool { - return validation.Match(m).IsSatisfied(obj) -} - -// DefaultMessage return the default Match error message -func (m Match) DefaultMessage() string { - return validation.Match(m).DefaultMessage() -} - -// GetKey return the m.Key -func (m Match) GetKey() string { - return validation.Match(m).GetKey() -} - -// GetLimitValue return the limit value -func (m Match) GetLimitValue() interface{} { - return validation.Match(m).GetLimitValue() -} - -// NoMatch Requires a string to not match a given regex. -type NoMatch validation.NoMatch - -// IsSatisfied judge whether obj is valid -func (n NoMatch) IsSatisfied(obj interface{}) bool { - return validation.NoMatch(n).IsSatisfied(obj) -} - -// DefaultMessage return the default NoMatch error message -func (n NoMatch) DefaultMessage() string { - return validation.NoMatch(n).DefaultMessage() -} - -// GetKey return the n.Key -func (n NoMatch) GetKey() string { - return validation.NoMatch(n).GetKey() -} - -// GetLimitValue return the limit value -func (n NoMatch) GetLimitValue() interface{} { - return validation.NoMatch(n).GetLimitValue() -} - -// AlphaDash check not Alpha -type AlphaDash validation.AlphaDash - -// DefaultMessage return the default AlphaDash error message -func (a AlphaDash) DefaultMessage() string { - return validation.AlphaDash(a).DefaultMessage() -} - -// GetKey return the n.Key -func (a AlphaDash) GetKey() string { - return validation.AlphaDash(a).GetKey() -} - -// GetLimitValue return the limit value -func (a AlphaDash) GetLimitValue() interface{} { - return validation.AlphaDash(a).GetLimitValue() -} - -// Email check struct -type Email validation.Email - -// DefaultMessage return the default Email error message -func (e Email) DefaultMessage() string { - return validation.Email(e).DefaultMessage() -} - -// GetKey return the n.Key -func (e Email) GetKey() string { - return validation.Email(e).GetKey() -} - -// GetLimitValue return the limit value -func (e Email) GetLimitValue() interface{} { - return validation.Email(e).GetLimitValue() -} - -// IP check struct -type IP validation.IP - -// DefaultMessage return the default IP error message -func (i IP) DefaultMessage() string { - return validation.IP(i).DefaultMessage() -} - -// GetKey return the i.Key -func (i IP) GetKey() string { - return validation.IP(i).GetKey() -} - -// GetLimitValue return the limit value -func (i IP) GetLimitValue() interface{} { - return validation.IP(i).GetLimitValue() -} - -// Base64 check struct -type Base64 validation.Base64 - -// DefaultMessage return the default Base64 error message -func (b Base64) DefaultMessage() string { - return validation.Base64(b).DefaultMessage() -} - -// GetKey return the b.Key -func (b Base64) GetKey() string { - return validation.Base64(b).GetKey() -} - -// GetLimitValue return the limit value -func (b Base64) GetLimitValue() interface{} { - return validation.Base64(b).GetLimitValue() -} - -// Mobile check struct -type Mobile validation.Mobile - -// DefaultMessage return the default Mobile error message -func (m Mobile) DefaultMessage() string { - return validation.Mobile(m).DefaultMessage() -} - -// GetKey return the m.Key -func (m Mobile) GetKey() string { - return validation.Mobile(m).GetKey() -} - -// GetLimitValue return the limit value -func (m Mobile) GetLimitValue() interface{} { - return validation.Mobile(m).GetLimitValue() -} - -// Tel check telephone struct -type Tel validation.Tel - -// DefaultMessage return the default Tel error message -func (t Tel) DefaultMessage() string { - return validation.Tel(t).DefaultMessage() -} - -// GetKey return the t.Key -func (t Tel) GetKey() string { - return validation.Tel(t).GetKey() -} - -// GetLimitValue return the limit value -func (t Tel) GetLimitValue() interface{} { - return validation.Tel(t).GetLimitValue() -} - -// Phone just for chinese telephone or mobile phone number -type Phone validation.Phone - -// IsSatisfied judge whether obj is valid -func (p Phone) IsSatisfied(obj interface{}) bool { - return validation.Phone(p).IsSatisfied(obj) -} - -// DefaultMessage return the default Phone error message -func (p Phone) DefaultMessage() string { - return validation.Phone(p).DefaultMessage() -} - -// GetKey return the p.Key -func (p Phone) GetKey() string { - return validation.Phone(p).GetKey() -} - -// GetLimitValue return the limit value -func (p Phone) GetLimitValue() interface{} { - return validation.Phone(p).GetLimitValue() -} - -// ZipCode check the zip struct -type ZipCode validation.ZipCode - -// DefaultMessage return the default Zip error message -func (z ZipCode) DefaultMessage() string { - return validation.ZipCode(z).DefaultMessage() -} - -// GetKey return the z.Key -func (z ZipCode) GetKey() string { - return validation.ZipCode(z).GetKey() -} - -// GetLimitValue return the limit value -func (z ZipCode) GetLimitValue() interface{} { - return validation.ZipCode(z).GetLimitValue() -} diff --git a/admin.go b/admin.go new file mode 100644 index 00000000..3e538a0e --- /dev/null +++ b/admin.go @@ -0,0 +1,420 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 beego + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "os" + "reflect" + "text/template" + "time" + + "github.com/prometheus/client_golang/prometheus/promhttp" + + "github.com/astaxie/beego/grace" + "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/toolbox" + "github.com/astaxie/beego/utils" +) + +// BeeAdminApp is the default adminApp used by admin module. +var beeAdminApp *adminApp + +// FilterMonitorFunc is default monitor filter when admin module is enable. +// if this func returns, admin module records qps for this request by condition of this function logic. +// usage: +// func MyFilterMonitor(method, requestPath string, t time.Duration, pattern string, statusCode int) bool { +// if method == "POST" { +// return false +// } +// if t.Nanoseconds() < 100 { +// return false +// } +// if strings.HasPrefix(requestPath, "/astaxie") { +// return false +// } +// return true +// } +// beego.FilterMonitorFunc = MyFilterMonitor. +var FilterMonitorFunc func(string, string, time.Duration, string, int) bool + +func init() { + beeAdminApp = &adminApp{ + routers: make(map[string]http.HandlerFunc), + } + // keep in mind that all data should be html escaped to avoid XSS attack + beeAdminApp.Route("/", adminIndex) + beeAdminApp.Route("/qps", qpsIndex) + beeAdminApp.Route("/prof", profIndex) + beeAdminApp.Route("/healthcheck", healthcheck) + beeAdminApp.Route("/task", taskStatus) + beeAdminApp.Route("/listconf", listConf) + beeAdminApp.Route("/metrics", promhttp.Handler().ServeHTTP) + FilterMonitorFunc = func(string, string, time.Duration, string, int) bool { return true } +} + +// AdminIndex is the default http.Handler for admin module. +// it matches url pattern "/". +func adminIndex(rw http.ResponseWriter, _ *http.Request) { + execTpl(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl) +} + +// QpsIndex is the http.Handler for writing qps statistics map result info in http.ResponseWriter. +// it's registered with url pattern "/qps" in admin module. +func qpsIndex(rw http.ResponseWriter, _ *http.Request) { + data := make(map[interface{}]interface{}) + data["Content"] = toolbox.StatisticsMap.GetMap() + + // do html escape before display path, avoid xss + if content, ok := (data["Content"]).(M); ok { + if resultLists, ok := (content["Data"]).([][]string); ok { + for i := range resultLists { + if len(resultLists[i]) > 0 { + resultLists[i][0] = template.HTMLEscapeString(resultLists[i][0]) + } + } + } + } + + execTpl(rw, data, qpsTpl, defaultScriptsTpl) +} + +// ListConf is the http.Handler of displaying all beego configuration values as key/value pair. +// it's registered with url pattern "/listconf" in admin module. +func listConf(rw http.ResponseWriter, r *http.Request) { + r.ParseForm() + command := r.Form.Get("command") + if command == "" { + rw.Write([]byte("command not support")) + return + } + + data := make(map[interface{}]interface{}) + switch command { + case "conf": + m := make(M) + list("BConfig", BConfig, m) + m["AppConfigPath"] = template.HTMLEscapeString(appConfigPath) + m["AppConfigProvider"] = template.HTMLEscapeString(appConfigProvider) + tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) + tmpl = template.Must(tmpl.Parse(configTpl)) + tmpl = template.Must(tmpl.Parse(defaultScriptsTpl)) + + data["Content"] = m + + tmpl.Execute(rw, data) + + case "router": + content := PrintTree() + content["Fields"] = []string{ + "Router Pattern", + "Methods", + "Controller", + } + data["Content"] = content + data["Title"] = "Routers" + execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl) + case "filter": + var ( + content = M{ + "Fields": []string{ + "Router Pattern", + "Filter Function", + }, + } + filterTypes = []string{} + filterTypeData = make(M) + ) + + if BeeApp.Handlers.enableFilter { + var filterType string + for k, fr := range map[int]string{ + BeforeStatic: "Before Static", + BeforeRouter: "Before Router", + BeforeExec: "Before Exec", + AfterExec: "After Exec", + FinishRouter: "Finish Router"} { + if bf := BeeApp.Handlers.filters[k]; len(bf) > 0 { + filterType = fr + filterTypes = append(filterTypes, filterType) + resultList := new([][]string) + for _, f := range bf { + var result = []string{ + // void xss + template.HTMLEscapeString(f.pattern), + template.HTMLEscapeString(utils.GetFuncName(f.filterFunc)), + } + *resultList = append(*resultList, result) + } + filterTypeData[filterType] = resultList + } + } + } + + content["Data"] = filterTypeData + content["Methods"] = filterTypes + + data["Content"] = content + data["Title"] = "Filters" + execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl) + default: + rw.Write([]byte("command not support")) + } +} + +func list(root string, p interface{}, m M) { + pt := reflect.TypeOf(p) + pv := reflect.ValueOf(p) + if pt.Kind() == reflect.Ptr { + pt = pt.Elem() + pv = pv.Elem() + } + for i := 0; i < pv.NumField(); i++ { + var key string + if root == "" { + key = pt.Field(i).Name + } else { + key = root + "." + pt.Field(i).Name + } + if pv.Field(i).Kind() == reflect.Struct { + list(key, pv.Field(i).Interface(), m) + } else { + m[key] = pv.Field(i).Interface() + } + } +} + +// PrintTree prints all registered routers. +func PrintTree() M { + var ( + content = M{} + methods = []string{} + methodsData = make(M) + ) + for method, t := range BeeApp.Handlers.routers { + + resultList := new([][]string) + + printTree(resultList, t) + + methods = append(methods, template.HTMLEscapeString(method)) + methodsData[template.HTMLEscapeString(method)] = resultList + } + + content["Data"] = methodsData + content["Methods"] = methods + return content +} + +func printTree(resultList *[][]string, t *Tree) { + for _, tr := range t.fixrouters { + printTree(resultList, tr) + } + if t.wildcard != nil { + printTree(resultList, t.wildcard) + } + for _, l := range t.leaves { + if v, ok := l.runObject.(*ControllerInfo); ok { + if v.routerType == routerTypeBeego { + var result = []string{ + template.HTMLEscapeString(v.pattern), + template.HTMLEscapeString(fmt.Sprintf("%s", v.methods)), + template.HTMLEscapeString(v.controllerType.String()), + } + *resultList = append(*resultList, result) + } else if v.routerType == routerTypeRESTFul { + var result = []string{ + template.HTMLEscapeString(v.pattern), + template.HTMLEscapeString(fmt.Sprintf("%s", v.methods)), + "", + } + *resultList = append(*resultList, result) + } else if v.routerType == routerTypeHandler { + var result = []string{ + template.HTMLEscapeString(v.pattern), + "", + "", + } + *resultList = append(*resultList, result) + } + } + } +} + +// ProfIndex is a http.Handler for showing profile command. +// it's in url pattern "/prof" in admin module. +func profIndex(rw http.ResponseWriter, r *http.Request) { + r.ParseForm() + command := r.Form.Get("command") + if command == "" { + return + } + + var ( + format = r.Form.Get("format") + data = make(map[interface{}]interface{}) + result bytes.Buffer + ) + toolbox.ProcessInput(command, &result) + data["Content"] = template.HTMLEscapeString(result.String()) + + if format == "json" && command == "gc summary" { + dataJSON, err := json.Marshal(data) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + rw.Header().Set("Content-Type", "application/json") + rw.Write(dataJSON) + return + } + + data["Title"] = template.HTMLEscapeString(command) + defaultTpl := defaultScriptsTpl + if command == "gc summary" { + defaultTpl = gcAjaxTpl + } + execTpl(rw, data, profillingTpl, defaultTpl) +} + +// Healthcheck is a http.Handler calling health checking and showing the result. +// it's in "/healthcheck" pattern in admin module. +func healthcheck(rw http.ResponseWriter, _ *http.Request) { + var ( + result []string + data = make(map[interface{}]interface{}) + resultList = new([][]string) + content = M{ + "Fields": []string{"Name", "Message", "Status"}, + } + ) + + for name, h := range toolbox.AdminCheckList { + if err := h.Check(); err != nil { + result = []string{ + "error", + template.HTMLEscapeString(name), + template.HTMLEscapeString(err.Error()), + } + } else { + result = []string{ + "success", + template.HTMLEscapeString(name), + "OK", + } + } + *resultList = append(*resultList, result) + } + + content["Data"] = resultList + data["Content"] = content + data["Title"] = "Health Check" + execTpl(rw, data, healthCheckTpl, defaultScriptsTpl) +} + +// TaskStatus is a http.Handler with running task status (task name, status and the last execution). +// it's in "/task" pattern in admin module. +func taskStatus(rw http.ResponseWriter, req *http.Request) { + data := make(map[interface{}]interface{}) + + // Run Task + req.ParseForm() + taskname := req.Form.Get("taskname") + if taskname != "" { + if t, ok := toolbox.AdminTaskList[taskname]; ok { + if err := t.Run(); err != nil { + data["Message"] = []string{"error", template.HTMLEscapeString(fmt.Sprintf("%s", err))} + } + data["Message"] = []string{"success", template.HTMLEscapeString(fmt.Sprintf("%s run success,Now the Status is
%s", taskname, t.GetStatus()))} + } else { + data["Message"] = []string{"warning", template.HTMLEscapeString(fmt.Sprintf("there's no task which named: %s", taskname))} + } + } + + // List Tasks + content := make(M) + resultList := new([][]string) + var fields = []string{ + "Task Name", + "Task Spec", + "Task Status", + "Last Time", + "", + } + for tname, tk := range toolbox.AdminTaskList { + result := []string{ + template.HTMLEscapeString(tname), + template.HTMLEscapeString(tk.GetSpec()), + template.HTMLEscapeString(tk.GetStatus()), + template.HTMLEscapeString(tk.GetPrev().String()), + } + *resultList = append(*resultList, result) + } + + content["Fields"] = fields + content["Data"] = resultList + data["Content"] = content + data["Title"] = "Tasks" + execTpl(rw, data, tasksTpl, defaultScriptsTpl) +} + +func execTpl(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) { + tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) + for _, tpl := range tpls { + tmpl = template.Must(tmpl.Parse(tpl)) + } + tmpl.Execute(rw, data) +} + +// adminApp is an http.HandlerFunc map used as beeAdminApp. +type adminApp struct { + routers map[string]http.HandlerFunc +} + +// Route adds http.HandlerFunc to adminApp with url pattern. +func (admin *adminApp) Route(pattern string, f http.HandlerFunc) { + admin.routers[pattern] = f +} + +// Run adminApp http server. +// Its addr is defined in configuration file as adminhttpaddr and adminhttpport. +func (admin *adminApp) Run() { + if len(toolbox.AdminTaskList) > 0 { + toolbox.StartTask() + } + addr := BConfig.Listen.AdminAddr + + if BConfig.Listen.AdminPort != 0 { + addr = fmt.Sprintf("%s:%d", BConfig.Listen.AdminAddr, BConfig.Listen.AdminPort) + } + for p, f := range admin.routers { + http.Handle(p, f) + } + logs.Info("Admin server Running on %s", addr) + + var err error + if BConfig.Listen.Graceful { + err = grace.ListenAndServe(addr, nil) + } else { + err = http.ListenAndServe(addr, nil) + } + if err != nil { + logs.Critical("Admin ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid())) + } +} diff --git a/admin_test.go b/admin_test.go new file mode 100644 index 00000000..71cc209e --- /dev/null +++ b/admin_test.go @@ -0,0 +1,77 @@ +package beego + +import ( + "fmt" + "testing" +) + +func TestList_01(t *testing.T) { + m := make(M) + list("BConfig", BConfig, m) + t.Log(m) + om := oldMap() + for k, v := range om { + if fmt.Sprint(m[k]) != fmt.Sprint(v) { + t.Log(k, "old-key", v, "new-key", m[k]) + t.FailNow() + } + } +} + +func oldMap() M { + m := make(M) + m["BConfig.AppName"] = BConfig.AppName + m["BConfig.RunMode"] = BConfig.RunMode + m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive + m["BConfig.ServerName"] = BConfig.ServerName + m["BConfig.RecoverPanic"] = BConfig.RecoverPanic + m["BConfig.CopyRequestBody"] = BConfig.CopyRequestBody + m["BConfig.EnableGzip"] = BConfig.EnableGzip + m["BConfig.MaxMemory"] = BConfig.MaxMemory + m["BConfig.EnableErrorsShow"] = BConfig.EnableErrorsShow + m["BConfig.Listen.Graceful"] = BConfig.Listen.Graceful + m["BConfig.Listen.ServerTimeOut"] = BConfig.Listen.ServerTimeOut + m["BConfig.Listen.ListenTCP4"] = BConfig.Listen.ListenTCP4 + m["BConfig.Listen.EnableHTTP"] = BConfig.Listen.EnableHTTP + m["BConfig.Listen.HTTPAddr"] = BConfig.Listen.HTTPAddr + m["BConfig.Listen.HTTPPort"] = BConfig.Listen.HTTPPort + m["BConfig.Listen.EnableHTTPS"] = BConfig.Listen.EnableHTTPS + m["BConfig.Listen.HTTPSAddr"] = BConfig.Listen.HTTPSAddr + m["BConfig.Listen.HTTPSPort"] = BConfig.Listen.HTTPSPort + m["BConfig.Listen.HTTPSCertFile"] = BConfig.Listen.HTTPSCertFile + m["BConfig.Listen.HTTPSKeyFile"] = BConfig.Listen.HTTPSKeyFile + m["BConfig.Listen.EnableAdmin"] = BConfig.Listen.EnableAdmin + m["BConfig.Listen.AdminAddr"] = BConfig.Listen.AdminAddr + m["BConfig.Listen.AdminPort"] = BConfig.Listen.AdminPort + m["BConfig.Listen.EnableFcgi"] = BConfig.Listen.EnableFcgi + m["BConfig.Listen.EnableStdIo"] = BConfig.Listen.EnableStdIo + m["BConfig.WebConfig.AutoRender"] = BConfig.WebConfig.AutoRender + m["BConfig.WebConfig.EnableDocs"] = BConfig.WebConfig.EnableDocs + m["BConfig.WebConfig.FlashName"] = BConfig.WebConfig.FlashName + m["BConfig.WebConfig.FlashSeparator"] = BConfig.WebConfig.FlashSeparator + m["BConfig.WebConfig.DirectoryIndex"] = BConfig.WebConfig.DirectoryIndex + m["BConfig.WebConfig.StaticDir"] = BConfig.WebConfig.StaticDir + m["BConfig.WebConfig.StaticExtensionsToGzip"] = BConfig.WebConfig.StaticExtensionsToGzip + m["BConfig.WebConfig.StaticCacheFileSize"] = BConfig.WebConfig.StaticCacheFileSize + m["BConfig.WebConfig.StaticCacheFileNum"] = BConfig.WebConfig.StaticCacheFileNum + m["BConfig.WebConfig.TemplateLeft"] = BConfig.WebConfig.TemplateLeft + m["BConfig.WebConfig.TemplateRight"] = BConfig.WebConfig.TemplateRight + m["BConfig.WebConfig.ViewsPath"] = BConfig.WebConfig.ViewsPath + m["BConfig.WebConfig.EnableXSRF"] = BConfig.WebConfig.EnableXSRF + m["BConfig.WebConfig.XSRFExpire"] = BConfig.WebConfig.XSRFExpire + m["BConfig.WebConfig.Session.SessionOn"] = BConfig.WebConfig.Session.SessionOn + m["BConfig.WebConfig.Session.SessionProvider"] = BConfig.WebConfig.Session.SessionProvider + m["BConfig.WebConfig.Session.SessionName"] = BConfig.WebConfig.Session.SessionName + m["BConfig.WebConfig.Session.SessionGCMaxLifetime"] = BConfig.WebConfig.Session.SessionGCMaxLifetime + m["BConfig.WebConfig.Session.SessionProviderConfig"] = BConfig.WebConfig.Session.SessionProviderConfig + m["BConfig.WebConfig.Session.SessionCookieLifeTime"] = BConfig.WebConfig.Session.SessionCookieLifeTime + m["BConfig.WebConfig.Session.SessionAutoSetCookie"] = BConfig.WebConfig.Session.SessionAutoSetCookie + m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain + m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly + m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs + m["BConfig.Log.EnableStaticLogs"] = BConfig.Log.EnableStaticLogs + m["BConfig.Log.AccessLogsFormat"] = BConfig.Log.AccessLogsFormat + m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum + m["BConfig.Log.Outputs"] = BConfig.Log.Outputs + return m +} diff --git a/server/web/adminui.go b/adminui.go similarity index 99% rename from server/web/adminui.go rename to adminui.go index de8c9455..cdcdef33 100644 --- a/server/web/adminui.go +++ b/adminui.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego var indexTpl = ` {{define "content"}} @@ -21,7 +21,7 @@ var indexTpl = ` For detail usage please check our document:

-Toolbox +Toolbox

Live Monitor diff --git a/app.go b/app.go new file mode 100644 index 00000000..f3fe6f7b --- /dev/null +++ b/app.go @@ -0,0 +1,496 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 beego + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/http/fcgi" + "os" + "path" + "strings" + "time" + + "github.com/astaxie/beego/grace" + "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/utils" + "golang.org/x/crypto/acme/autocert" +) + +var ( + // BeeApp is an application instance + BeeApp *App +) + +func init() { + // create beego application + BeeApp = NewApp() +} + +// App defines beego application with a new PatternServeMux. +type App struct { + Handlers *ControllerRegister + Server *http.Server +} + +// NewApp returns a new beego application. +func NewApp() *App { + cr := NewControllerRegister() + app := &App{Handlers: cr, Server: &http.Server{}} + return app +} + +// MiddleWare function for http.Handler +type MiddleWare func(http.Handler) http.Handler + +// Run beego application. +func (app *App) Run(mws ...MiddleWare) { + addr := BConfig.Listen.HTTPAddr + + if BConfig.Listen.HTTPPort != 0 { + addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPAddr, BConfig.Listen.HTTPPort) + } + + var ( + err error + l net.Listener + endRunning = make(chan bool, 1) + ) + + // run cgi server + if BConfig.Listen.EnableFcgi { + if BConfig.Listen.EnableStdIo { + if err = fcgi.Serve(nil, app.Handlers); err == nil { // standard I/O + logs.Info("Use FCGI via standard I/O") + } else { + logs.Critical("Cannot use FCGI via standard I/O", err) + } + return + } + if BConfig.Listen.HTTPPort == 0 { + // remove the Socket file before start + if utils.FileExists(addr) { + os.Remove(addr) + } + l, err = net.Listen("unix", addr) + } else { + l, err = net.Listen("tcp", addr) + } + if err != nil { + logs.Critical("Listen: ", err) + } + if err = fcgi.Serve(l, app.Handlers); err != nil { + logs.Critical("fcgi.Serve: ", err) + } + return + } + + app.Server.Handler = app.Handlers + for i := len(mws) - 1; i >= 0; i-- { + if mws[i] == nil { + continue + } + app.Server.Handler = mws[i](app.Server.Handler) + } + app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second + app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second + app.Server.ErrorLog = logs.GetLogger("HTTP") + + // run graceful mode + if BConfig.Listen.Graceful { + httpsAddr := BConfig.Listen.HTTPSAddr + app.Server.Addr = httpsAddr + if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS { + go func() { + time.Sleep(1000 * time.Microsecond) + if BConfig.Listen.HTTPSPort != 0 { + httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort) + app.Server.Addr = httpsAddr + } + server := grace.NewServer(httpsAddr, app.Server.Handler) + server.Server.ReadTimeout = app.Server.ReadTimeout + server.Server.WriteTimeout = app.Server.WriteTimeout + if BConfig.Listen.EnableMutualHTTPS { + if err := server.ListenAndServeMutualTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile, BConfig.Listen.TrustCaFile); err != nil { + logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid())) + time.Sleep(100 * time.Microsecond) + } + } else { + if BConfig.Listen.AutoTLS { + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...), + Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir), + } + app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate} + BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", "" + } + if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil { + logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid())) + time.Sleep(100 * time.Microsecond) + } + } + endRunning <- true + }() + } + if BConfig.Listen.EnableHTTP { + go func() { + server := grace.NewServer(addr, app.Server.Handler) + server.Server.ReadTimeout = app.Server.ReadTimeout + server.Server.WriteTimeout = app.Server.WriteTimeout + if BConfig.Listen.ListenTCP4 { + server.Network = "tcp4" + } + if err := server.ListenAndServe(); err != nil { + logs.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid())) + time.Sleep(100 * time.Microsecond) + } + endRunning <- true + }() + } + <-endRunning + return + } + + // run normal mode + if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS { + go func() { + time.Sleep(1000 * time.Microsecond) + if BConfig.Listen.HTTPSPort != 0 { + app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort) + } else if BConfig.Listen.EnableHTTP { + logs.Info("Start https server error, conflict with http. Please reset https port") + return + } + logs.Info("https server Running on https://%s", app.Server.Addr) + if BConfig.Listen.AutoTLS { + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...), + Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir), + } + app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate} + BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", "" + } else if BConfig.Listen.EnableMutualHTTPS { + pool := x509.NewCertPool() + data, err := ioutil.ReadFile(BConfig.Listen.TrustCaFile) + if err != nil { + logs.Info("MutualHTTPS should provide TrustCaFile") + return + } + pool.AppendCertsFromPEM(data) + app.Server.TLSConfig = &tls.Config{ + ClientCAs: pool, + ClientAuth: tls.RequireAndVerifyClientCert, + } + } + if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil { + logs.Critical("ListenAndServeTLS: ", err) + time.Sleep(100 * time.Microsecond) + endRunning <- true + } + }() + + } + if BConfig.Listen.EnableHTTP { + go func() { + app.Server.Addr = addr + logs.Info("http server Running on http://%s", app.Server.Addr) + if BConfig.Listen.ListenTCP4 { + ln, err := net.Listen("tcp4", app.Server.Addr) + if err != nil { + logs.Critical("ListenAndServe: ", err) + time.Sleep(100 * time.Microsecond) + endRunning <- true + return + } + if err = app.Server.Serve(ln); err != nil { + logs.Critical("ListenAndServe: ", err) + time.Sleep(100 * time.Microsecond) + endRunning <- true + return + } + } else { + if err := app.Server.ListenAndServe(); err != nil { + logs.Critical("ListenAndServe: ", err) + time.Sleep(100 * time.Microsecond) + endRunning <- true + } + } + }() + } + <-endRunning +} + +// Router adds a patterned controller handler to BeeApp. +// it's an alias method of App.Router. +// usage: +// simple router +// beego.Router("/admin", &admin.UserController{}) +// beego.Router("/admin/index", &admin.ArticleController{}) +// +// regex router +// +// beego.Router("/api/:id([0-9]+)", &controllers.RController{}) +// +// custom rules +// beego.Router("/api/list",&RestController{},"*:ListFood") +// beego.Router("/api/create",&RestController{},"post:CreateFood") +// beego.Router("/api/update",&RestController{},"put:UpdateFood") +// beego.Router("/api/delete",&RestController{},"delete:DeleteFood") +func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App { + BeeApp.Handlers.Add(rootpath, c, mappingMethods...) + return BeeApp +} + +// UnregisterFixedRoute unregisters the route with the specified fixedRoute. It is particularly useful +// in web applications that inherit most routes from a base webapp via the underscore +// import, and aim to overwrite only certain paths. +// The method parameter can be empty or "*" for all HTTP methods, or a particular +// method type (e.g. "GET" or "POST") for selective removal. +// +// Usage (replace "GET" with "*" for all methods): +// beego.UnregisterFixedRoute("/yourpreviouspath", "GET") +// beego.Router("/yourpreviouspath", yourControllerAddress, "get:GetNewPage") +func UnregisterFixedRoute(fixedRoute string, method string) *App { + subPaths := splitPath(fixedRoute) + if method == "" || method == "*" { + for m := range HTTPMETHOD { + if _, ok := BeeApp.Handlers.routers[m]; !ok { + continue + } + if BeeApp.Handlers.routers[m].prefix == strings.Trim(fixedRoute, "/ ") { + findAndRemoveSingleTree(BeeApp.Handlers.routers[m]) + continue + } + findAndRemoveTree(subPaths, BeeApp.Handlers.routers[m], m) + } + return BeeApp + } + // Single HTTP method + um := strings.ToUpper(method) + if _, ok := BeeApp.Handlers.routers[um]; ok { + if BeeApp.Handlers.routers[um].prefix == strings.Trim(fixedRoute, "/ ") { + findAndRemoveSingleTree(BeeApp.Handlers.routers[um]) + return BeeApp + } + findAndRemoveTree(subPaths, BeeApp.Handlers.routers[um], um) + } + return BeeApp +} + +func findAndRemoveTree(paths []string, entryPointTree *Tree, method string) { + for i := range entryPointTree.fixrouters { + if entryPointTree.fixrouters[i].prefix == paths[0] { + if len(paths) == 1 { + if len(entryPointTree.fixrouters[i].fixrouters) > 0 { + // If the route had children subtrees, remove just the functional leaf, + // to allow children to function as before + if len(entryPointTree.fixrouters[i].leaves) > 0 { + entryPointTree.fixrouters[i].leaves[0] = nil + entryPointTree.fixrouters[i].leaves = entryPointTree.fixrouters[i].leaves[1:] + } + } else { + // Remove the *Tree from the fixrouters slice + entryPointTree.fixrouters[i] = nil + + if i == len(entryPointTree.fixrouters)-1 { + entryPointTree.fixrouters = entryPointTree.fixrouters[:i] + } else { + entryPointTree.fixrouters = append(entryPointTree.fixrouters[:i], entryPointTree.fixrouters[i+1:len(entryPointTree.fixrouters)]...) + } + } + return + } + findAndRemoveTree(paths[1:], entryPointTree.fixrouters[i], method) + } + } +} + +func findAndRemoveSingleTree(entryPointTree *Tree) { + if entryPointTree == nil { + return + } + if len(entryPointTree.fixrouters) > 0 { + // If the route had children subtrees, remove just the functional leaf, + // to allow children to function as before + if len(entryPointTree.leaves) > 0 { + entryPointTree.leaves[0] = nil + entryPointTree.leaves = entryPointTree.leaves[1:] + } + } +} + +// Include will generate router file in the router/xxx.go from the controller's comments +// usage: +// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) +// type BankAccount struct{ +// beego.Controller +// } +// +// register the function +// func (b *BankAccount)Mapping(){ +// b.Mapping("ShowAccount" , b.ShowAccount) +// b.Mapping("ModifyAccount", b.ModifyAccount) +//} +// +// //@router /account/:id [get] +// func (b *BankAccount) ShowAccount(){ +// //logic +// } +// +// +// //@router /account/:id [post] +// func (b *BankAccount) ModifyAccount(){ +// //logic +// } +// +// the comments @router url methodlist +// url support all the function Router's pattern +// methodlist [get post head put delete options *] +func Include(cList ...ControllerInterface) *App { + BeeApp.Handlers.Include(cList...) + return BeeApp +} + +// RESTRouter adds a restful controller handler to BeeApp. +// its' controller implements beego.ControllerInterface and +// defines a param "pattern/:objectId" to visit each resource. +func RESTRouter(rootpath string, c ControllerInterface) *App { + Router(rootpath, c) + Router(path.Join(rootpath, ":objectId"), c) + return BeeApp +} + +// AutoRouter adds defined controller handler to BeeApp. +// it's same to App.AutoRouter. +// if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page, +// visit the url /main/list to exec List function or /main/page to exec Page function. +func AutoRouter(c ControllerInterface) *App { + BeeApp.Handlers.AddAuto(c) + return BeeApp +} + +// AutoPrefix adds controller handler to BeeApp with prefix. +// it's same to App.AutoRouterWithPrefix. +// if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page, +// visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function. +func AutoPrefix(prefix string, c ControllerInterface) *App { + BeeApp.Handlers.AddAutoPrefix(prefix, c) + return BeeApp +} + +// Get used to register router for Get method +// usage: +// beego.Get("/", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Get(rootpath string, f FilterFunc) *App { + BeeApp.Handlers.Get(rootpath, f) + return BeeApp +} + +// Post used to register router for Post method +// usage: +// beego.Post("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Post(rootpath string, f FilterFunc) *App { + BeeApp.Handlers.Post(rootpath, f) + return BeeApp +} + +// Delete used to register router for Delete method +// usage: +// beego.Delete("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Delete(rootpath string, f FilterFunc) *App { + BeeApp.Handlers.Delete(rootpath, f) + return BeeApp +} + +// Put used to register router for Put method +// usage: +// beego.Put("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Put(rootpath string, f FilterFunc) *App { + BeeApp.Handlers.Put(rootpath, f) + return BeeApp +} + +// Head used to register router for Head method +// usage: +// beego.Head("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Head(rootpath string, f FilterFunc) *App { + BeeApp.Handlers.Head(rootpath, f) + return BeeApp +} + +// Options used to register router for Options method +// usage: +// beego.Options("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Options(rootpath string, f FilterFunc) *App { + BeeApp.Handlers.Options(rootpath, f) + return BeeApp +} + +// Patch used to register router for Patch method +// usage: +// beego.Patch("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Patch(rootpath string, f FilterFunc) *App { + BeeApp.Handlers.Patch(rootpath, f) + return BeeApp +} + +// Any used to register router for all methods +// usage: +// beego.Any("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Any(rootpath string, f FilterFunc) *App { + BeeApp.Handlers.Any(rootpath, f) + return BeeApp +} + +// Handler used to register a Handler router +// usage: +// beego.Handler("/api", http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { +// fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) +// })) +func Handler(rootpath string, h http.Handler, options ...interface{}) *App { + BeeApp.Handlers.Handler(rootpath, h, options...) + return BeeApp +} + +// InsertFilter adds a FilterFunc with pattern condition and action constant. +// The pos means action constant including +// beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter. +// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute) +func InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) *App { + BeeApp.Handlers.InsertFilter(pattern, pos, filter, params...) + return BeeApp +} diff --git a/server/web/beego.go b/beego.go similarity index 65% rename from server/web/beego.go rename to beego.go index 14e51a94..8ebe0bab 100644 --- a/server/web/beego.go +++ b/beego.go @@ -12,15 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "os" "path/filepath" - "sync" + "strconv" + "strings" ) const ( + // VERSION represent beego web framework version. + VERSION = "1.12.2" + // DEV is for develop DEV = "dev" // PROD is for production @@ -34,7 +38,7 @@ type M map[string]interface{} type hookfunc func() error var ( - hooks = make([]hookfunc, 0) // hook function slice to store the hookfunc + hooks = make([]hookfunc, 0) //hook function slice to store the hookfunc ) // AddAPPStartHook is used to register the hookfunc @@ -51,39 +55,55 @@ func AddAPPStartHook(hf ...hookfunc) { // beego.Run("127.0.0.1:8089") func Run(params ...string) { + initBeforeHTTPRun() + if len(params) > 0 && params[0] != "" { - BeeApp.Run(params[0]) + strs := strings.Split(params[0], ":") + if len(strs) > 0 && strs[0] != "" { + BConfig.Listen.HTTPAddr = strs[0] + } + if len(strs) > 1 && strs[1] != "" { + BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1]) + } + + BConfig.Listen.Domains = params } - BeeApp.Run("") + + BeeApp.Run() } // RunWithMiddleWares Run beego application with middlewares. func RunWithMiddleWares(addr string, mws ...MiddleWare) { - BeeApp.Run(addr, mws...) + initBeforeHTTPRun() + + strs := strings.Split(addr, ":") + if len(strs) > 0 && strs[0] != "" { + BConfig.Listen.HTTPAddr = strs[0] + BConfig.Listen.Domains = []string{strs[0]} + } + if len(strs) > 1 && strs[1] != "" { + BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1]) + } + + BeeApp.Run(mws...) } -var initHttpOnce sync.Once - -// TODO move to module init function func initBeforeHTTPRun() { - initHttpOnce.Do(func() { - // init hooks - AddAPPStartHook( - registerMime, - registerDefaultErrorHandler, - registerSession, - registerTemplate, - registerAdmin, - registerGzip, - registerCommentRouter, - ) + //init hooks + AddAPPStartHook( + registerMime, + registerDefaultErrorHandler, + registerSession, + registerTemplate, + registerAdmin, + registerGzip, + ) - for _, hk := range hooks { - if err := hk(); err != nil { - panic(err) - } + for _, hk := range hooks { + if err := hk(); err != nil { + panic(err) } - }) + } } // TestBeegoInit is for test package init diff --git a/build_info.go b/build_info.go index 42f42c28..6dc2835e 100644 --- a/build_info.go +++ b/build_info.go @@ -15,18 +15,13 @@ package beego var ( - BuildVersion string + BuildVersion string BuildGitRevision string - BuildStatus string - BuildTag string - BuildTime string + BuildStatus string + BuildTag string + BuildTime string GoVersion string GitBranch string ) - -const ( - // VERSION represent beego web framework version. - VERSION = "2.0.0-alpha" -) diff --git a/client/cache/README.md b/cache/README.md similarity index 100% rename from client/cache/README.md rename to cache/README.md diff --git a/adapter/cache/cache.go b/cache/cache.go similarity index 100% rename from adapter/cache/cache.go rename to cache/cache.go diff --git a/adapter/cache/cache_test.go b/cache/cache_test.go similarity index 100% rename from adapter/cache/cache_test.go rename to cache/cache_test.go diff --git a/client/cache/conv.go b/cache/conv.go similarity index 90% rename from client/cache/conv.go rename to cache/conv.go index 158f7f41..87800586 100644 --- a/client/cache/conv.go +++ b/cache/conv.go @@ -19,7 +19,7 @@ import ( "strconv" ) -// GetString converts interface to string. +// GetString convert interface to string. func GetString(v interface{}) string { switch result := v.(type) { case string: @@ -34,7 +34,7 @@ func GetString(v interface{}) string { return "" } -// GetInt converts interface to int. +// GetInt convert interface to int. func GetInt(v interface{}) int { switch result := v.(type) { case int: @@ -52,7 +52,7 @@ func GetInt(v interface{}) int { return 0 } -// GetInt64 converts interface to int64. +// GetInt64 convert interface to int64. func GetInt64(v interface{}) int64 { switch result := v.(type) { case int: @@ -71,7 +71,7 @@ func GetInt64(v interface{}) int64 { return 0 } -// GetFloat64 converts interface to float64. +// GetFloat64 convert interface to float64. func GetFloat64(v interface{}) float64 { switch result := v.(type) { case float64: @@ -85,7 +85,7 @@ func GetFloat64(v interface{}) float64 { return 0 } -// GetBool converts interface to bool. +// GetBool convert interface to bool. func GetBool(v interface{}) bool { switch result := v.(type) { case bool: diff --git a/adapter/cache/conv_test.go b/cache/conv_test.go similarity index 100% rename from adapter/cache/conv_test.go rename to cache/conv_test.go diff --git a/client/cache/file.go b/cache/file.go similarity index 68% rename from client/cache/file.go rename to cache/file.go index dc818258..6f12d3ee 100644 --- a/client/cache/file.go +++ b/cache/file.go @@ -16,7 +16,6 @@ package cache import ( "bytes" - "context" "crypto/md5" "encoding/gob" "encoding/hex" @@ -29,12 +28,10 @@ import ( "reflect" "strconv" "time" - - "github.com/pkg/errors" ) -// FileCacheItem is basic unit of file cache adapter which -// contains data and expire time. +// FileCacheItem is basic unit of file cache adapter. +// it contains data and expire time. type FileCacheItem struct { Data interface{} Lastaccess time.Time @@ -57,15 +54,15 @@ type FileCache struct { EmbedExpiry int } -// NewFileCache creates a new file cache with no config. -// The level and expiry need to be set in the method StartAndGC as config string. +// NewFileCache Create new file cache with no config. +// the level and expiry need set in method StartAndGC as config string. func NewFileCache() Cache { // return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix} return &FileCache{} } -// StartAndGC starts gc for file cache. -// config must be in the format {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"} +// StartAndGC will start and begin gc for file cache. +// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"} func (fc *FileCache) StartAndGC(config string) error { cfg := make(map[string]string) @@ -94,14 +91,14 @@ func (fc *FileCache) StartAndGC(config string) error { return nil } -// Init makes new a dir for file cache if it does not already exist +// Init will make new dir for file cache if not exist. func (fc *FileCache) Init() { if ok, _ := exists(fc.CachePath); !ok { // todo : error handle _ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle } } -// getCachedFilename returns an md5 encoded file name. +// get cached file name. it's md5 encoded. func (fc *FileCache) getCacheFileName(key string) string { m := md5.New() io.WriteString(m, key) @@ -122,45 +119,34 @@ func (fc *FileCache) getCacheFileName(key string) string { } // Get value from file cache. -// if nonexistent or expired return an empty string. -func (fc *FileCache) Get(ctx context.Context, key string) (interface{}, error) { +// if non-exist or expired, return empty string. +func (fc *FileCache) Get(key string) interface{} { fileData, err := FileGetContents(fc.getCacheFileName(key)) if err != nil { - return nil, err + return "" } - var to FileCacheItem - err = GobDecode(fileData, &to) - if err != nil { - return nil, err - } - + GobDecode(fileData, &to) if to.Expired.Before(time.Now()) { - return nil, errors.New("The key is expired") + return "" } - return to.Data, nil + return to.Data } // GetMulti gets values from file cache. -// if nonexistent or expired return an empty string. -func (fc *FileCache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { +// if non-exist or expired, return empty string. +func (fc *FileCache) GetMulti(keys []string) []interface{} { var rc []interface{} for _, key := range keys { - val, err := fc.Get(context.Background(), key) - if err != nil { - rc = append(rc, err) - } else { - rc = append(rc, val) - } - + rc = append(rc, fc.Get(key)) } - return rc, nil + return rc } // Put value into file cache. -// timeout: how long this file should be kept in ms +// timeout means how long to keep this file, unit of ms. // if timeout equals fc.EmbedExpiry(default is 0), cache this item forever. -func (fc *FileCache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { +func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error { gob.Register(val) item := FileCacheItem{Data: val} @@ -178,7 +164,7 @@ func (fc *FileCache) Put(ctx context.Context, key string, val interface{}, timeo } // Delete file cache value. -func (fc *FileCache) Delete(ctx context.Context, key string) error { +func (fc *FileCache) Delete(key string) error { filename := fc.getCacheFileName(key) if ok, _ := exists(filename); ok { return os.Remove(filename) @@ -186,45 +172,46 @@ func (fc *FileCache) Delete(ctx context.Context, key string) error { return nil } -// Incr increases cached int value. -// fc value is saved forever unless deleted. -func (fc *FileCache) Incr(ctx context.Context, key string) error { - data, _ := fc.Get(context.Background(), key) +// Incr will increase cached int value. +// fc value is saving forever unless Delete. +func (fc *FileCache) Incr(key string) error { + data := fc.Get(key) var incr int if reflect.TypeOf(data).Name() != "int" { incr = 0 } else { incr = data.(int) + 1 } - fc.Put(context.Background(), key, incr, time.Duration(fc.EmbedExpiry)) + fc.Put(key, incr, time.Duration(fc.EmbedExpiry)) return nil } -// Decr decreases cached int value. -func (fc *FileCache) Decr(ctx context.Context, key string) error { - data, _ := fc.Get(context.Background(), key) +// Decr will decrease cached int value. +func (fc *FileCache) Decr(key string) error { + data := fc.Get(key) var decr int if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 { decr = 0 } else { decr = data.(int) - 1 } - fc.Put(context.Background(), key, decr, time.Duration(fc.EmbedExpiry)) + fc.Put(key, decr, time.Duration(fc.EmbedExpiry)) return nil } -// IsExist checks if value exists. -func (fc *FileCache) IsExist(ctx context.Context, key string) (bool, error) { +// IsExist check value is exist. +func (fc *FileCache) IsExist(key string) bool { ret, _ := exists(fc.getCacheFileName(key)) - return ret, nil + return ret } -// ClearAll cleans cached files (not implemented) -func (fc *FileCache) ClearAll(context.Context) error { +// ClearAll will clean cached files. +// not implemented. +func (fc *FileCache) ClearAll() error { return nil } -// Check if a file exists +// check file exist. func exists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { @@ -236,19 +223,19 @@ func exists(path string) (bool, error) { return false, err } -// FileGetContents Reads bytes from a file. -// if non-existent, create this file. +// FileGetContents Get bytes to file. +// if non-exist, create this file. func FileGetContents(filename string) (data []byte, e error) { return ioutil.ReadFile(filename) } -// FilePutContents puts bytes into a file. -// if non-existent, create this file. +// FilePutContents Put bytes to file. +// if non-exist, create this file. func FilePutContents(filename string, content []byte) error { return ioutil.WriteFile(filename, content, os.ModePerm) } -// GobEncode Gob encodes a file cache item. +// GobEncode Gob encodes file cache item. func GobEncode(data interface{}) ([]byte, error) { buf := bytes.NewBuffer(nil) enc := gob.NewEncoder(buf) @@ -259,7 +246,7 @@ func GobEncode(data interface{}) ([]byte, error) { return buf.Bytes(), err } -// GobDecode Gob decodes a file cache item. +// GobDecode Gob decodes file cache item. func GobDecode(data []byte, to *FileCacheItem) error { buf := bytes.NewBuffer(data) dec := gob.NewDecoder(buf) diff --git a/client/cache/memcache/memcache.go b/cache/memcache/memcache.go similarity index 71% rename from client/cache/memcache/memcache.go rename to cache/memcache/memcache.go index f3774571..19116bfa 100644 --- a/client/cache/memcache/memcache.go +++ b/cache/memcache/memcache.go @@ -30,15 +30,13 @@ package memcache import ( - "context" "encoding/json" "errors" "strings" "time" + "github.com/astaxie/beego/cache" "github.com/bradfitz/gomemcache/memcache" - - "github.com/astaxie/beego/client/cache" ) // Cache Memcache adapter. @@ -47,31 +45,34 @@ type Cache struct { conninfo []string } -// NewMemCache creates a new memcache adapter. +// NewMemCache create new memcache adapter. func NewMemCache() cache.Cache { return &Cache{} } // Get get value from memcache. -func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) { +func (rc *Cache) Get(key string) interface{} { if rc.conn == nil { if err := rc.connectInit(); err != nil { - return nil, err + return err } } if item, err := rc.conn.Get(key); err == nil { - return item.Value, nil - } else { - return nil, err + return item.Value } + return nil } -// GetMulti gets a value from a key in memcache. -func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { +// GetMulti get value from memcache. +func (rc *Cache) GetMulti(keys []string) []interface{} { + size := len(keys) var rv []interface{} if rc.conn == nil { if err := rc.connectInit(); err != nil { - return rv, err + for i := 0; i < size; i++ { + rv = append(rv, err) + } + return rv } } mv, err := rc.conn.GetMulti(keys) @@ -79,12 +80,16 @@ func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, er for _, v := range mv { rv = append(rv, v.Value) } + return rv } - return rv, err + for i := 0; i < size; i++ { + rv = append(rv, err) + } + return rv } -// Put puts a value into memcache. -func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { +// Put put value to memcache. +func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -101,8 +106,8 @@ func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout t return rc.conn.Set(&item) } -// Delete deletes a value in memcache. -func (rc *Cache) Delete(ctx context.Context, key string) error { +// Delete delete value in memcache. +func (rc *Cache) Delete(key string) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -111,8 +116,8 @@ func (rc *Cache) Delete(ctx context.Context, key string) error { return rc.conn.Delete(key) } -// Incr increases counter. -func (rc *Cache) Incr(ctx context.Context, key string) error { +// Incr increase counter. +func (rc *Cache) Incr(key string) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -122,8 +127,8 @@ func (rc *Cache) Incr(ctx context.Context, key string) error { return err } -// Decr decreases counter. -func (rc *Cache) Decr(ctx context.Context, key string) error { +// Decr decrease counter. +func (rc *Cache) Decr(key string) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -133,19 +138,19 @@ func (rc *Cache) Decr(ctx context.Context, key string) error { return err } -// IsExist checks if a value exists in memcache. -func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) { +// IsExist check value exists in memcache. +func (rc *Cache) IsExist(key string) bool { if rc.conn == nil { if err := rc.connectInit(); err != nil { - return false, err + return false } } _, err := rc.conn.Get(key) - return err == nil, err + return err == nil } -// ClearAll clears all cache in memcache. -func (rc *Cache) ClearAll(context.Context) error { +// ClearAll clear all cached in memcache. +func (rc *Cache) ClearAll() error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -154,9 +159,9 @@ func (rc *Cache) ClearAll(context.Context) error { return rc.conn.FlushAll() } -// StartAndGC starts the memcache adapter. -// config: must be in the format {"conn":"connection info"}. -// If an error occurs during connecting, an error is returned +// StartAndGC start memcache adapter. +// config string is like {"conn":"connection info"}. +// if connecting error, return. func (rc *Cache) StartAndGC(config string) error { var cf map[string]string json.Unmarshal([]byte(config), &cf) diff --git a/adapter/cache/memcache/memcache_test.go b/cache/memcache/memcache_test.go similarity index 90% rename from adapter/cache/memcache/memcache_test.go rename to cache/memcache/memcache_test.go index b9b6dc6b..d9129b69 100644 --- a/adapter/cache/memcache/memcache_test.go +++ b/cache/memcache/memcache_test.go @@ -15,23 +15,17 @@ package memcache import ( - "fmt" - "os" + _ "github.com/bradfitz/gomemcache/memcache" + "strconv" "testing" "time" - "github.com/astaxie/beego/adapter/cache" + "github.com/astaxie/beego/cache" ) func TestMemcacheCache(t *testing.T) { - - addr := os.Getenv("MEMCACHE_ADDR") - if addr == "" { - addr = "127.0.0.1:11211" - } - - bm, err := cache.NewCache("memcache", fmt.Sprintf(`{"conn": "%s"}`, addr)) + bm, err := cache.NewCache("memcache", `{"conn": "127.0.0.1:11211"}`) if err != nil { t.Error("init err") } @@ -76,7 +70,7 @@ func TestMemcacheCache(t *testing.T) { t.Error("delete err") } - // test string + //test string if err = bm.Put("astaxie", "author", timeoutDuration); err != nil { t.Error("set Error", err) } @@ -88,7 +82,7 @@ func TestMemcacheCache(t *testing.T) { t.Error("get err") } - // test GetMulti + //test GetMulti if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil { t.Error("set Error", err) } diff --git a/client/cache/memory.go b/cache/memory.go similarity index 65% rename from client/cache/memory.go rename to cache/memory.go index 6f87ec08..d8314e3c 100644 --- a/client/cache/memory.go +++ b/cache/memory.go @@ -15,7 +15,6 @@ package cache import ( - "context" "encoding/json" "errors" "sync" @@ -23,11 +22,11 @@ import ( ) var ( - // Timer for how often to recycle the expired cache items in memory (in seconds) + // DefaultEvery means the clock time of recycling the expired cache items in memory. DefaultEvery = 60 // 1 minute ) -// MemoryItem stores memory cache item. +// MemoryItem store memory cache item. type MemoryItem struct { val interface{} createdTime time.Time @@ -42,8 +41,8 @@ func (mi *MemoryItem) isExpire() bool { return time.Now().Sub(mi.createdTime) > mi.lifespan } -// MemoryCache is a memory cache adapter. -// Contains a RW locker for safe map storage. +// MemoryCache is Memory cache adapter. +// it contains a RW locker for safe map storage. type MemoryCache struct { sync.RWMutex dur time.Duration @@ -57,65 +56,60 @@ func NewMemoryCache() Cache { return &cache } -// Get returns cache from memory. -// If non-existent or expired, return nil. -func (bc *MemoryCache) Get(ctx context.Context, key string) (interface{}, error) { +// Get cache from memory. +// if non-existed or expired, return nil. +func (bc *MemoryCache) Get(name string) interface{} { bc.RLock() defer bc.RUnlock() - if itm, ok := bc.items[key]; ok { + if itm, ok := bc.items[name]; ok { if itm.isExpire() { - return nil, errors.New("the key is expired") + return nil } - return itm.val, nil + return itm.val } - return nil, nil + return nil } // GetMulti gets caches from memory. -// If non-existent or expired, return nil. -func (bc *MemoryCache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { +// if non-existed or expired, return nil. +func (bc *MemoryCache) GetMulti(names []string) []interface{} { var rc []interface{} - for _, name := range keys { - val, err := bc.Get(context.Background(), name) - if err != nil { - rc = append(rc, err) - } else { - rc = append(rc, val) - } + for _, name := range names { + rc = append(rc, bc.Get(name)) } - return rc, nil + return rc } -// Put puts cache into memory. -// If lifespan is 0, it will never overwrite this value unless restarted -func (bc *MemoryCache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { +// Put cache to memory. +// if lifespan is 0, it will be forever till restart. +func (bc *MemoryCache) Put(name string, value interface{}, lifespan time.Duration) error { bc.Lock() defer bc.Unlock() - bc.items[key] = &MemoryItem{ - val: val, + bc.items[name] = &MemoryItem{ + val: value, createdTime: time.Now(), - lifespan: timeout, + lifespan: lifespan, } return nil } // Delete cache in memory. -func (bc *MemoryCache) Delete(ctx context.Context, key string) error { +func (bc *MemoryCache) Delete(name string) error { bc.Lock() defer bc.Unlock() - if _, ok := bc.items[key]; !ok { + if _, ok := bc.items[name]; !ok { return errors.New("key not exist") } - delete(bc.items, key) - if _, ok := bc.items[key]; ok { + delete(bc.items, name) + if _, ok := bc.items[name]; ok { return errors.New("delete key error") } return nil } -// Incr increases cache counter in memory. -// Supports int,int32,int64,uint,uint32,uint64. -func (bc *MemoryCache) Incr(ctx context.Context, key string) error { +// Incr increase cache counter in memory. +// it supports int,int32,int64,uint,uint32,uint64. +func (bc *MemoryCache) Incr(key string) error { bc.Lock() defer bc.Unlock() itm, ok := bc.items[key] @@ -141,8 +135,8 @@ func (bc *MemoryCache) Incr(ctx context.Context, key string) error { return nil } -// Decr decreases counter in memory. -func (bc *MemoryCache) Decr(ctx context.Context, key string) error { +// Decr decrease counter in memory. +func (bc *MemoryCache) Decr(key string) error { bc.Lock() defer bc.Unlock() itm, ok := bc.items[key] @@ -180,25 +174,25 @@ func (bc *MemoryCache) Decr(ctx context.Context, key string) error { return nil } -// IsExist checks if cache exists in memory. -func (bc *MemoryCache) IsExist(ctx context.Context, key string) (bool, error) { +// IsExist check cache exist in memory. +func (bc *MemoryCache) IsExist(name string) bool { bc.RLock() defer bc.RUnlock() - if v, ok := bc.items[key]; ok { - return !v.isExpire(), nil + if v, ok := bc.items[name]; ok { + return !v.isExpire() } - return false, nil + return false } -// ClearAll deletes all cache in memory. -func (bc *MemoryCache) ClearAll(context.Context) error { +// ClearAll will delete all cache in memory. +func (bc *MemoryCache) ClearAll() error { bc.Lock() defer bc.Unlock() bc.items = make(map[string]*MemoryItem) return nil } -// StartAndGC starts memory cache. Checks expiration in every clock time. +// StartAndGC start memory cache. it will check expiration in every clock time. func (bc *MemoryCache) StartAndGC(config string) error { var cf map[string]int json.Unmarshal([]byte(config), &cf) @@ -236,7 +230,7 @@ func (bc *MemoryCache) vacuum() { } } -// expiredKeys returns keys list which are expired. +// expiredKeys returns key list which are expired. func (bc *MemoryCache) expiredKeys() (keys []string) { bc.RLock() defer bc.RUnlock() @@ -248,7 +242,7 @@ func (bc *MemoryCache) expiredKeys() (keys []string) { return } -// ClearItems removes all items who's key is in keys +// clearItems removes all the items which key in keys. func (bc *MemoryCache) clearItems(keys []string) { bc.Lock() defer bc.Unlock() diff --git a/client/cache/redis/redis.go b/cache/redis/redis.go similarity index 75% rename from client/cache/redis/redis.go rename to cache/redis/redis.go index 34059835..56faf211 100644 --- a/client/cache/redis/redis.go +++ b/cache/redis/redis.go @@ -30,21 +30,20 @@ package redis import ( - "context" "encoding/json" "errors" "fmt" "strconv" - "strings" "time" "github.com/gomodule/redigo/redis" - "github.com/astaxie/beego/client/cache" + "github.com/astaxie/beego/cache" + "strings" ) var ( - // The collection name of redis for the cache adapter. + // DefaultKey the collection name of redis for cache adapter. DefaultKey = "beecacheRedis" ) @@ -57,16 +56,16 @@ type Cache struct { password string maxIdle int - // Timeout value (less than the redis server's timeout value) - timeout time.Duration + //the timeout to a value less than the redis server's timeout. + timeout time.Duration } -// NewRedisCache creates a new redis cache with default collection name. +// NewRedisCache create new redis cache with default collection name. func NewRedisCache() cache.Cache { return &Cache{key: DefaultKey} } -// Execute the redis commands. args[0] must be the key name +// actually do the redis cmds, args[0] must be the key name. func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) { if len(args) < 1 { return nil, errors.New("missing required arguments") @@ -84,60 +83,63 @@ func (rc *Cache) associate(originKey interface{}) string { } // Get cache from redis. -func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) { +func (rc *Cache) Get(key string) interface{} { if v, err := rc.do("GET", key); err == nil { - return v, nil - } else { - return nil, err + return v } + return nil } -// GetMulti gets cache from redis. -func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { +// GetMulti get cache from redis. +func (rc *Cache) GetMulti(keys []string) []interface{} { c := rc.p.Get() defer c.Close() var args []interface{} for _, key := range keys { args = append(args, rc.associate(key)) } - return redis.Values(c.Do("MGET", args...)) + values, err := redis.Values(c.Do("MGET", args...)) + if err != nil { + return nil + } + return values } -// Put puts cache into redis. -func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { +// Put put cache to redis. +func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error { _, err := rc.do("SETEX", key, int64(timeout/time.Second), val) return err } -// Delete deletes a key's cache in redis. -func (rc *Cache) Delete(ctx context.Context, key string) error { +// Delete delete cache in redis. +func (rc *Cache) Delete(key string) error { _, err := rc.do("DEL", key) return err } -// IsExist checks cache's existence in redis. -func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) { +// IsExist check cache's existence in redis. +func (rc *Cache) IsExist(key string) bool { v, err := redis.Bool(rc.do("EXISTS", key)) if err != nil { - return false, err + return false } - return v, nil + return v } -// Incr increases a key's counter in redis. -func (rc *Cache) Incr(ctx context.Context, key string) error { +// Incr increase counter in redis. +func (rc *Cache) Incr(key string) error { _, err := redis.Bool(rc.do("INCRBY", key, 1)) return err } -// Decr decreases a key's counter in redis. -func (rc *Cache) Decr(ctx context.Context, key string) error { +// Decr decrease counter in redis. +func (rc *Cache) Decr(key string) error { _, err := redis.Bool(rc.do("INCRBY", key, -1)) return err } -// ClearAll deletes all cache in the redis collection -func (rc *Cache) ClearAll(context.Context) error { +// ClearAll clean all cache in redis. delete this redis collection. +func (rc *Cache) ClearAll() error { cachedKeys, err := rc.Scan(rc.key + ":*") if err != nil { return err @@ -152,7 +154,7 @@ func (rc *Cache) ClearAll(context.Context) error { return err } -// Scan scans all keys matching a given pattern. +// Scan scan all keys matching the pattern. a better choice than `keys` func (rc *Cache) Scan(pattern string) (keys []string, err error) { c := rc.p.Get() defer c.Close() @@ -181,9 +183,10 @@ func (rc *Cache) Scan(pattern string) (keys []string, err error) { } } -// StartAndGC starts the redis cache adapter. -// config: must be in this format {"key":"collection key","conn":"connection info","dbNum":"0"} -// Cached items in redis are stored forever, no garbage collection happens +// StartAndGC start redis cache adapter. +// config is like {"key":"collection key","conn":"connection info","dbNum":"0"} +// the cache item in redis are stored forever, +// so no gc operation. func (rc *Cache) StartAndGC(config string) error { var cf map[string]string json.Unmarshal([]byte(config), &cf) diff --git a/adapter/cache/redis/redis_test.go b/cache/redis/redis_test.go similarity index 86% rename from adapter/cache/redis/redis_test.go rename to cache/redis/redis_test.go index 7ae12197..7ac88f87 100644 --- a/adapter/cache/redis/redis_test.go +++ b/cache/redis/redis_test.go @@ -16,22 +16,15 @@ package redis import ( "fmt" - "os" "testing" "time" + "github.com/astaxie/beego/cache" "github.com/gomodule/redigo/redis" - - "github.com/astaxie/beego/adapter/cache" ) func TestRedisCache(t *testing.T) { - redisAddr := os.Getenv("REDIS_ADDR") - if redisAddr == "" { - redisAddr = "127.0.0.1:6379" - } - - bm, err := cache.NewCache("redis", fmt.Sprintf(`{"conn": "%s"}`, redisAddr)) + bm, err := cache.NewCache("redis", `{"conn": "127.0.0.1:6379"}`) if err != nil { t.Error("init err") } @@ -126,10 +119,26 @@ func TestCache_Scan(t *testing.T) { t.Error("set Error", err) } } + // scan all for the first time + keys, err := bm.(*Cache).Scan(DefaultKey + ":*") + if err != nil { + t.Error("scan Error", err) + } + if len(keys) != 10000 { + t.Error("scan all err") + } // clear all if err = bm.ClearAll(); err != nil { t.Error("clear all err") } + // scan all for the second time + keys, err = bm.(*Cache).Scan(DefaultKey + ":*") + if err != nil { + t.Error("scan Error", err) + } + if len(keys) != 0 { + t.Error("scan all err") + } } diff --git a/client/cache/ssdb/ssdb.go b/cache/ssdb/ssdb.go similarity index 70% rename from client/cache/ssdb/ssdb.go rename to cache/ssdb/ssdb.go index 1acee861..fa2ce04b 100644 --- a/client/cache/ssdb/ssdb.go +++ b/cache/ssdb/ssdb.go @@ -1,7 +1,6 @@ package ssdb import ( - "context" "encoding/json" "errors" "strconv" @@ -10,7 +9,7 @@ import ( "github.com/ssdb/gossdb/ssdb" - "github.com/astaxie/beego/client/cache" + "github.com/astaxie/beego/cache" ) // Cache SSDB adapter @@ -19,32 +18,35 @@ type Cache struct { conninfo []string } -//NewSsdbCache creates new ssdb adapter. +//NewSsdbCache create new ssdb adapter. func NewSsdbCache() cache.Cache { return &Cache{} } -// Get gets a key's value from memcache. -func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) { +// Get get value from memcache. +func (rc *Cache) Get(key string) interface{} { if rc.conn == nil { if err := rc.connectInit(); err != nil { - return nil, nil + return nil } } value, err := rc.conn.Get(key) if err == nil { - return value, nil + return value } - return nil, nil + return nil } -// GetMulti gets one or keys values from ssdb. -func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { +// GetMulti get value from memcache. +func (rc *Cache) GetMulti(keys []string) []interface{} { size := len(keys) var values []interface{} if rc.conn == nil { if err := rc.connectInit(); err != nil { - return values, err + for i := 0; i < size; i++ { + values = append(values, err) + } + return values } } res, err := rc.conn.Do("multi_get", keys) @@ -53,15 +55,15 @@ func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, er for i := 1; i < resSize; i += 2 { values = append(values, res[i+1]) } - return values, nil + return values } for i := 0; i < size; i++ { values = append(values, err) } - return values, nil + return values } -// DelMulti deletes one or more keys from memcache +// DelMulti get value from memcache. func (rc *Cache) DelMulti(keys []string) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { @@ -72,15 +74,14 @@ func (rc *Cache) DelMulti(keys []string) error { return err } -// Put puts value into memcache. -// value: must be of type string -func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { +// Put put value to memcache. only support string. +func (rc *Cache) Put(key string, value interface{}, timeout time.Duration) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err } } - v, ok := val.(string) + v, ok := value.(string) if !ok { return errors.New("value must string") } @@ -101,8 +102,8 @@ func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout t return errors.New("bad response") } -// Delete deletes a value in memcache. -func (rc *Cache) Delete(ctx context.Context, key string) error { +// Delete delete value in memcache. +func (rc *Cache) Delete(key string) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -112,8 +113,8 @@ func (rc *Cache) Delete(ctx context.Context, key string) error { return err } -// Incr increases a key's counter. -func (rc *Cache) Incr(ctx context.Context, key string) error { +// Incr increase counter. +func (rc *Cache) Incr(key string) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -123,8 +124,8 @@ func (rc *Cache) Incr(ctx context.Context, key string) error { return err } -// Decr decrements a key's counter. -func (rc *Cache) Decr(ctx context.Context, key string) error { +// Decr decrease counter. +func (rc *Cache) Decr(key string) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -134,26 +135,26 @@ func (rc *Cache) Decr(ctx context.Context, key string) error { return err } -// IsExist checks if a key exists in memcache. -func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) { +// IsExist check value exists in memcache. +func (rc *Cache) IsExist(key string) bool { if rc.conn == nil { if err := rc.connectInit(); err != nil { - return false, err + return false } } resp, err := rc.conn.Do("exists", key) if err != nil { - return false, err + return false } if len(resp) == 2 && resp[1] == "1" { - return true, nil + return true } - return false, nil + return false } -// ClearAll clears all cached items in memcache. -func (rc *Cache) ClearAll(context.Context) error { +// ClearAll clear all cached in memcache. +func (rc *Cache) ClearAll() error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -194,9 +195,9 @@ func (rc *Cache) Scan(keyStart string, keyEnd string, limit int) ([]string, erro return resp, nil } -// StartAndGC starts the memcache adapter. -// config: must be in the format {"conn":"connection info"}. -// If an error occurs during connection, an error is returned +// StartAndGC start memcache adapter. +// config string is like {"conn":"connection info"}. +// if connecting error, return. func (rc *Cache) StartAndGC(config string) error { var cf map[string]string json.Unmarshal([]byte(config), &cf) diff --git a/adapter/cache/ssdb/ssdb_test.go b/cache/ssdb/ssdb_test.go similarity index 90% rename from adapter/cache/ssdb/ssdb_test.go rename to cache/ssdb/ssdb_test.go index 080167cd..dd474960 100644 --- a/adapter/cache/ssdb/ssdb_test.go +++ b/cache/ssdb/ssdb_test.go @@ -1,22 +1,15 @@ package ssdb import ( - "fmt" - "os" "strconv" "testing" "time" - "github.com/astaxie/beego/adapter/cache" + "github.com/astaxie/beego/cache" ) func TestSsdbcacheCache(t *testing.T) { - ssdbAddr := os.Getenv("SSDB_ADDR") - if ssdbAddr == "" { - ssdbAddr = "127.0.0.1:8888" - } - - ssdb, err := cache.NewCache("ssdb", fmt.Sprintf(`{"conn": "%s"}`, ssdbAddr)) + ssdb, err := cache.NewCache("ssdb", `{"conn": "127.0.0.1:8888"}`) if err != nil { t.Error("init err") } diff --git a/client/cache/cache.go b/client/cache/cache.go deleted file mode 100644 index ddf246ab..00000000 --- a/client/cache/cache.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 cache provide a Cache interface and some implement engine -// Usage: -// -// import( -// "github.com/astaxie/beego/cache" -// ) -// -// bm, err := cache.NewCache("memory", `{"interval":60}`) -// -// Use it like this: -// -// bm.Put("astaxie", 1, 10 * time.Second) -// bm.Get("astaxie") -// bm.IsExist("astaxie") -// bm.Delete("astaxie") -// -// more docs http://beego.me/docs/module/cache.md -package cache - -import ( - "context" - "fmt" - "time" -) - -// Cache interface contains all behaviors for cache adapter. -// usage: -// cache.Register("file",cache.NewFileCache) // this operation is run in init method of file.go. -// c,err := cache.NewCache("file","{....}") -// c.Put("key",value, 3600 * time.Second) -// v := c.Get("key") -// -// c.Incr("counter") // now is 1 -// c.Incr("counter") // now is 2 -// count := c.Get("counter").(int) -type Cache interface { - // Get a cached value by key. - Get(ctx context.Context, key string) (interface{}, error) - // GetMulti is a batch version of Get. - GetMulti(ctx context.Context, keys []string) ([]interface{}, error) - // Set a cached value with key and expire time. - Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error - // Delete cached value by key. - Delete(ctx context.Context, key string) error - // Increment a cached int value by key, as a counter. - Incr(ctx context.Context, key string) error - // Decrement a cached int value by key, as a counter. - Decr(ctx context.Context, key string) error - // Check if a cached value exists or not. - IsExist(ctx context.Context, key string) (bool, error) - // Clear all cache. - ClearAll(ctx context.Context) error - // Start gc routine based on config string settings. - StartAndGC(config string) error -} - -// Instance is a function create a new Cache Instance -type Instance func() Cache - -var adapters = make(map[string]Instance) - -// Register makes a cache adapter available by the adapter name. -// If Register is called twice with the same name or if driver is nil, -// it panics. -func Register(name string, adapter Instance) { - if adapter == nil { - panic("cache: Register adapter is nil") - } - if _, ok := adapters[name]; ok { - panic("cache: Register called twice for adapter " + name) - } - adapters[name] = adapter -} - -// NewCache creates a new cache driver by adapter name and config string. -// config: must be in JSON format such as {"interval":360}. -// Starts gc automatically. -func NewCache(adapterName, config string) (adapter Cache, err error) { - instanceFunc, ok := adapters[adapterName] - if !ok { - err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName) - return - } - adapter = instanceFunc() - err = adapter.StartAndGC(config) - if err != nil { - adapter = nil - } - return -} diff --git a/client/cache/cache_test.go b/client/cache/cache_test.go deleted file mode 100644 index 6066b72d..00000000 --- a/client/cache/cache_test.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 cache - -import ( - "context" - "os" - "sync" - "testing" - "time" -) - -func TestCacheIncr(t *testing.T) { - bm, err := NewCache("memory", `{"interval":20}`) - if err != nil { - t.Error("init err") - } - // timeoutDuration := 10 * time.Second - - bm.Put(context.Background(), "edwardhey", 0, time.Second*20) - wg := sync.WaitGroup{} - wg.Add(10) - for i := 0; i < 10; i++ { - go func() { - defer wg.Done() - bm.Incr(context.Background(), "edwardhey") - }() - } - wg.Wait() - val, _ := bm.Get(context.Background(), "edwardhey") - if val.(int) != 10 { - t.Error("Incr err") - } -} - -func TestCache(t *testing.T) { - bm, err := NewCache("memory", `{"interval":20}`) - if err != nil { - t.Error("init err") - } - timeoutDuration := 10 * time.Second - if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil { - t.Error("set Error", err) - } - if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { - t.Error("check err") - } - - if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 1 { - t.Error("get err") - } - - time.Sleep(30 * time.Second) - - if res, _ := bm.IsExist(context.Background(), "astaxie"); res { - t.Error("check err") - } - - if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil { - t.Error("set Error", err) - } - - if err = bm.Incr(context.Background(), "astaxie"); err != nil { - t.Error("Incr Error", err) - } - - if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 2 { - t.Error("get err") - } - - if err = bm.Decr(context.Background(), "astaxie"); err != nil { - t.Error("Decr Error", err) - } - - if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 1 { - t.Error("get err") - } - bm.Delete(context.Background(), "astaxie") - if res, _ := bm.IsExist(context.Background(), "astaxie"); res { - t.Error("delete err") - } - - // test GetMulti - if err = bm.Put(context.Background(), "astaxie", "author", timeoutDuration); err != nil { - t.Error("set Error", err) - } - if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { - t.Error("check err") - } - if v, _ := bm.Get(context.Background(), "astaxie"); v.(string) != "author" { - t.Error("get err") - } - - if err = bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration); err != nil { - t.Error("set Error", err) - } - if res, _ := bm.IsExist(context.Background(), "astaxie1"); !res { - t.Error("check err") - } - - vv, _ := bm.GetMulti(context.Background(), []string{"astaxie", "astaxie1"}) - if len(vv) != 2 { - t.Error("GetMulti ERROR") - } - if vv[0].(string) != "author" { - t.Error("GetMulti ERROR") - } - if vv[1].(string) != "author1" { - t.Error("GetMulti ERROR") - } -} - -func TestFileCache(t *testing.T) { - bm, err := NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}`) - if err != nil { - t.Error("init err") - } - timeoutDuration := 10 * time.Second - if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil { - t.Error("set Error", err) - } - if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { - t.Error("check err") - } - - if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 1 { - t.Error("get err") - } - - if err = bm.Incr(context.Background(), "astaxie"); err != nil { - t.Error("Incr Error", err) - } - - if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 2 { - t.Error("get err") - } - - if err = bm.Decr(context.Background(), "astaxie"); err != nil { - t.Error("Decr Error", err) - } - - if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 1 { - t.Error("get err") - } - bm.Delete(context.Background(), "astaxie") - if res, _ := bm.IsExist(context.Background(), "astaxie"); res { - t.Error("delete err") - } - - // test string - if err = bm.Put(context.Background(), "astaxie", "author", timeoutDuration); err != nil { - t.Error("set Error", err) - } - if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { - t.Error("check err") - } - if v, _ := bm.Get(context.Background(), "astaxie"); v.(string) != "author" { - t.Error("get err") - } - - // test GetMulti - if err = bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration); err != nil { - t.Error("set Error", err) - } - if res, _ := bm.IsExist(context.Background(), "astaxie1"); !res { - t.Error("check err") - } - - vv, _ := bm.GetMulti(context.Background(), []string{"astaxie", "astaxie1"}) - if len(vv) != 2 { - t.Error("GetMulti ERROR") - } - if vv[0].(string) != "author" { - t.Error("GetMulti ERROR") - } - if vv[1].(string) != "author1" { - t.Error("GetMulti ERROR") - } - - os.RemoveAll("cache") -} diff --git a/client/cache/conv_test.go b/client/cache/conv_test.go deleted file mode 100644 index b90e224a..00000000 --- a/client/cache/conv_test.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 cache - -import ( - "testing" -) - -func TestGetString(t *testing.T) { - var t1 = "test1" - if "test1" != GetString(t1) { - t.Error("get string from string error") - } - var t2 = []byte("test2") - if "test2" != GetString(t2) { - t.Error("get string from byte array error") - } - var t3 = 1 - if "1" != GetString(t3) { - t.Error("get string from int error") - } - var t4 int64 = 1 - if "1" != GetString(t4) { - t.Error("get string from int64 error") - } - var t5 = 1.1 - if "1.1" != GetString(t5) { - t.Error("get string from float64 error") - } - - if "" != GetString(nil) { - t.Error("get string from nil error") - } -} - -func TestGetInt(t *testing.T) { - var t1 = 1 - if 1 != GetInt(t1) { - t.Error("get int from int error") - } - var t2 int32 = 32 - if 32 != GetInt(t2) { - t.Error("get int from int32 error") - } - var t3 int64 = 64 - if 64 != GetInt(t3) { - t.Error("get int from int64 error") - } - var t4 = "128" - if 128 != GetInt(t4) { - t.Error("get int from num string error") - } - if 0 != GetInt(nil) { - t.Error("get int from nil error") - } -} - -func TestGetInt64(t *testing.T) { - var i int64 = 1 - var t1 = 1 - if i != GetInt64(t1) { - t.Error("get int64 from int error") - } - var t2 int32 = 1 - if i != GetInt64(t2) { - t.Error("get int64 from int32 error") - } - var t3 int64 = 1 - if i != GetInt64(t3) { - t.Error("get int64 from int64 error") - } - var t4 = "1" - if i != GetInt64(t4) { - t.Error("get int64 from num string error") - } - if 0 != GetInt64(nil) { - t.Error("get int64 from nil") - } -} - -func TestGetFloat64(t *testing.T) { - var f = 1.11 - var t1 float32 = 1.11 - if f != GetFloat64(t1) { - t.Error("get float64 from float32 error") - } - var t2 = 1.11 - if f != GetFloat64(t2) { - t.Error("get float64 from float64 error") - } - var t3 = "1.11" - if f != GetFloat64(t3) { - t.Error("get float64 from string error") - } - - var f2 float64 = 1 - var t4 = 1 - if f2 != GetFloat64(t4) { - t.Error("get float64 from int error") - } - - if 0 != GetFloat64(nil) { - t.Error("get float64 from nil error") - } -} - -func TestGetBool(t *testing.T) { - var t1 = true - if !GetBool(t1) { - t.Error("get bool from bool error") - } - var t2 = "true" - if !GetBool(t2) { - t.Error("get bool from string error") - } - if GetBool(nil) { - t.Error("get bool from nil error") - } -} - -func byteArrayEquals(a []byte, b []byte) bool { - if len(a) != len(b) { - return false - } - for i, v := range a { - if v != b[i] { - return false - } - } - return true -} diff --git a/client/cache/memcache/memcache_test.go b/client/cache/memcache/memcache_test.go deleted file mode 100644 index bc8936a7..00000000 --- a/client/cache/memcache/memcache_test.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 memcache - -import ( - "context" - "fmt" - "os" - "strconv" - "testing" - "time" - - _ "github.com/bradfitz/gomemcache/memcache" - - "github.com/astaxie/beego/client/cache" -) - -func TestMemcacheCache(t *testing.T) { - - addr := os.Getenv("MEMCACHE_ADDR") - if addr == "" { - addr = "127.0.0.1:11211" - } - - bm, err := cache.NewCache("memcache", fmt.Sprintf(`{"conn": "%s"}`, addr)) - if err != nil { - t.Error("init err") - } - timeoutDuration := 10 * time.Second - if err = bm.Put(context.Background(), "astaxie", "1", timeoutDuration); err != nil { - t.Error("set Error", err) - } - if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { - t.Error("check err") - } - - time.Sleep(11 * time.Second) - - if res, _ := bm.IsExist(context.Background(), "astaxie"); res { - t.Error("check err") - } - if err = bm.Put(context.Background(), "astaxie", "1", timeoutDuration); err != nil { - t.Error("set Error", err) - } - - val, _ := bm.Get(context.Background(), "astaxie") - if v, err := strconv.Atoi(string(val.([]byte))); err != nil || v != 1 { - t.Error("get err") - } - - if err = bm.Incr(context.Background(), "astaxie"); err != nil { - t.Error("Incr Error", err) - } - - val, _ = bm.Get(context.Background(), "astaxie") - if v, err := strconv.Atoi(string(val.([]byte))); err != nil || v != 2 { - t.Error("get err") - } - - if err = bm.Decr(context.Background(), "astaxie"); err != nil { - t.Error("Decr Error", err) - } - - val, _ = bm.Get(context.Background(), "astaxie") - if v, err := strconv.Atoi(string(val.([]byte))); err != nil || v != 1 { - t.Error("get err") - } - bm.Delete(context.Background(), "astaxie") - if res, _ := bm.IsExist(context.Background(), "astaxie"); res { - t.Error("delete err") - } - - // test string - if err = bm.Put(context.Background(), "astaxie", "author", timeoutDuration); err != nil { - t.Error("set Error", err) - } - if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { - t.Error("check err") - } - - val, _ = bm.Get(context.Background(), "astaxie") - if v := val.([]byte); string(v) != "author" { - t.Error("get err") - } - - // test GetMulti - if err = bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration); err != nil { - t.Error("set Error", err) - } - if res, _ := bm.IsExist(context.Background(), "astaxie1"); !res { - t.Error("check err") - } - - vv, _ := bm.GetMulti(context.Background(), []string{"astaxie", "astaxie1"}) - if len(vv) != 2 { - t.Error("GetMulti ERROR") - } - if string(vv[0].([]byte)) != "author" && string(vv[0].([]byte)) != "author1" { - t.Error("GetMulti ERROR") - } - if string(vv[1].([]byte)) != "author1" && string(vv[1].([]byte)) != "author" { - t.Error("GetMulti ERROR") - } - - // test clear all - if err = bm.ClearAll(context.Background()); err != nil { - t.Error("clear all err") - } -} diff --git a/client/cache/redis/redis_test.go b/client/cache/redis/redis_test.go deleted file mode 100644 index f82b2c40..00000000 --- a/client/cache/redis/redis_test.go +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 redis - -import ( - "context" - "fmt" - "os" - "testing" - "time" - - "github.com/gomodule/redigo/redis" - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/client/cache" -) - -func TestRedisCache(t *testing.T) { - - redisAddr := os.Getenv("REDIS_ADDR") - if redisAddr == "" { - redisAddr = "127.0.0.1:6379" - } - - bm, err := cache.NewCache("redis", fmt.Sprintf(`{"conn": "%s"}`, redisAddr)) - if err != nil { - t.Error("init err") - } - timeoutDuration := 10 * time.Second - if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil { - t.Error("set Error", err) - } - if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { - t.Error("check err") - } - - time.Sleep(11 * time.Second) - - if res, _ := bm.IsExist(context.Background(), "astaxie"); res { - t.Error("check err") - } - if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil { - t.Error("set Error", err) - } - - val, _ := bm.Get(context.Background(), "astaxie") - if v, _ := redis.Int(val, err); v != 1 { - t.Error("get err") - } - - if err = bm.Incr(context.Background(), "astaxie"); err != nil { - t.Error("Incr Error", err) - } - val, _ = bm.Get(context.Background(), "astaxie") - if v, _ := redis.Int(val, err); v != 2 { - t.Error("get err") - } - - if err = bm.Decr(context.Background(), "astaxie"); err != nil { - t.Error("Decr Error", err) - } - - val, _ = bm.Get(context.Background(), "astaxie") - if v, _ := redis.Int(val, err); v != 1 { - t.Error("get err") - } - bm.Delete(context.Background(), "astaxie") - if res, _ := bm.IsExist(context.Background(), "astaxie"); res { - t.Error("delete err") - } - - // test string - if err = bm.Put(context.Background(), "astaxie", "author", timeoutDuration); err != nil { - t.Error("set Error", err) - } - if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { - t.Error("check err") - } - - val, _ = bm.Get(context.Background(), "astaxie") - if v, _ := redis.String(val, err); v != "author" { - t.Error("get err") - } - - // test GetMulti - if err = bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration); err != nil { - t.Error("set Error", err) - } - if res, _ := bm.IsExist(context.Background(), "astaxie1"); !res { - t.Error("check err") - } - - vv, _ := bm.GetMulti(context.Background(), []string{"astaxie", "astaxie1"}) - if len(vv) != 2 { - t.Error("GetMulti ERROR") - } - if v, _ := redis.String(vv[0], nil); v != "author" { - t.Error("GetMulti ERROR") - } - if v, _ := redis.String(vv[1], nil); v != "author1" { - t.Error("GetMulti ERROR") - } - - // test clear all - if err = bm.ClearAll(context.Background()); err != nil { - t.Error("clear all err") - } -} - -func TestCache_Scan(t *testing.T) { - timeoutDuration := 10 * time.Second - - addr := os.Getenv("REDIS_ADDR") - if addr == "" { - addr = "127.0.0.1:6379" - } - - // init - bm, err := cache.NewCache("redis", fmt.Sprintf(`{"conn": "%s"}`, addr)) - if err != nil { - t.Error("init err") - } - // insert all - for i := 0; i < 100; i++ { - if err = bm.Put(context.Background(), fmt.Sprintf("astaxie%d", i), fmt.Sprintf("author%d", i), timeoutDuration); err != nil { - t.Error("set Error", err) - } - } - time.Sleep(time.Second) - // scan all for the first time - keys, err := bm.(*Cache).Scan(DefaultKey + ":*") - if err != nil { - t.Error("scan Error", err) - } - - assert.Equal(t, 100, len(keys), "scan all error") - - // clear all - if err = bm.ClearAll(context.Background()); err != nil { - t.Error("clear all err") - } - - // scan all for the second time - keys, err = bm.(*Cache).Scan(DefaultKey + ":*") - if err != nil { - t.Error("scan Error", err) - } - if len(keys) != 0 { - t.Error("scan all err") - } -} diff --git a/client/cache/ssdb/ssdb_test.go b/client/cache/ssdb/ssdb_test.go deleted file mode 100644 index cebaa975..00000000 --- a/client/cache/ssdb/ssdb_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package ssdb - -import ( - "context" - "fmt" - "os" - "strconv" - "testing" - "time" - - "github.com/astaxie/beego/client/cache" -) - -func TestSsdbcacheCache(t *testing.T) { - - ssdbAddr := os.Getenv("SSDB_ADDR") - if ssdbAddr == "" { - ssdbAddr = "127.0.0.1:8888" - } - - ssdb, err := cache.NewCache("ssdb", fmt.Sprintf(`{"conn": "%s"}`, ssdbAddr)) - if err != nil { - t.Error("init err") - } - - // test put and exist - if res, _ := ssdb.IsExist(context.Background(), "ssdb"); res { - t.Error("check err") - } - timeoutDuration := 10 * time.Second - // timeoutDuration := -10*time.Second if timeoutDuration is negtive,it means permanent - if err = ssdb.Put(context.Background(), "ssdb", "ssdb", timeoutDuration); err != nil { - t.Error("set Error", err) - } - if res, _ := ssdb.IsExist(context.Background(), "ssdb"); !res { - t.Error("check err") - } - - // Get test done - if err = ssdb.Put(context.Background(), "ssdb", "ssdb", timeoutDuration); err != nil { - t.Error("set Error", err) - } - - if v, _ := ssdb.Get(context.Background(), "ssdb"); v != "ssdb" { - t.Error("get Error") - } - - // inc/dec test done - if err = ssdb.Put(context.Background(), "ssdb", "2", timeoutDuration); err != nil { - t.Error("set Error", err) - } - if err = ssdb.Incr(context.Background(), "ssdb"); err != nil { - t.Error("incr Error", err) - } - - val, _ := ssdb.Get(context.Background(), "ssdb") - if v, err := strconv.Atoi(val.(string)); err != nil || v != 3 { - t.Error("get err") - } - - if err = ssdb.Decr(context.Background(), "ssdb"); err != nil { - t.Error("decr error") - } - - // test del - if err = ssdb.Put(context.Background(), "ssdb", "3", timeoutDuration); err != nil { - t.Error("set Error", err) - } - - val, _ = ssdb.Get(context.Background(), "ssdb") - if v, err := strconv.Atoi(val.(string)); err != nil || v != 3 { - t.Error("get err") - } - if err := ssdb.Delete(context.Background(), "ssdb"); err == nil { - if e, _ := ssdb.IsExist(context.Background(), "ssdb"); e { - t.Error("delete err") - } - } - - // test string - if err = ssdb.Put(context.Background(), "ssdb", "ssdb", -10*time.Second); err != nil { - t.Error("set Error", err) - } - if res, _ := ssdb.IsExist(context.Background(), "ssdb"); !res { - t.Error("check err") - } - if v, _ := ssdb.Get(context.Background(), "ssdb"); v.(string) != "ssdb" { - t.Error("get err") - } - - // test GetMulti done - if err = ssdb.Put(context.Background(), "ssdb1", "ssdb1", -10*time.Second); err != nil { - t.Error("set Error", err) - } - if res, _ := ssdb.IsExist(context.Background(), "ssdb1"); !res { - t.Error("check err") - } - vv, _ := ssdb.GetMulti(context.Background(), []string{"ssdb", "ssdb1"}) - if len(vv) != 2 { - t.Error("getmulti error") - } - if vv[0].(string) != "ssdb" { - t.Error("getmulti error") - } - if vv[1].(string) != "ssdb1" { - t.Error("getmulti error") - } - - // test clear all done - if err = ssdb.ClearAll(context.Background()); err != nil { - t.Error("clear all err") - } - e1, _ := ssdb.IsExist(context.Background(), "ssdb") - e2, _ := ssdb.IsExist(context.Background(), "ssdb1") - if e1 || e2 { - t.Error("check err") - } -} diff --git a/client/httplib/filter.go b/client/httplib/filter.go deleted file mode 100644 index 5daed64c..00000000 --- a/client/httplib/filter.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 httplib - -import ( - "context" - "net/http" -) - -type FilterChain func(next Filter) Filter - -type Filter func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) diff --git a/client/httplib/filter/opentracing/filter.go b/client/httplib/filter/opentracing/filter.go deleted file mode 100644 index 765a82a9..00000000 --- a/client/httplib/filter/opentracing/filter.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 opentracing - -import ( - "context" - "net/http" - - "github.com/astaxie/beego/client/httplib" - logKit "github.com/go-kit/kit/log" - opentracingKit "github.com/go-kit/kit/tracing/opentracing" - "github.com/opentracing/opentracing-go" -) - -type FilterChainBuilder struct { - // CustomSpanFunc users are able to custom their span - CustomSpanFunc func(span opentracing.Span, ctx context.Context, - req *httplib.BeegoHTTPRequest, resp *http.Response, err error) -} - -func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filter { - - return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { - - method := req.GetRequest().Method - - operationName := method + "#" + req.GetRequest().URL.String() - span, spanCtx := opentracing.StartSpanFromContext(ctx, operationName) - defer span.Finish() - - inject := opentracingKit.ContextToHTTP(opentracing.GlobalTracer(), logKit.NewNopLogger()) - inject(spanCtx, req.GetRequest()) - resp, err := next(spanCtx, req) - - if resp != nil { - span.SetTag("http.status_code", resp.StatusCode) - } - span.SetTag("http.method", method) - span.SetTag("peer.hostname", req.GetRequest().URL.Host) - span.SetTag("http.url", req.GetRequest().URL.String()) - span.SetTag("http.scheme", req.GetRequest().URL.Scheme) - span.SetTag("span.kind", "client") - span.SetTag("component", "beego") - if err != nil { - span.SetTag("error", true) - span.SetTag("message", err.Error()) - } else if resp != nil && !(resp.StatusCode < 300 && resp.StatusCode >= 200) { - span.SetTag("error", true) - } - - span.SetTag("peer.address", req.GetRequest().RemoteAddr) - span.SetTag("http.proto", req.GetRequest().Proto) - - if builder.CustomSpanFunc != nil { - builder.CustomSpanFunc(span, ctx, req, resp, err) - } - return resp, err - } -} diff --git a/client/httplib/filter/opentracing/filter_test.go b/client/httplib/filter/opentracing/filter_test.go deleted file mode 100644 index 7281f93f..00000000 --- a/client/httplib/filter/opentracing/filter_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 opentracing - -import ( - "context" - "errors" - "net/http" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/client/httplib" -) - -func TestFilterChainBuilder_FilterChain(t *testing.T) { - next := func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { - time.Sleep(100 * time.Millisecond) - return &http.Response{ - StatusCode: 404, - }, errors.New("hello") - } - builder := &FilterChainBuilder{} - filter := builder.FilterChain(next) - req := httplib.Get("https://github.com/notifications?query=repo%3Aastaxie%2Fbeego") - resp, err := filter(context.Background(), req) - assert.NotNil(t, resp) - assert.NotNil(t, err) -} diff --git a/client/httplib/filter/prometheus/filter.go b/client/httplib/filter/prometheus/filter.go deleted file mode 100644 index ce88b70e..00000000 --- a/client/httplib/filter/prometheus/filter.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 prometheus - -import ( - "context" - "net/http" - "strconv" - "time" - - "github.com/prometheus/client_golang/prometheus" - - "github.com/astaxie/beego/client/httplib" -) - -type FilterChainBuilder struct { - summaryVec prometheus.ObserverVec - AppName string - ServerName string - RunMode string -} - -func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filter { - - builder.summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{ - Name: "beego", - Subsystem: "remote_http_request", - ConstLabels: map[string]string{ - "server": builder.ServerName, - "env": builder.RunMode, - "appname": builder.AppName, - }, - Help: "The statics info for remote http requests", - }, []string{"proto", "scheme", "method", "host", "path", "status", "duration", "isError"}) - - return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { - startTime := time.Now() - resp, err := next(ctx, req) - endTime := time.Now() - go builder.report(startTime, endTime, ctx, req, resp, err) - return resp, err - } -} - -func (builder *FilterChainBuilder) report(startTime time.Time, endTime time.Time, - ctx context.Context, req *httplib.BeegoHTTPRequest, resp *http.Response, err error) { - - proto := req.GetRequest().Proto - - scheme := req.GetRequest().URL.Scheme - method := req.GetRequest().Method - - host := req.GetRequest().URL.Host - path := req.GetRequest().URL.Path - - status := -1 - if resp != nil { - status = resp.StatusCode - } - - dur := int(endTime.Sub(startTime) / time.Millisecond) - - builder.summaryVec.WithLabelValues(proto, scheme, method, host, path, - strconv.Itoa(status), strconv.Itoa(dur), strconv.FormatBool(err == nil)) -} diff --git a/client/httplib/filter/prometheus/filter_test.go b/client/httplib/filter/prometheus/filter_test.go deleted file mode 100644 index 46edc3d2..00000000 --- a/client/httplib/filter/prometheus/filter_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 prometheus - -import ( - "context" - "net/http" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/client/httplib" -) - -func TestFilterChainBuilder_FilterChain(t *testing.T) { - next := func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { - time.Sleep(100 * time.Millisecond) - return &http.Response{ - StatusCode: 404, - }, nil - } - builder := &FilterChainBuilder{} - filter := builder.FilterChain(next) - req := httplib.Get("https://github.com/notifications?query=repo%3Aastaxie%2Fbeego") - resp, err := filter(context.Background(), req) - assert.NotNil(t, resp) - assert.Nil(t, err) -} diff --git a/client/httplib/httplib_test.go b/client/httplib/httplib_test.go deleted file mode 100644 index 88935715..00000000 --- a/client/httplib/httplib_test.go +++ /dev/null @@ -1,302 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 httplib - -import ( - "context" - "errors" - "io/ioutil" - "net" - "net/http" - "os" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestResponse(t *testing.T) { - req := Get("http://httpbin.org/get") - resp, err := req.Response() - if err != nil { - t.Fatal(err) - } - t.Log(resp) -} - -func TestDoRequest(t *testing.T) { - req := Get("https://goolnk.com/33BD2j") - retryAmount := 1 - req.Retries(1) - req.RetryDelay(1400 * time.Millisecond) - retryDelay := 1400 * time.Millisecond - - req.setting.CheckRedirect = func(redirectReq *http.Request, redirectVia []*http.Request) error { - return errors.New("Redirect triggered") - } - - startTime := time.Now().UnixNano() / int64(time.Millisecond) - - _, err := req.Response() - if err == nil { - t.Fatal("Response should have yielded an error") - } - - endTime := time.Now().UnixNano() / int64(time.Millisecond) - elapsedTime := endTime - startTime - delayedTime := int64(retryAmount) * retryDelay.Milliseconds() - - if elapsedTime < delayedTime { - t.Errorf("Not enough retries. Took %dms. Delay was meant to take %dms", elapsedTime, delayedTime) - } - -} - -func TestGet(t *testing.T) { - req := Get("http://httpbin.org/get") - b, err := req.Bytes() - if err != nil { - t.Fatal(err) - } - t.Log(b) - - s, err := req.String() - if err != nil { - t.Fatal(err) - } - t.Log(s) - - if string(b) != s { - t.Fatal("request data not match") - } -} - -func TestSimplePost(t *testing.T) { - v := "smallfish" - req := Post("http://httpbin.org/post") - req.Param("username", v) - - str, err := req.String() - if err != nil { - t.Fatal(err) - } - t.Log(str) - - n := strings.Index(str, v) - if n == -1 { - t.Fatal(v + " not found in post") - } -} - -//func TestPostFile(t *testing.T) { -// v := "smallfish" -// req := Post("http://httpbin.org/post") -// req.Debug(true) -// req.Param("username", v) -// req.PostFile("uploadfile", "httplib_test.go") - -// str, err := req.String() -// if err != nil { -// t.Fatal(err) -// } -// t.Log(str) - -// n := strings.Index(str, v) -// if n == -1 { -// t.Fatal(v + " not found in post") -// } -//} - -func TestSimplePut(t *testing.T) { - str, err := Put("http://httpbin.org/put").String() - if err != nil { - t.Fatal(err) - } - t.Log(str) -} - -func TestSimpleDelete(t *testing.T) { - str, err := Delete("http://httpbin.org/delete").String() - if err != nil { - t.Fatal(err) - } - t.Log(str) -} - -func TestSimpleDeleteParam(t *testing.T) { - str, err := Delete("http://httpbin.org/delete").Param("key", "val").String() - if err != nil { - t.Fatal(err) - } - t.Log(str) -} - -func TestWithCookie(t *testing.T) { - v := "smallfish" - str, err := Get("http://httpbin.org/cookies/set?k1=" + v).SetEnableCookie(true).String() - if err != nil { - t.Fatal(err) - } - t.Log(str) - - str, err = Get("http://httpbin.org/cookies").SetEnableCookie(true).String() - if err != nil { - t.Fatal(err) - } - t.Log(str) - - n := strings.Index(str, v) - if n == -1 { - t.Fatal(v + " not found in cookie") - } -} - -func TestWithBasicAuth(t *testing.T) { - str, err := Get("http://httpbin.org/basic-auth/user/passwd").SetBasicAuth("user", "passwd").String() - if err != nil { - t.Fatal(err) - } - t.Log(str) - n := strings.Index(str, "authenticated") - if n == -1 { - t.Fatal("authenticated not found in response") - } -} - -func TestWithUserAgent(t *testing.T) { - v := "beego" - str, err := Get("http://httpbin.org/headers").SetUserAgent(v).String() - if err != nil { - t.Fatal(err) - } - t.Log(str) - - n := strings.Index(str, v) - if n == -1 { - t.Fatal(v + " not found in user-agent") - } -} - -func TestWithSetting(t *testing.T) { - v := "beego" - var setting BeegoHTTPSettings - setting.EnableCookie = true - setting.UserAgent = v - setting.Transport = &http.Transport{ - DialContext: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - DualStack: true, - }).DialContext, - MaxIdleConns: 50, - IdleConnTimeout: 90 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - } - setting.ReadWriteTimeout = 5 * time.Second - SetDefaultSetting(setting) - - str, err := Get("http://httpbin.org/get").String() - if err != nil { - t.Fatal(err) - } - t.Log(str) - - n := strings.Index(str, v) - if n == -1 { - t.Fatal(v + " not found in user-agent") - } -} - -func TestToJson(t *testing.T) { - req := Get("http://httpbin.org/ip") - resp, err := req.Response() - if err != nil { - t.Fatal(err) - } - t.Log(resp) - - // httpbin will return http remote addr - type IP struct { - Origin string `json:"origin"` - } - var ip IP - err = req.ToJSON(&ip) - if err != nil { - t.Fatal(err) - } - t.Log(ip.Origin) - ips := strings.Split(ip.Origin, ",") - if len(ips) == 0 { - t.Fatal("response is not valid ip") - } - for i := range ips { - if net.ParseIP(strings.TrimSpace(ips[i])).To4() == nil { - t.Fatal("response is not valid ip") - } - } - -} - -func TestToFile(t *testing.T) { - f := "beego_testfile" - req := Get("http://httpbin.org/ip") - err := req.ToFile(f) - if err != nil { - t.Fatal(err) - } - defer os.Remove(f) - b, err := ioutil.ReadFile(f) - if n := strings.Index(string(b), "origin"); n == -1 { - t.Fatal(err) - } -} - -func TestToFileDir(t *testing.T) { - f := "./files/beego_testfile" - req := Get("http://httpbin.org/ip") - err := req.ToFile(f) - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll("./files") - b, err := ioutil.ReadFile(f) - if n := strings.Index(string(b), "origin"); n == -1 { - t.Fatal(err) - } -} - -func TestHeader(t *testing.T) { - req := Get("http://httpbin.org/headers") - req.Header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36") - str, err := req.String() - if err != nil { - t.Fatal(err) - } - t.Log(str) -} - -// TestAddFilter make sure that AddFilters only work for the specific request -func TestAddFilter(t *testing.T) { - req := Get("http://beego.me") - req.AddFilters(func(next Filter) Filter { - return func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) { - return next(ctx, req) - } - }) - - r := Get("http://beego.me") - assert.Equal(t, 1, len(req.setting.FilterChains)-len(r.setting.FilterChains)) -} diff --git a/client/orm/cmd_utils.go b/client/orm/cmd_utils.go deleted file mode 100644 index 8d6c0c33..00000000 --- a/client/orm/cmd_utils.go +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "fmt" - "strings" -) - -type dbIndex struct { - Table string - Name string - SQL string -} - -// get database column type string. -func getColumnTyp(al *alias, fi *fieldInfo) (col string) { - T := al.DbBaser.DbTypes() - fieldType := fi.fieldType - fieldSize := fi.size - -checkColumn: - switch fieldType { - case TypeBooleanField: - col = T["bool"] - case TypeVarCharField: - if al.Driver == DRPostgres && fi.toText { - col = T["string-text"] - } else { - col = fmt.Sprintf(T["string"], fieldSize) - } - case TypeCharField: - col = fmt.Sprintf(T["string-char"], fieldSize) - case TypeTextField: - col = T["string-text"] - case TypeTimeField: - col = T["time.Time-clock"] - case TypeDateField: - col = T["time.Time-date"] - case TypeDateTimeField: - // the precision of sqlite is not implemented - if al.Driver == 2 || fi.timePrecision == nil { - col = T["time.Time"] - } else { - s := T["time.Time-precision"] - col = fmt.Sprintf(s, *fi.timePrecision) - } - - case TypeBitField: - col = T["int8"] - case TypeSmallIntegerField: - col = T["int16"] - case TypeIntegerField: - col = T["int32"] - case TypeBigIntegerField: - if al.Driver == DRSqlite { - fieldType = TypeIntegerField - goto checkColumn - } - col = T["int64"] - case TypePositiveBitField: - col = T["uint8"] - case TypePositiveSmallIntegerField: - col = T["uint16"] - case TypePositiveIntegerField: - col = T["uint32"] - case TypePositiveBigIntegerField: - col = T["uint64"] - case TypeFloatField: - col = T["float64"] - case TypeDecimalField: - s := T["float64-decimal"] - if !strings.Contains(s, "%d") { - col = s - } else { - col = fmt.Sprintf(s, fi.digits, fi.decimals) - } - case TypeJSONField: - if al.Driver != DRPostgres { - fieldType = TypeVarCharField - goto checkColumn - } - col = T["json"] - case TypeJsonbField: - if al.Driver != DRPostgres { - fieldType = TypeVarCharField - goto checkColumn - } - col = T["jsonb"] - case RelForeignKey, RelOneToOne: - fieldType = fi.relModelInfo.fields.pk.fieldType - fieldSize = fi.relModelInfo.fields.pk.size - goto checkColumn - } - - return -} - -// create alter sql string. -func getColumnAddQuery(al *alias, fi *fieldInfo) string { - Q := al.DbBaser.TableQuote() - typ := getColumnTyp(al, fi) - - if !fi.null { - typ += " " + "NOT NULL" - } - - return fmt.Sprintf("ALTER TABLE %s%s%s ADD COLUMN %s%s%s %s %s", - Q, fi.mi.table, Q, - Q, fi.column, Q, - typ, getColumnDefault(fi), - ) -} - -// Get string value for the attribute "DEFAULT" for the CREATE, ALTER commands -func getColumnDefault(fi *fieldInfo) string { - var ( - v, t, d string - ) - - // Skip default attribute if field is in relations - if fi.rel || fi.reverse { - return v - } - - t = " DEFAULT '%s' " - - // These defaults will be useful if there no config value orm:"default" and NOT NULL is on - switch fi.fieldType { - case TypeTimeField, TypeDateField, TypeDateTimeField, TypeTextField: - return v - - case TypeBitField, TypeSmallIntegerField, TypeIntegerField, - TypeBigIntegerField, TypePositiveBitField, TypePositiveSmallIntegerField, - TypePositiveIntegerField, TypePositiveBigIntegerField, TypeFloatField, - TypeDecimalField: - t = " DEFAULT %s " - d = "0" - case TypeBooleanField: - t = " DEFAULT %s " - d = "FALSE" - case TypeJSONField, TypeJsonbField: - d = "{}" - } - - if fi.colDefault { - if !fi.initial.Exist() { - v = fmt.Sprintf(t, "") - } else { - v = fmt.Sprintf(t, fi.initial.String()) - } - } else { - if !fi.null { - v = fmt.Sprintf(t, d) - } - } - - return v -} diff --git a/client/orm/db_alias_test.go b/client/orm/db_alias_test.go deleted file mode 100644 index 6275cb2a..00000000 --- a/client/orm/db_alias_test.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2020 beego-dev -// -// Licensed 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 orm - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestRegisterDataBase(t *testing.T) { - err := RegisterDataBase("test-params", DBARGS.Driver, DBARGS.Source, - MaxIdleConnections(20), - MaxOpenConnections(300), - ConnMaxLifetime(time.Minute)) - assert.Nil(t, err) - - al := getDbAlias("test-params") - assert.NotNil(t, al) - assert.Equal(t, al.MaxIdleConns, 20) - assert.Equal(t, al.MaxOpenConns, 300) - assert.Equal(t, al.ConnMaxLifetime, time.Minute) -} - -func TestRegisterDataBase_MaxStmtCacheSizeNegative1(t *testing.T) { - aliasName := "TestRegisterDataBase_MaxStmtCacheSizeNegative1" - err := RegisterDataBase(aliasName, DBARGS.Driver, DBARGS.Source, MaxStmtCacheSize(-1)) - assert.Nil(t, err) - - al := getDbAlias(aliasName) - assert.NotNil(t, al) - assert.Equal(t, al.DB.stmtDecoratorsLimit, 0) -} - -func TestRegisterDataBase_MaxStmtCacheSize0(t *testing.T) { - aliasName := "TestRegisterDataBase_MaxStmtCacheSize0" - err := RegisterDataBase(aliasName, DBARGS.Driver, DBARGS.Source, MaxStmtCacheSize(0)) - assert.Nil(t, err) - - al := getDbAlias(aliasName) - assert.NotNil(t, al) - assert.Equal(t, al.DB.stmtDecoratorsLimit, 0) -} - -func TestRegisterDataBase_MaxStmtCacheSize1(t *testing.T) { - aliasName := "TestRegisterDataBase_MaxStmtCacheSize1" - err := RegisterDataBase(aliasName, DBARGS.Driver, DBARGS.Source, MaxStmtCacheSize(1)) - assert.Nil(t, err) - - al := getDbAlias(aliasName) - assert.NotNil(t, al) - assert.Equal(t, al.DB.stmtDecoratorsLimit, 1) -} - -func TestRegisterDataBase_MaxStmtCacheSize841(t *testing.T) { - aliasName := "TestRegisterDataBase_MaxStmtCacheSize841" - err := RegisterDataBase(aliasName, DBARGS.Driver, DBARGS.Source, MaxStmtCacheSize(841)) - assert.Nil(t, err) - - al := getDbAlias(aliasName) - assert.NotNil(t, al) - assert.Equal(t, al.DB.stmtDecoratorsLimit, 841) -} - -func TestDBCache(t *testing.T) { - dataBaseCache.add("test1", &alias{}) - dataBaseCache.add("default", &alias{}) - al := dataBaseCache.getDefault() - assert.NotNil(t, al) - al, ok := dataBaseCache.get("test1") - assert.NotNil(t, al) - assert.True(t, ok) -} diff --git a/client/orm/do_nothing_orm.go b/client/orm/do_nothing_orm.go deleted file mode 100644 index fc5b2159..00000000 --- a/client/orm/do_nothing_orm.go +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 orm - -import ( - "context" - "database/sql" - - "github.com/astaxie/beego/core/utils" -) - -// DoNothingOrm won't do anything, usually you use this to custom your mock Ormer implementation -// I think golang mocking interface is hard to use -// this may help you to integrate with Ormer - -var _ Ormer = new(DoNothingOrm) - -type DoNothingOrm struct { -} - -func (d *DoNothingOrm) Read(md interface{}, cols ...string) error { - return nil -} - -func (d *DoNothingOrm) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error { - return nil -} - -func (d *DoNothingOrm) ReadForUpdate(md interface{}, cols ...string) error { - return nil -} - -func (d *DoNothingOrm) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error { - return nil -} - -func (d *DoNothingOrm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) { - return false, 0, nil -} - -func (d *DoNothingOrm) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) { - return false, 0, nil -} - -func (d *DoNothingOrm) LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) { - return 0, nil -} - -func (d *DoNothingOrm) LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) { - return 0, nil -} - -func (d *DoNothingOrm) QueryM2M(md interface{}, name string) QueryM2Mer { - return nil -} - -func (d *DoNothingOrm) QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer { - return nil -} - -func (d *DoNothingOrm) QueryTable(ptrStructOrTableName interface{}) QuerySeter { - return nil -} - -func (d *DoNothingOrm) QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) QuerySeter { - return nil -} - -func (d *DoNothingOrm) DBStats() *sql.DBStats { - return nil -} - -func (d *DoNothingOrm) Insert(md interface{}) (int64, error) { - return 0, nil -} - -func (d *DoNothingOrm) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) { - return 0, nil -} - -func (d *DoNothingOrm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) { - return 0, nil -} - -func (d *DoNothingOrm) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) { - return 0, nil -} - -func (d *DoNothingOrm) InsertMulti(bulk int, mds interface{}) (int64, error) { - return 0, nil -} - -func (d *DoNothingOrm) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) { - return 0, nil -} - -func (d *DoNothingOrm) Update(md interface{}, cols ...string) (int64, error) { - return 0, nil -} - -func (d *DoNothingOrm) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) { - return 0, nil -} - -func (d *DoNothingOrm) Delete(md interface{}, cols ...string) (int64, error) { - return 0, nil -} - -func (d *DoNothingOrm) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) { - return 0, nil -} - -func (d *DoNothingOrm) Raw(query string, args ...interface{}) RawSeter { - return nil -} - -func (d *DoNothingOrm) RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter { - return nil -} - -func (d *DoNothingOrm) Driver() Driver { - return nil -} - -func (d *DoNothingOrm) Begin() (TxOrmer, error) { - return nil, nil -} - -func (d *DoNothingOrm) BeginWithCtx(ctx context.Context) (TxOrmer, error) { - return nil, nil -} - -func (d *DoNothingOrm) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) { - return nil, nil -} - -func (d *DoNothingOrm) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) { - return nil, nil -} - -func (d *DoNothingOrm) DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error { - return nil -} - -func (d *DoNothingOrm) DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error { - return nil -} - -func (d *DoNothingOrm) DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error { - return nil -} - -func (d *DoNothingOrm) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error { - return nil -} - -// DoNothingTxOrm is similar with DoNothingOrm, usually you use it to test -type DoNothingTxOrm struct { - DoNothingOrm -} - -func (d *DoNothingTxOrm) Commit() error { - return nil -} - -func (d *DoNothingTxOrm) Rollback() error { - return nil -} diff --git a/client/orm/do_nothing_orm_test.go b/client/orm/do_nothing_orm_test.go deleted file mode 100644 index 4d477353..00000000 --- a/client/orm/do_nothing_orm_test.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 orm - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestDoNothingOrm(t *testing.T) { - o := &DoNothingOrm{} - err := o.DoTxWithCtxAndOpts(nil, nil, nil) - assert.Nil(t, err) - - err = o.DoTxWithCtx(nil, nil) - assert.Nil(t, err) - - err = o.DoTx(nil) - assert.Nil(t, err) - - err = o.DoTxWithOpts(nil, nil) - assert.Nil(t, err) - - assert.Nil(t, o.Driver()) - - assert.Nil(t, o.QueryM2MWithCtx(nil, nil, "")) - assert.Nil(t, o.QueryM2M(nil, "")) - assert.Nil(t, o.ReadWithCtx(nil, nil)) - assert.Nil(t, o.Read(nil)) - - txOrm, err := o.BeginWithCtxAndOpts(nil, nil) - assert.Nil(t, err) - assert.Nil(t, txOrm) - - txOrm, err = o.BeginWithCtx(nil) - assert.Nil(t, err) - assert.Nil(t, txOrm) - - txOrm, err = o.BeginWithOpts(nil) - assert.Nil(t, err) - assert.Nil(t, txOrm) - - txOrm, err = o.Begin() - assert.Nil(t, err) - assert.Nil(t, txOrm) - - assert.Nil(t, o.RawWithCtx(nil, "")) - assert.Nil(t, o.Raw("")) - - i, err := o.InsertMulti(0, nil) - assert.Nil(t, err) - assert.Equal(t, int64(0), i) - - i, err = o.Insert(nil) - assert.Nil(t, err) - assert.Equal(t, int64(0), i) - - i, err = o.InsertWithCtx(nil, nil) - assert.Nil(t, err) - assert.Equal(t, int64(0), i) - - i, err = o.InsertOrUpdateWithCtx(nil, nil) - assert.Nil(t, err) - assert.Equal(t, int64(0), i) - - i, err = o.InsertOrUpdate(nil) - assert.Nil(t, err) - assert.Equal(t, int64(0), i) - - i, err = o.InsertMultiWithCtx(nil, 0, nil) - assert.Nil(t, err) - assert.Equal(t, int64(0), i) - - i, err = o.LoadRelatedWithCtx(nil, nil, "") - assert.Nil(t, err) - assert.Equal(t, int64(0), i) - - i, err = o.LoadRelated(nil, "") - assert.Nil(t, err) - assert.Equal(t, int64(0), i) - - assert.Nil(t, o.QueryTableWithCtx(nil, nil)) - assert.Nil(t, o.QueryTable(nil)) - - assert.Nil(t, o.Read(nil)) - assert.Nil(t, o.ReadWithCtx(nil, nil)) - assert.Nil(t, o.ReadForUpdateWithCtx(nil, nil)) - assert.Nil(t, o.ReadForUpdate(nil)) - - ok, i, err := o.ReadOrCreate(nil, "") - assert.Nil(t, err) - assert.Equal(t, int64(0), i) - assert.False(t, ok) - - ok, i, err = o.ReadOrCreateWithCtx(nil, nil, "") - assert.Nil(t, err) - assert.Equal(t, int64(0), i) - assert.False(t, ok) - - i, err = o.Delete(nil) - assert.Nil(t, err) - assert.Equal(t, int64(0), i) - - i, err = o.DeleteWithCtx(nil, nil) - assert.Nil(t, err) - assert.Equal(t, int64(0), i) - - i, err = o.Update(nil) - assert.Nil(t, err) - assert.Equal(t, int64(0), i) - - i, err = o.UpdateWithCtx(nil, nil) - assert.Nil(t, err) - assert.Equal(t, int64(0), i) - - assert.Nil(t, o.DBStats()) - - to := &DoNothingTxOrm{} - assert.Nil(t, to.Commit()) - assert.Nil(t, to.Rollback()) -} diff --git a/client/orm/filter.go b/client/orm/filter.go deleted file mode 100644 index bc13c3fa..00000000 --- a/client/orm/filter.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 orm - -import ( - "context" -) - -// FilterChain is used to build a Filter -// don't forget to call next(...) inside your Filter -type FilterChain func(next Filter) Filter - -// Filter's behavior is a little big strange. -// it's only be called when users call methods of Ormer -// return value is an array. it's a little bit hard to understand, -// for example, the Ormer's Read method only return error -// so the filter processing this method should return an array whose first element is error -// and, Ormer's ReadOrCreateWithCtx return three values, so the Filter's result should contains three values -type Filter func(ctx context.Context, inv *Invocation) []interface{} - -var globalFilterChains = make([]FilterChain, 0, 4) - -// AddGlobalFilterChain adds a new FilterChain -// All orm instances built after this invocation will use this filterChain, -// but instances built before this invocation will not be affected -func AddGlobalFilterChain(filterChain ...FilterChain) { - globalFilterChains = append(globalFilterChains, filterChain...) -} diff --git a/client/orm/filter/bean/default_value_filter.go b/client/orm/filter/bean/default_value_filter.go deleted file mode 100644 index 3dac5c74..00000000 --- a/client/orm/filter/bean/default_value_filter.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 bean - -import ( - "context" - "reflect" - "strings" - - "github.com/astaxie/beego/core/logs" - - "github.com/astaxie/beego/client/orm" - "github.com/astaxie/beego/core/bean" -) - -// DefaultValueFilterChainBuilder only works for InsertXXX method, -// But InsertOrUpdate and InsertOrUpdateWithCtx is more dangerous than other methods. -// so we won't handle those two methods unless you set includeInsertOrUpdate to true -// And if the element is not pointer, this filter doesn't work -type DefaultValueFilterChainBuilder struct { - factory bean.AutoWireBeanFactory - compatibleWithOldStyle bool - - // only the includeInsertOrUpdate is true, this filter will handle those two methods - includeInsertOrUpdate bool -} - -// NewDefaultValueFilterChainBuilder will create an instance of DefaultValueFilterChainBuilder -// In beego v1.x, the default value config looks like orm:default(xxxx) -// But the default value in 2.x is default:xxx -// so if you want to be compatible with v1.x, please pass true as compatibleWithOldStyle -func NewDefaultValueFilterChainBuilder(typeAdapters map[string]bean.TypeAdapter, - includeInsertOrUpdate bool, - compatibleWithOldStyle bool) *DefaultValueFilterChainBuilder { - factory := bean.NewTagAutoWireBeanFactory() - - if compatibleWithOldStyle { - newParser := factory.FieldTagParser - factory.FieldTagParser = func(field reflect.StructField) *bean.FieldMetadata { - if newParser != nil && field.Tag.Get(bean.DefaultValueTagKey) != "" { - return newParser(field) - } else { - res := &bean.FieldMetadata{} - ormMeta := field.Tag.Get("orm") - ormMetaParts := strings.Split(ormMeta, ";") - for _, p := range ormMetaParts { - if strings.HasPrefix(p, "default(") && strings.HasSuffix(p, ")") { - res.DftValue = p[8 : len(p)-1] - } - } - return res - } - } - } - - for k, v := range typeAdapters { - factory.Adapters[k] = v - } - - return &DefaultValueFilterChainBuilder{ - factory: factory, - compatibleWithOldStyle: compatibleWithOldStyle, - includeInsertOrUpdate: includeInsertOrUpdate, - } -} - -func (d *DefaultValueFilterChainBuilder) FilterChain(next orm.Filter) orm.Filter { - return func(ctx context.Context, inv *orm.Invocation) []interface{} { - switch inv.Method { - case "Insert", "InsertWithCtx": - d.handleInsert(ctx, inv) - break - case "InsertOrUpdate", "InsertOrUpdateWithCtx": - d.handleInsertOrUpdate(ctx, inv) - break - case "InsertMulti", "InsertMultiWithCtx": - d.handleInsertMulti(ctx, inv) - break - } - return next(ctx, inv) - } -} - -func (d *DefaultValueFilterChainBuilder) handleInsert(ctx context.Context, inv *orm.Invocation) { - d.setDefaultValue(ctx, inv.Args[0]) -} - -func (d *DefaultValueFilterChainBuilder) handleInsertOrUpdate(ctx context.Context, inv *orm.Invocation) { - if d.includeInsertOrUpdate { - ins := inv.Args[0] - if ins == nil { - return - } - - pkName := inv.GetPkFieldName() - pkField := reflect.Indirect(reflect.ValueOf(ins)).FieldByName(pkName) - - if pkField.IsZero() { - d.setDefaultValue(ctx, ins) - } - } -} - -func (d *DefaultValueFilterChainBuilder) handleInsertMulti(ctx context.Context, inv *orm.Invocation) { - mds := inv.Args[1] - - if t := reflect.TypeOf(mds).Kind(); t != reflect.Array && t != reflect.Slice { - // do nothing - return - } - - mdsArr := reflect.Indirect(reflect.ValueOf(mds)) - for i := 0; i < mdsArr.Len(); i++ { - d.setDefaultValue(ctx, mdsArr.Index(i).Interface()) - } - logs.Warn("%v", mdsArr.Index(0).Interface()) -} - -func (d *DefaultValueFilterChainBuilder) setDefaultValue(ctx context.Context, ins interface{}) { - err := d.factory.AutoWire(ctx, nil, ins) - if err != nil { - logs.Error("try to wire the bean for orm.Insert failed. "+ - "the default value is not set: %v, ", err) - } -} diff --git a/client/orm/filter/bean/default_value_filter_test.go b/client/orm/filter/bean/default_value_filter_test.go deleted file mode 100644 index 2a6ed1f4..00000000 --- a/client/orm/filter/bean/default_value_filter_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 bean - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/client/orm" -) - -func TestDefaultValueFilterChainBuilder_FilterChain(t *testing.T) { - builder := NewDefaultValueFilterChainBuilder(nil, true, true) - o := orm.NewFilterOrmDecorator(&defaultValueTestOrm{}, builder.FilterChain) - - // test insert - entity := &DefaultValueTestEntity{} - _, _ = o.Insert(entity) - assert.Equal(t, 12, entity.Age) - assert.Equal(t, 13, entity.AgeInOldStyle) - assert.Equal(t, 0, entity.AgeIgnore) - - // test InsertOrUpdate - entity = &DefaultValueTestEntity{} - orm.RegisterModel(entity) - - _, _ = o.InsertOrUpdate(entity) - assert.Equal(t, 12, entity.Age) - assert.Equal(t, 13, entity.AgeInOldStyle) - - // we won't set the default value because we find the pk is not Zero value - entity.Id = 3 - entity.AgeInOldStyle = 0 - _, _ = o.InsertOrUpdate(entity) - assert.Equal(t, 0, entity.AgeInOldStyle) - - entity = &DefaultValueTestEntity{} - - // the entity is not array, it will be ignored - _, _ = o.InsertMulti(3, entity) - assert.Equal(t, 0, entity.Age) - assert.Equal(t, 0, entity.AgeInOldStyle) - - _, _ = o.InsertMulti(3, []*DefaultValueTestEntity{entity}) - assert.Equal(t, 12, entity.Age) - assert.Equal(t, 13, entity.AgeInOldStyle) - -} - -type defaultValueTestOrm struct { - orm.DoNothingOrm -} - -type DefaultValueTestEntity struct { - Id int - Age int `default:"12"` - AgeInOldStyle int `orm:"default(13);bee()"` - AgeIgnore int -} diff --git a/client/orm/filter/opentracing/filter.go b/client/orm/filter/opentracing/filter.go deleted file mode 100644 index 7f9658b4..00000000 --- a/client/orm/filter/opentracing/filter.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 opentracing - -import ( - "context" - "strings" - - "github.com/opentracing/opentracing-go" - - "github.com/astaxie/beego/client/orm" -) - -// FilterChainBuilder provides an extension point -// this Filter's behavior looks a little bit strange -// for example: -// if we want to trace QuerySetter -// actually we trace invoking "QueryTable" and "QueryTableWithCtx" -// the method Begin*, Commit and Rollback are ignored. -// When use using those methods, it means that they want to manager their transaction manually, so we won't handle them. -type FilterChainBuilder struct { - // CustomSpanFunc users are able to custom their span - CustomSpanFunc func(span opentracing.Span, ctx context.Context, inv *orm.Invocation) -} - -func (builder *FilterChainBuilder) FilterChain(next orm.Filter) orm.Filter { - return func(ctx context.Context, inv *orm.Invocation) []interface{} { - operationName := builder.operationName(ctx, inv) - if strings.HasPrefix(inv.Method, "Begin") || inv.Method == "Commit" || inv.Method == "Rollback" { - return next(ctx, inv) - } - - span, spanCtx := opentracing.StartSpanFromContext(ctx, operationName) - defer span.Finish() - res := next(spanCtx, inv) - builder.buildSpan(span, spanCtx, inv) - return res - } -} - -func (builder *FilterChainBuilder) buildSpan(span opentracing.Span, ctx context.Context, inv *orm.Invocation) { - span.SetTag("orm.method", inv.Method) - span.SetTag("orm.table", inv.GetTableName()) - span.SetTag("orm.insideTx", inv.InsideTx) - span.SetTag("orm.txName", ctx.Value(orm.TxNameKey)) - span.SetTag("span.kind", "client") - span.SetTag("component", "beego") - - if builder.CustomSpanFunc != nil { - builder.CustomSpanFunc(span, ctx, inv) - } -} - -func (builder *FilterChainBuilder) operationName(ctx context.Context, inv *orm.Invocation) string { - if n, ok := ctx.Value(orm.TxNameKey).(string); ok { - return inv.Method + "#tx(" + n + ")" - } - return inv.Method + "#" + inv.GetTableName() -} diff --git a/client/orm/filter/opentracing/filter_test.go b/client/orm/filter/opentracing/filter_test.go deleted file mode 100644 index 428dacda..00000000 --- a/client/orm/filter/opentracing/filter_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 opentracing - -import ( - "context" - "testing" - "time" - - "github.com/opentracing/opentracing-go" - - "github.com/astaxie/beego/client/orm" -) - -func TestFilterChainBuilder_FilterChain(t *testing.T) { - next := func(ctx context.Context, inv *orm.Invocation) []interface{} { - inv.TxName = "Hello" - return []interface{}{} - } - - builder := &FilterChainBuilder{ - CustomSpanFunc: func(span opentracing.Span, ctx context.Context, inv *orm.Invocation) { - span.SetTag("hello", "hell") - }, - } - - inv := &orm.Invocation{ - Method: "Hello", - TxStartTime: time.Now(), - } - builder.FilterChain(next)(context.Background(), inv) -} diff --git a/client/orm/filter/prometheus/filter.go b/client/orm/filter/prometheus/filter.go deleted file mode 100644 index e74e946a..00000000 --- a/client/orm/filter/prometheus/filter.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 prometheus - -import ( - "context" - "strconv" - "strings" - "time" - - "github.com/prometheus/client_golang/prometheus" - - "github.com/astaxie/beego/client/orm" -) - -// FilterChainBuilder is an extension point, -// when we want to support some configuration, -// please use this structure -// this Filter's behavior looks a little bit strange -// for example: -// if we want to records the metrics of QuerySetter -// actually we only records metrics of invoking "QueryTable" and "QueryTableWithCtx" -type FilterChainBuilder struct { - summaryVec prometheus.ObserverVec - AppName string - ServerName string - RunMode string -} - -func (builder *FilterChainBuilder) FilterChain(next orm.Filter) orm.Filter { - - builder.summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{ - Name: "beego", - Subsystem: "orm_operation", - ConstLabels: map[string]string{ - "server": builder.ServerName, - "env": builder.RunMode, - "appname": builder.AppName, - }, - Help: "The statics info for orm operation", - }, []string{"method", "name", "duration", "insideTx", "txName"}) - - return func(ctx context.Context, inv *orm.Invocation) []interface{} { - startTime := time.Now() - res := next(ctx, inv) - endTime := time.Now() - dur := (endTime.Sub(startTime)) / time.Millisecond - - // if the TPS is too large, here may be some problem - // thinking about using goroutine pool - go builder.report(ctx, inv, dur) - return res - } -} - -func (builder *FilterChainBuilder) report(ctx context.Context, inv *orm.Invocation, dur time.Duration) { - // start a transaction, we don't record it - if strings.HasPrefix(inv.Method, "Begin") { - return - } - if inv.Method == "Commit" || inv.Method == "Rollback" { - builder.reportTxn(ctx, inv) - return - } - builder.summaryVec.WithLabelValues(inv.Method, inv.GetTableName(), strconv.Itoa(int(dur)), - strconv.FormatBool(inv.InsideTx), inv.TxName) -} - -func (builder *FilterChainBuilder) reportTxn(ctx context.Context, inv *orm.Invocation) { - dur := time.Now().Sub(inv.TxStartTime) / time.Millisecond - builder.summaryVec.WithLabelValues(inv.Method, inv.TxName, strconv.Itoa(int(dur)), - strconv.FormatBool(inv.InsideTx), inv.TxName) -} diff --git a/client/orm/filter/prometheus/filter_test.go b/client/orm/filter/prometheus/filter_test.go deleted file mode 100644 index 72b16038..00000000 --- a/client/orm/filter/prometheus/filter_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 prometheus - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/client/orm" -) - -func TestFilterChainBuilder_FilterChain1(t *testing.T) { - next := func(ctx context.Context, inv *orm.Invocation) []interface{} { - inv.Method = "coming" - return []interface{}{} - } - builder := &FilterChainBuilder{} - filter := builder.FilterChain(next) - - assert.NotNil(t, builder.summaryVec) - assert.NotNil(t, filter) - - inv := &orm.Invocation{} - filter(context.Background(), inv) - assert.Equal(t, "coming", inv.Method) - - inv = &orm.Invocation{ - Method: "Hello", - TxStartTime: time.Now(), - } - builder.reportTxn(context.Background(), inv) - - inv = &orm.Invocation{ - Method: "Begin", - } - - ctx := context.Background() - // it will be ignored - builder.report(ctx, inv, time.Second) - - inv.Method = "Commit" - builder.report(ctx, inv, time.Second) - - inv.Method = "Update" - builder.report(ctx, inv, time.Second) - -} diff --git a/client/orm/filter_orm_decorator.go b/client/orm/filter_orm_decorator.go deleted file mode 100644 index 9f837cba..00000000 --- a/client/orm/filter_orm_decorator.go +++ /dev/null @@ -1,514 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 orm - -import ( - "context" - "database/sql" - "reflect" - "time" - - "github.com/astaxie/beego/core/utils" -) - -const ( - TxNameKey = "TxName" -) - -var _ Ormer = new(filterOrmDecorator) -var _ TxOrmer = new(filterOrmDecorator) - -type filterOrmDecorator struct { - ormer - TxBeginner - TxCommitter - - root Filter - - insideTx bool - txStartTime time.Time - txName string -} - -func NewFilterOrmDecorator(delegate Ormer, filterChains ...FilterChain) Ormer { - res := &filterOrmDecorator{ - ormer: delegate, - TxBeginner: delegate, - root: func(ctx context.Context, inv *Invocation) []interface{} { - return inv.execute(ctx) - }, - } - - for i := len(filterChains) - 1; i >= 0; i-- { - node := filterChains[i] - res.root = node(res.root) - } - return res -} - -func NewFilterTxOrmDecorator(delegate TxOrmer, root Filter, txName string) TxOrmer { - res := &filterOrmDecorator{ - ormer: delegate, - TxCommitter: delegate, - root: root, - insideTx: true, - txStartTime: time.Now(), - txName: txName, - } - return res -} - -func (f *filterOrmDecorator) Read(md interface{}, cols ...string) error { - return f.ReadWithCtx(context.Background(), md, cols...) -} - -func (f *filterOrmDecorator) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error { - mi, _ := modelCache.getByMd(md) - inv := &Invocation{ - Method: "ReadWithCtx", - Args: []interface{}{md, cols}, - Md: md, - mi: mi, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - f: func(c context.Context) []interface{} { - err := f.ormer.ReadWithCtx(c, md, cols...) - return []interface{}{err} - }, - } - res := f.root(ctx, inv) - return f.convertError(res[0]) -} - -func (f *filterOrmDecorator) ReadForUpdate(md interface{}, cols ...string) error { - return f.ReadForUpdateWithCtx(context.Background(), md, cols...) -} - -func (f *filterOrmDecorator) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error { - mi, _ := modelCache.getByMd(md) - inv := &Invocation{ - Method: "ReadForUpdateWithCtx", - Args: []interface{}{md, cols}, - Md: md, - mi: mi, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - f: func(c context.Context) []interface{} { - err := f.ormer.ReadForUpdateWithCtx(c, md, cols...) - return []interface{}{err} - }, - } - res := f.root(ctx, inv) - return f.convertError(res[0]) -} - -func (f *filterOrmDecorator) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) { - return f.ReadOrCreateWithCtx(context.Background(), md, col1, cols...) -} - -func (f *filterOrmDecorator) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) { - - mi, _ := modelCache.getByMd(md) - inv := &Invocation{ - Method: "ReadOrCreateWithCtx", - Args: []interface{}{md, col1, cols}, - Md: md, - mi: mi, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - f: func(c context.Context) []interface{} { - ok, res, err := f.ormer.ReadOrCreateWithCtx(c, md, col1, cols...) - return []interface{}{ok, res, err} - }, - } - res := f.root(ctx, inv) - return res[0].(bool), res[1].(int64), f.convertError(res[2]) -} - -func (f *filterOrmDecorator) LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) { - return f.LoadRelatedWithCtx(context.Background(), md, name, args...) -} - -func (f *filterOrmDecorator) LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) { - - mi, _ := modelCache.getByMd(md) - inv := &Invocation{ - Method: "LoadRelatedWithCtx", - Args: []interface{}{md, name, args}, - Md: md, - mi: mi, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - f: func(c context.Context) []interface{} { - res, err := f.ormer.LoadRelatedWithCtx(c, md, name, args...) - return []interface{}{res, err} - }, - } - res := f.root(ctx, inv) - return res[0].(int64), f.convertError(res[1]) -} - -func (f *filterOrmDecorator) QueryM2M(md interface{}, name string) QueryM2Mer { - return f.QueryM2MWithCtx(context.Background(), md, name) -} - -func (f *filterOrmDecorator) QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer { - - mi, _ := modelCache.getByMd(md) - inv := &Invocation{ - Method: "QueryM2MWithCtx", - Args: []interface{}{md, name}, - Md: md, - mi: mi, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - f: func(c context.Context) []interface{} { - res := f.ormer.QueryM2MWithCtx(c, md, name) - return []interface{}{res} - }, - } - res := f.root(ctx, inv) - if res[0] == nil { - return nil - } - return res[0].(QueryM2Mer) -} - -func (f *filterOrmDecorator) QueryTable(ptrStructOrTableName interface{}) QuerySeter { - return f.QueryTableWithCtx(context.Background(), ptrStructOrTableName) -} - -func (f *filterOrmDecorator) QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) QuerySeter { - var ( - name string - md interface{} - mi *modelInfo - ) - - if table, ok := ptrStructOrTableName.(string); ok { - name = table - } else { - name = getFullName(indirectType(reflect.TypeOf(ptrStructOrTableName))) - md = ptrStructOrTableName - } - - if m, ok := modelCache.getByFullName(name); ok { - mi = m - } - - inv := &Invocation{ - Method: "QueryTableWithCtx", - Args: []interface{}{ptrStructOrTableName}, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - Md: md, - mi: mi, - f: func(c context.Context) []interface{} { - res := f.ormer.QueryTableWithCtx(c, ptrStructOrTableName) - return []interface{}{res} - }, - } - res := f.root(ctx, inv) - - if res[0] == nil { - return nil - } - return res[0].(QuerySeter) -} - -func (f *filterOrmDecorator) DBStats() *sql.DBStats { - inv := &Invocation{ - Method: "DBStats", - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - f: func(c context.Context) []interface{} { - res := f.ormer.DBStats() - return []interface{}{res} - }, - } - res := f.root(context.Background(), inv) - - if res[0] == nil { - return nil - } - - return res[0].(*sql.DBStats) -} - -func (f *filterOrmDecorator) Insert(md interface{}) (int64, error) { - return f.InsertWithCtx(context.Background(), md) -} - -func (f *filterOrmDecorator) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) { - mi, _ := modelCache.getByMd(md) - inv := &Invocation{ - Method: "InsertWithCtx", - Args: []interface{}{md}, - Md: md, - mi: mi, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - f: func(c context.Context) []interface{} { - res, err := f.ormer.InsertWithCtx(c, md) - return []interface{}{res, err} - }, - } - res := f.root(ctx, inv) - return res[0].(int64), f.convertError(res[1]) -} - -func (f *filterOrmDecorator) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) { - return f.InsertOrUpdateWithCtx(context.Background(), md, colConflitAndArgs...) -} - -func (f *filterOrmDecorator) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) { - mi, _ := modelCache.getByMd(md) - inv := &Invocation{ - Method: "InsertOrUpdateWithCtx", - Args: []interface{}{md, colConflitAndArgs}, - Md: md, - mi: mi, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - f: func(c context.Context) []interface{} { - res, err := f.ormer.InsertOrUpdateWithCtx(c, md, colConflitAndArgs...) - return []interface{}{res, err} - }, - } - res := f.root(ctx, inv) - return res[0].(int64), f.convertError(res[1]) -} - -func (f *filterOrmDecorator) InsertMulti(bulk int, mds interface{}) (int64, error) { - return f.InsertMultiWithCtx(context.Background(), bulk, mds) -} - -// InsertMultiWithCtx uses the first element's model info -func (f *filterOrmDecorator) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) { - var ( - md interface{} - mi *modelInfo - ) - - sind := reflect.Indirect(reflect.ValueOf(mds)) - - if (sind.Kind() == reflect.Array || sind.Kind() == reflect.Slice) && sind.Len() > 0 { - ind := reflect.Indirect(sind.Index(0)) - md = ind.Interface() - mi, _ = modelCache.getByMd(md) - } - - inv := &Invocation{ - Method: "InsertMultiWithCtx", - Args: []interface{}{bulk, mds}, - Md: md, - mi: mi, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - f: func(c context.Context) []interface{} { - res, err := f.ormer.InsertMultiWithCtx(c, bulk, mds) - return []interface{}{res, err} - }, - } - res := f.root(ctx, inv) - return res[0].(int64), f.convertError(res[1]) -} - -func (f *filterOrmDecorator) Update(md interface{}, cols ...string) (int64, error) { - return f.UpdateWithCtx(context.Background(), md, cols...) -} - -func (f *filterOrmDecorator) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) { - mi, _ := modelCache.getByMd(md) - inv := &Invocation{ - Method: "UpdateWithCtx", - Args: []interface{}{md, cols}, - Md: md, - mi: mi, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - f: func(c context.Context) []interface{} { - res, err := f.ormer.UpdateWithCtx(c, md, cols...) - return []interface{}{res, err} - }, - } - res := f.root(ctx, inv) - return res[0].(int64), f.convertError(res[1]) -} - -func (f *filterOrmDecorator) Delete(md interface{}, cols ...string) (int64, error) { - return f.DeleteWithCtx(context.Background(), md, cols...) -} - -func (f *filterOrmDecorator) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) { - mi, _ := modelCache.getByMd(md) - inv := &Invocation{ - Method: "DeleteWithCtx", - Args: []interface{}{md, cols}, - Md: md, - mi: mi, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - f: func(c context.Context) []interface{} { - res, err := f.ormer.DeleteWithCtx(c, md, cols...) - return []interface{}{res, err} - }, - } - res := f.root(ctx, inv) - return res[0].(int64), f.convertError(res[1]) -} - -func (f *filterOrmDecorator) Raw(query string, args ...interface{}) RawSeter { - return f.RawWithCtx(context.Background(), query, args...) -} - -func (f *filterOrmDecorator) RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter { - inv := &Invocation{ - Method: "RawWithCtx", - Args: []interface{}{query, args}, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - f: func(c context.Context) []interface{} { - res := f.ormer.RawWithCtx(c, query, args...) - return []interface{}{res} - }, - } - res := f.root(ctx, inv) - - if res[0] == nil { - return nil - } - return res[0].(RawSeter) -} - -func (f *filterOrmDecorator) Driver() Driver { - inv := &Invocation{ - Method: "Driver", - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - f: func(c context.Context) []interface{} { - res := f.ormer.Driver() - return []interface{}{res} - }, - } - res := f.root(context.Background(), inv) - if res[0] == nil { - return nil - } - return res[0].(Driver) -} - -func (f *filterOrmDecorator) Begin() (TxOrmer, error) { - return f.BeginWithCtxAndOpts(context.Background(), nil) -} - -func (f *filterOrmDecorator) BeginWithCtx(ctx context.Context) (TxOrmer, error) { - return f.BeginWithCtxAndOpts(ctx, nil) -} - -func (f *filterOrmDecorator) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) { - return f.BeginWithCtxAndOpts(context.Background(), opts) -} - -func (f *filterOrmDecorator) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) { - inv := &Invocation{ - Method: "BeginWithCtxAndOpts", - Args: []interface{}{opts}, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - f: func(c context.Context) []interface{} { - res, err := f.TxBeginner.BeginWithCtxAndOpts(c, opts) - res = NewFilterTxOrmDecorator(res, f.root, getTxNameFromCtx(c)) - return []interface{}{res, err} - }, - } - res := f.root(ctx, inv) - return res[0].(TxOrmer), f.convertError(res[1]) -} - -func (f *filterOrmDecorator) DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error { - return f.DoTxWithCtxAndOpts(context.Background(), nil, task) -} - -func (f *filterOrmDecorator) DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error { - return f.DoTxWithCtxAndOpts(ctx, nil, task) -} - -func (f *filterOrmDecorator) DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error { - return f.DoTxWithCtxAndOpts(context.Background(), opts, task) -} - -func (f *filterOrmDecorator) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error { - inv := &Invocation{ - Method: "DoTxWithCtxAndOpts", - Args: []interface{}{opts, task}, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - TxName: getTxNameFromCtx(ctx), - f: func(c context.Context) []interface{} { - err := doTxTemplate(f, c, opts, task) - return []interface{}{err} - }, - } - res := f.root(ctx, inv) - return f.convertError(res[0]) -} - -func (f *filterOrmDecorator) Commit() error { - inv := &Invocation{ - Method: "Commit", - Args: []interface{}{}, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - TxName: f.txName, - f: func(c context.Context) []interface{} { - err := f.TxCommitter.Commit() - return []interface{}{err} - }, - } - res := f.root(context.Background(), inv) - return f.convertError(res[0]) -} - -func (f *filterOrmDecorator) Rollback() error { - inv := &Invocation{ - Method: "Rollback", - Args: []interface{}{}, - InsideTx: f.insideTx, - TxStartTime: f.txStartTime, - TxName: f.txName, - f: func(c context.Context) []interface{} { - err := f.TxCommitter.Rollback() - return []interface{}{err} - }, - } - res := f.root(context.Background(), inv) - return f.convertError(res[0]) -} - -func (f *filterOrmDecorator) convertError(v interface{}) error { - if v == nil { - return nil - } - return v.(error) -} - -func getTxNameFromCtx(ctx context.Context) string { - txName := "" - if n, ok := ctx.Value(TxNameKey).(string); ok { - txName = n - } - return txName -} diff --git a/client/orm/filter_orm_decorator_test.go b/client/orm/filter_orm_decorator_test.go deleted file mode 100644 index 671ca001..00000000 --- a/client/orm/filter_orm_decorator_test.go +++ /dev/null @@ -1,434 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 orm - -import ( - "context" - "database/sql" - "errors" - "sync" - "testing" - - "github.com/astaxie/beego/core/utils" - - "github.com/stretchr/testify/assert" -) - -func TestFilterOrmDecorator_Read(t *testing.T) { - - register() - - o := &filterMockOrm{} - od := NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - assert.Equal(t, "ReadWithCtx", inv.Method) - assert.Equal(t, 2, len(inv.Args)) - assert.Equal(t, "FILTER_TEST", inv.GetTableName()) - return next(ctx, inv) - } - }) - - fte := &FilterTestEntity{} - err := od.Read(fte) - assert.NotNil(t, err) - assert.Equal(t, "read error", err.Error()) -} - -func TestFilterOrmDecorator_BeginTx(t *testing.T) { - register() - - o := &filterMockOrm{} - od := NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - if inv.Method == "BeginWithCtxAndOpts" { - assert.Equal(t, 1, len(inv.Args)) - assert.Equal(t, "", inv.GetTableName()) - assert.False(t, inv.InsideTx) - } else if inv.Method == "Commit" { - assert.Equal(t, 0, len(inv.Args)) - assert.Equal(t, "Commit_tx", inv.TxName) - assert.Equal(t, "", inv.GetTableName()) - assert.True(t, inv.InsideTx) - } else if inv.Method == "Rollback" { - assert.Equal(t, 0, len(inv.Args)) - assert.Equal(t, "Rollback_tx", inv.TxName) - assert.Equal(t, "", inv.GetTableName()) - assert.True(t, inv.InsideTx) - } else { - t.Fail() - } - - return next(ctx, inv) - } - }) - to, err := od.Begin() - assert.True(t, validateBeginResult(t, to, err)) - - to, err = od.BeginWithOpts(nil) - assert.True(t, validateBeginResult(t, to, err)) - - ctx := context.WithValue(context.Background(), TxNameKey, "Commit_tx") - to, err = od.BeginWithCtx(ctx) - assert.True(t, validateBeginResult(t, to, err)) - - err = to.Commit() - assert.NotNil(t, err) - assert.Equal(t, "commit", err.Error()) - - ctx = context.WithValue(context.Background(), TxNameKey, "Rollback_tx") - to, err = od.BeginWithCtxAndOpts(ctx, nil) - assert.True(t, validateBeginResult(t, to, err)) - - err = to.Rollback() - assert.NotNil(t, err) - assert.Equal(t, "rollback", err.Error()) -} - -func TestFilterOrmDecorator_DBStats(t *testing.T) { - o := &filterMockOrm{} - od := NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - assert.Equal(t, "DBStats", inv.Method) - assert.Equal(t, 0, len(inv.Args)) - assert.Equal(t, "", inv.GetTableName()) - return next(ctx, inv) - } - }) - res := od.DBStats() - assert.NotNil(t, res) - assert.Equal(t, -1, res.MaxOpenConnections) -} - -func TestFilterOrmDecorator_Delete(t *testing.T) { - register() - o := &filterMockOrm{} - od := NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - assert.Equal(t, "DeleteWithCtx", inv.Method) - assert.Equal(t, 2, len(inv.Args)) - assert.Equal(t, "FILTER_TEST", inv.GetTableName()) - return next(ctx, inv) - } - }) - res, err := od.Delete(&FilterTestEntity{}) - assert.NotNil(t, err) - assert.Equal(t, "delete error", err.Error()) - assert.Equal(t, int64(-2), res) -} - -func TestFilterOrmDecorator_DoTx(t *testing.T) { - o := &filterMockOrm{} - od := NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - if inv.Method == "DoTxWithCtxAndOpts" { - assert.Equal(t, 2, len(inv.Args)) - assert.Equal(t, "", inv.GetTableName()) - assert.False(t, inv.InsideTx) - } - return next(ctx, inv) - } - }) - - err := od.DoTx(func(c context.Context, txOrm TxOrmer) error { - return nil - }) - assert.NotNil(t, err) - - err = od.DoTxWithCtx(context.Background(), func(c context.Context, txOrm TxOrmer) error { - return nil - }) - assert.NotNil(t, err) - - err = od.DoTxWithOpts(nil, func(c context.Context, txOrm TxOrmer) error { - return nil - }) - assert.NotNil(t, err) - - od = NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - if inv.Method == "DoTxWithCtxAndOpts" { - assert.Equal(t, 2, len(inv.Args)) - assert.Equal(t, "", inv.GetTableName()) - assert.Equal(t, "do tx name", inv.TxName) - assert.False(t, inv.InsideTx) - } - return next(ctx, inv) - } - }) - - ctx := context.WithValue(context.Background(), TxNameKey, "do tx name") - err = od.DoTxWithCtxAndOpts(ctx, nil, func(c context.Context, txOrm TxOrmer) error { - return nil - }) - assert.NotNil(t, err) -} - -func TestFilterOrmDecorator_Driver(t *testing.T) { - o := &filterMockOrm{} - od := NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - assert.Equal(t, "Driver", inv.Method) - assert.Equal(t, 0, len(inv.Args)) - assert.Equal(t, "", inv.GetTableName()) - assert.False(t, inv.InsideTx) - return next(ctx, inv) - } - }) - res := od.Driver() - assert.Nil(t, res) -} - -func TestFilterOrmDecorator_Insert(t *testing.T) { - register() - o := &filterMockOrm{} - od := NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - assert.Equal(t, "InsertWithCtx", inv.Method) - assert.Equal(t, 1, len(inv.Args)) - assert.Equal(t, "FILTER_TEST", inv.GetTableName()) - assert.False(t, inv.InsideTx) - return next(ctx, inv) - } - }) - - i, err := od.Insert(&FilterTestEntity{}) - assert.NotNil(t, err) - assert.Equal(t, "insert error", err.Error()) - assert.Equal(t, int64(100), i) -} - -func TestFilterOrmDecorator_InsertMulti(t *testing.T) { - register() - o := &filterMockOrm{} - od := NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - assert.Equal(t, "InsertMultiWithCtx", inv.Method) - assert.Equal(t, 2, len(inv.Args)) - assert.Equal(t, "FILTER_TEST", inv.GetTableName()) - assert.False(t, inv.InsideTx) - return next(ctx, inv) - } - }) - - bulk := []*FilterTestEntity{&FilterTestEntity{}, &FilterTestEntity{}} - i, err := od.InsertMulti(2, bulk) - assert.NotNil(t, err) - assert.Equal(t, "insert multi error", err.Error()) - assert.Equal(t, int64(2), i) -} - -func TestFilterOrmDecorator_InsertOrUpdate(t *testing.T) { - register() - o := &filterMockOrm{} - od := NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - assert.Equal(t, "InsertOrUpdateWithCtx", inv.Method) - assert.Equal(t, 2, len(inv.Args)) - assert.Equal(t, "FILTER_TEST", inv.GetTableName()) - assert.False(t, inv.InsideTx) - return next(ctx, inv) - } - }) - i, err := od.InsertOrUpdate(&FilterTestEntity{}) - assert.NotNil(t, err) - assert.Equal(t, "insert or update error", err.Error()) - assert.Equal(t, int64(1), i) -} - -func TestFilterOrmDecorator_LoadRelated(t *testing.T) { - o := &filterMockOrm{} - od := NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - assert.Equal(t, "LoadRelatedWithCtx", inv.Method) - assert.Equal(t, 3, len(inv.Args)) - assert.Equal(t, "FILTER_TEST", inv.GetTableName()) - assert.False(t, inv.InsideTx) - return next(ctx, inv) - } - }) - i, err := od.LoadRelated(&FilterTestEntity{}, "hello") - assert.NotNil(t, err) - assert.Equal(t, "load related error", err.Error()) - assert.Equal(t, int64(99), i) -} - -func TestFilterOrmDecorator_QueryM2M(t *testing.T) { - o := &filterMockOrm{} - od := NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - assert.Equal(t, "QueryM2MWithCtx", inv.Method) - assert.Equal(t, 2, len(inv.Args)) - assert.Equal(t, "FILTER_TEST", inv.GetTableName()) - assert.False(t, inv.InsideTx) - return next(ctx, inv) - } - }) - res := od.QueryM2M(&FilterTestEntity{}, "hello") - assert.Nil(t, res) -} - -func TestFilterOrmDecorator_QueryTable(t *testing.T) { - register() - o := &filterMockOrm{} - od := NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - assert.Equal(t, "QueryTableWithCtx", inv.Method) - assert.Equal(t, 1, len(inv.Args)) - assert.Equal(t, "FILTER_TEST", inv.GetTableName()) - assert.False(t, inv.InsideTx) - return next(ctx, inv) - } - }) - res := od.QueryTable(&FilterTestEntity{}) - assert.Nil(t, res) -} - -func TestFilterOrmDecorator_Raw(t *testing.T) { - register() - o := &filterMockOrm{} - od := NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - assert.Equal(t, "RawWithCtx", inv.Method) - assert.Equal(t, 2, len(inv.Args)) - assert.Equal(t, "", inv.GetTableName()) - assert.False(t, inv.InsideTx) - return next(ctx, inv) - } - }) - res := od.Raw("hh") - assert.Nil(t, res) -} - -func TestFilterOrmDecorator_ReadForUpdate(t *testing.T) { - register() - o := &filterMockOrm{} - od := NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - assert.Equal(t, "ReadForUpdateWithCtx", inv.Method) - assert.Equal(t, 2, len(inv.Args)) - assert.Equal(t, "FILTER_TEST", inv.GetTableName()) - assert.False(t, inv.InsideTx) - return next(ctx, inv) - } - }) - err := od.ReadForUpdate(&FilterTestEntity{}) - assert.NotNil(t, err) - assert.Equal(t, "read for update error", err.Error()) -} - -func TestFilterOrmDecorator_ReadOrCreate(t *testing.T) { - register() - o := &filterMockOrm{} - od := NewFilterOrmDecorator(o, func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - assert.Equal(t, "ReadOrCreateWithCtx", inv.Method) - assert.Equal(t, 3, len(inv.Args)) - assert.Equal(t, "FILTER_TEST", inv.GetTableName()) - assert.False(t, inv.InsideTx) - return next(ctx, inv) - } - }) - ok, i, err := od.ReadOrCreate(&FilterTestEntity{}, "name") - assert.NotNil(t, err) - assert.Equal(t, "read or create error", err.Error()) - assert.True(t, ok) - assert.Equal(t, int64(13), i) -} - -var _ Ormer = new(filterMockOrm) - -// filterMockOrm is only used in this test file -type filterMockOrm struct { - DoNothingOrm -} - -func (f *filterMockOrm) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) { - return true, 13, errors.New("read or create error") -} - -func (f *filterMockOrm) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error { - return errors.New("read for update error") -} - -func (f *filterMockOrm) LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) { - return 99, errors.New("load related error") -} - -func (f *filterMockOrm) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) { - return 1, errors.New("insert or update error") -} - -func (f *filterMockOrm) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) { - return 2, errors.New("insert multi error") -} - -func (f *filterMockOrm) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) { - return 100, errors.New("insert error") -} - -func (f *filterMockOrm) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(c context.Context, txOrm TxOrmer) error) error { - return task(ctx, nil) -} - -func (f *filterMockOrm) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) { - return -2, errors.New("delete error") -} - -func (f *filterMockOrm) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) { - return &filterMockOrm{}, errors.New("begin tx") -} - -func (f *filterMockOrm) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error { - return errors.New("read error") -} - -func (f *filterMockOrm) Commit() error { - return errors.New("commit") -} - -func (f *filterMockOrm) Rollback() error { - return errors.New("rollback") -} - -func (f *filterMockOrm) DBStats() *sql.DBStats { - return &sql.DBStats{ - MaxOpenConnections: -1, - } -} - -func validateBeginResult(t *testing.T, to TxOrmer, err error) bool { - assert.NotNil(t, err) - assert.Equal(t, "begin tx", err.Error()) - _, ok := to.(*filterOrmDecorator).TxCommitter.(*filterMockOrm) - assert.True(t, ok) - return true -} - -var filterTestEntityRegisterOnce sync.Once - -type FilterTestEntity struct { - ID int - Name string -} - -func register() { - filterTestEntityRegisterOnce.Do(func() { - RegisterModel(&FilterTestEntity{}) - }) -} - -func (f *FilterTestEntity) TableName() string { - return "FILTER_TEST" -} diff --git a/client/orm/filter_test.go b/client/orm/filter_test.go deleted file mode 100644 index f9c86039..00000000 --- a/client/orm/filter_test.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 orm - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAddGlobalFilterChain(t *testing.T) { - AddGlobalFilterChain(func(next Filter) Filter { - return func(ctx context.Context, inv *Invocation) []interface{} { - return next(ctx, inv) - } - }) - assert.Equal(t, 1, len(globalFilterChains)) - globalFilterChains = nil -} diff --git a/client/orm/hints/db_hints.go b/client/orm/hints/db_hints.go deleted file mode 100644 index 7bfe8eb0..00000000 --- a/client/orm/hints/db_hints.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2020 beego-dev -// -// Licensed 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 hints - -import ( - "github.com/astaxie/beego/core/utils" -) - -const ( - //query level - KeyForceIndex = iota - KeyUseIndex - KeyIgnoreIndex - KeyForUpdate - KeyLimit - KeyOffset - KeyOrderBy - KeyRelDepth -) - -type Hint struct { - key interface{} - value interface{} -} - -var _ utils.KV = new(Hint) - -// GetKey return key -func (s *Hint) GetKey() interface{} { - return s.key -} - -// GetValue return value -func (s *Hint) GetValue() interface{} { - return s.value -} - -var _ utils.KV = new(Hint) - -// ForceIndex return a hint about ForceIndex -func ForceIndex(indexes ...string) *Hint { - return NewHint(KeyForceIndex, indexes) -} - -// UseIndex return a hint about UseIndex -func UseIndex(indexes ...string) *Hint { - return NewHint(KeyUseIndex, indexes) -} - -// IgnoreIndex return a hint about IgnoreIndex -func IgnoreIndex(indexes ...string) *Hint { - return NewHint(KeyIgnoreIndex, indexes) -} - -// ForUpdate return a hint about ForUpdate -func ForUpdate() *Hint { - return NewHint(KeyForUpdate, true) -} - -// DefaultRelDepth return a hint about DefaultRelDepth -func DefaultRelDepth() *Hint { - return NewHint(KeyRelDepth, true) -} - -// RelDepth return a hint about RelDepth -func RelDepth(d int) *Hint { - return NewHint(KeyRelDepth, d) -} - -// Limit return a hint about Limit -func Limit(d int64) *Hint { - return NewHint(KeyLimit, d) -} - -// Offset return a hint about Offset -func Offset(d int64) *Hint { - return NewHint(KeyOffset, d) -} - -// OrderBy return a hint about OrderBy -func OrderBy(s string) *Hint { - return NewHint(KeyOrderBy, s) -} - -// NewHint return a hint -func NewHint(key interface{}, value interface{}) *Hint { - return &Hint{ - key: key, - value: value, - } -} diff --git a/client/orm/hints/db_hints_test.go b/client/orm/hints/db_hints_test.go deleted file mode 100644 index 510f9f16..00000000 --- a/client/orm/hints/db_hints_test.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2020 beego-dev -// -// Licensed 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 hints - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestNewHint_time(t *testing.T) { - key := "qweqwe" - value := time.Second - hint := NewHint(key, value) - - assert.Equal(t, hint.GetKey(), key) - assert.Equal(t, hint.GetValue(), value) -} - -func TestNewHint_int(t *testing.T) { - key := "qweqwe" - value := 281230 - hint := NewHint(key, value) - - assert.Equal(t, hint.GetKey(), key) - assert.Equal(t, hint.GetValue(), value) -} - -func TestNewHint_float(t *testing.T) { - key := "qweqwe" - value := 21.2459753 - hint := NewHint(key, value) - - assert.Equal(t, hint.GetKey(), key) - assert.Equal(t, hint.GetValue(), value) -} - -func TestForceIndex(t *testing.T) { - s := []string{`f_index1`, `f_index2`, `f_index3`} - hint := ForceIndex(s...) - assert.Equal(t, hint.GetValue(), s) - assert.Equal(t, hint.GetKey(), KeyForceIndex) -} - -func TestForceIndex_0(t *testing.T) { - var s []string - hint := ForceIndex(s...) - assert.Equal(t, hint.GetValue(), s) - assert.Equal(t, hint.GetKey(), KeyForceIndex) -} - -func TestIgnoreIndex(t *testing.T) { - s := []string{`i_index1`, `i_index2`, `i_index3`} - hint := IgnoreIndex(s...) - assert.Equal(t, hint.GetValue(), s) - assert.Equal(t, hint.GetKey(), KeyIgnoreIndex) -} - -func TestIgnoreIndex_0(t *testing.T) { - var s []string - hint := IgnoreIndex(s...) - assert.Equal(t, hint.GetValue(), s) - assert.Equal(t, hint.GetKey(), KeyIgnoreIndex) -} - -func TestUseIndex(t *testing.T) { - s := []string{`u_index1`, `u_index2`, `u_index3`} - hint := UseIndex(s...) - assert.Equal(t, hint.GetValue(), s) - assert.Equal(t, hint.GetKey(), KeyUseIndex) -} - -func TestUseIndex_0(t *testing.T) { - var s []string - hint := UseIndex(s...) - assert.Equal(t, hint.GetValue(), s) - assert.Equal(t, hint.GetKey(), KeyUseIndex) -} - -func TestForUpdate(t *testing.T) { - hint := ForUpdate() - assert.Equal(t, hint.GetValue(), true) - assert.Equal(t, hint.GetKey(), KeyForUpdate) -} - -func TestDefaultRelDepth(t *testing.T) { - hint := DefaultRelDepth() - assert.Equal(t, hint.GetValue(), true) - assert.Equal(t, hint.GetKey(), KeyRelDepth) -} - -func TestRelDepth(t *testing.T) { - hint := RelDepth(157965) - assert.Equal(t, hint.GetValue(), 157965) - assert.Equal(t, hint.GetKey(), KeyRelDepth) -} - -func TestLimit(t *testing.T) { - hint := Limit(1579625) - assert.Equal(t, hint.GetValue(), int64(1579625)) - assert.Equal(t, hint.GetKey(), KeyLimit) -} - -func TestOffset(t *testing.T) { - hint := Offset(int64(1572123965)) - assert.Equal(t, hint.GetValue(), int64(1572123965)) - assert.Equal(t, hint.GetKey(), KeyOffset) -} - -func TestOrderBy(t *testing.T) { - hint := OrderBy(`-ID`) - assert.Equal(t, hint.GetValue(), `-ID`) - assert.Equal(t, hint.GetKey(), KeyOrderBy) -} diff --git a/client/orm/invocation.go b/client/orm/invocation.go deleted file mode 100644 index 9e7c1974..00000000 --- a/client/orm/invocation.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 orm - -import ( - "context" - "time" -) - -// Invocation represents an "Orm" invocation -type Invocation struct { - Method string - // Md may be nil in some cases. It depends on method - Md interface{} - // the args are all arguments except context.Context - Args []interface{} - - mi *modelInfo - // f is the Orm operation - f func(ctx context.Context) []interface{} - - // insideTx indicates whether this is inside a transaction - InsideTx bool - TxStartTime time.Time - TxName string -} - -func (inv *Invocation) GetTableName() string { - if inv.mi != nil { - return inv.mi.table - } - return "" -} - -func (inv *Invocation) execute(ctx context.Context) []interface{} { - return inv.f(ctx) -} - -// GetPkFieldName return the primary key of this table -// if not found, "" is returned -func (inv *Invocation) GetPkFieldName() string { - if inv.mi.fields.pk != nil { - return inv.mi.fields.pk.name - } - return "" -} diff --git a/client/orm/migration/doc.go b/client/orm/migration/doc.go deleted file mode 100644 index 0c6564d4..00000000 --- a/client/orm/migration/doc.go +++ /dev/null @@ -1,32 +0,0 @@ -// Package migration enables you to generate migrations back and forth. It generates both migrations. -// -// //Creates a table -// m.CreateTable("tablename","InnoDB","utf8"); -// -// //Alter a table -// m.AlterTable("tablename") -// -// Standard Column Methods -// * SetDataType -// * SetNullable -// * SetDefault -// * SetUnsigned (use only on integer types unless produces error) -// -// //Sets a primary column, multiple calls allowed, standard column methods available -// m.PriCol("id").SetAuto(true).SetNullable(false).SetDataType("INT(10)").SetUnsigned(true) -// -// //UniCol Can be used multiple times, allows standard Column methods. Use same "index" string to add to same index -// m.UniCol("index","column") -// -// //Standard Column Initialisation, can call .Remove() after NewCol("") on alter to remove -// m.NewCol("name").SetDataType("VARCHAR(255) COLLATE utf8_unicode_ci").SetNullable(false) -// m.NewCol("value").SetDataType("DOUBLE(8,2)").SetNullable(false) -// -// //Rename Columns , only use with Alter table, doesn't works with Create, prefix standard column methods with "Old" to -// //create a true reversible migration eg: SetOldDataType("DOUBLE(12,3)") -// m.RenameColumn("from","to")... -// -// //Foreign Columns, single columns are only supported, SetOnDelete & SetOnUpdate are available, call appropriately. -// //Supports standard column methods, automatic reverse. -// m.ForeignCol("local_col","foreign_col","foreign_table") -package migration diff --git a/client/orm/model_utils_test.go b/client/orm/model_utils_test.go deleted file mode 100644 index b65aadcb..00000000 --- a/client/orm/model_utils_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 orm - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -type Interface struct { - Id int - Name string - - Index1 string - Index2 string - - Unique1 string - Unique2 string -} - -func (i *Interface) TableIndex() [][]string { - return [][]string{{"index1"}, {"index2"}} -} - -func (i *Interface) TableUnique() [][]string { - return [][]string{{"unique1"}, {"unique2"}} -} - -func (i *Interface) TableName() string { - return "INTERFACE_" -} - -func (i *Interface) TableEngine() string { - return "innodb" -} - -func TestDbBase_GetTables(t *testing.T) { - RegisterModel(&Interface{}) - mi, ok := modelCache.get("INTERFACE_") - assert.True(t, ok) - assert.NotNil(t, mi) - - engine := getTableEngine(mi.addrField) - assert.Equal(t, "innodb", engine) - uniques := getTableUnique(mi.addrField) - assert.Equal(t, [][]string{{"unique1"}, {"unique2"}}, uniques) - indexes := getTableIndex(mi.addrField) - assert.Equal(t, [][]string{{"index1"}, {"index2"}}, indexes) -} diff --git a/client/orm/models.go b/client/orm/models.go deleted file mode 100644 index 19941d2e..00000000 --- a/client/orm/models.go +++ /dev/null @@ -1,569 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "errors" - "fmt" - "reflect" - "runtime/debug" - "strings" - "sync" -) - -const ( - odCascade = "cascade" - odSetNULL = "set_null" - odSetDefault = "set_default" - odDoNothing = "do_nothing" - defaultStructTagName = "orm" - defaultStructTagDelim = ";" -) - -var ( - modelCache = NewModelCacheHandler() -) - -// model info collection -type _modelCache struct { - sync.RWMutex // only used outsite for bootStrap - orders []string - cache map[string]*modelInfo - cacheByFullName map[string]*modelInfo - done bool -} - -//NewModelCacheHandler generator of _modelCache -func NewModelCacheHandler() *_modelCache { - return &_modelCache{ - cache: make(map[string]*modelInfo), - cacheByFullName: make(map[string]*modelInfo), - } -} - -// get all model info -func (mc *_modelCache) all() map[string]*modelInfo { - m := make(map[string]*modelInfo, len(mc.cache)) - for k, v := range mc.cache { - m[k] = v - } - return m -} - -// get ordered model info -func (mc *_modelCache) allOrdered() []*modelInfo { - m := make([]*modelInfo, 0, len(mc.orders)) - for _, table := range mc.orders { - m = append(m, mc.cache[table]) - } - return m -} - -// get model info by table name -func (mc *_modelCache) get(table string) (mi *modelInfo, ok bool) { - mi, ok = mc.cache[table] - return -} - -// get model info by full name -func (mc *_modelCache) getByFullName(name string) (mi *modelInfo, ok bool) { - mi, ok = mc.cacheByFullName[name] - return -} - -func (mc *_modelCache) getByMd(md interface{}) (*modelInfo, bool) { - val := reflect.ValueOf(md) - ind := reflect.Indirect(val) - typ := ind.Type() - name := getFullName(typ) - return mc.getByFullName(name) -} - -// set model info to collection -func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo { - mii := mc.cache[table] - mc.cache[table] = mi - mc.cacheByFullName[mi.fullName] = mi - if mii == nil { - mc.orders = append(mc.orders, table) - } - return mii -} - -// clean all model info. -func (mc *_modelCache) clean() { - mc.Lock() - defer mc.Unlock() - - mc.orders = make([]string, 0) - mc.cache = make(map[string]*modelInfo) - mc.cacheByFullName = make(map[string]*modelInfo) - mc.done = false -} - -//bootstrap bootstrap for models -func (mc *_modelCache) bootstrap() { - mc.Lock() - defer mc.Unlock() - if mc.done { - return - } - var ( - err error - models map[string]*modelInfo - ) - if dataBaseCache.getDefault() == nil { - err = fmt.Errorf("must have one register DataBase alias named `default`") - goto end - } - - // set rel and reverse model - // RelManyToMany set the relTable - models = mc.all() - for _, mi := range models { - for _, fi := range mi.fields.columns { - if fi.rel || fi.reverse { - elm := fi.addrValue.Type().Elem() - if fi.fieldType == RelReverseMany || fi.fieldType == RelManyToMany { - elm = elm.Elem() - } - // check the rel or reverse model already register - name := getFullName(elm) - mii, ok := mc.getByFullName(name) - if !ok || mii.pkg != elm.PkgPath() { - err = fmt.Errorf("can not find rel in field `%s`, `%s` may be miss register", fi.fullName, elm.String()) - goto end - } - fi.relModelInfo = mii - - switch fi.fieldType { - case RelManyToMany: - if fi.relThrough != "" { - if i := strings.LastIndex(fi.relThrough, "."); i != -1 && len(fi.relThrough) > (i+1) { - pn := fi.relThrough[:i] - rmi, ok := mc.getByFullName(fi.relThrough) - if !ok || pn != rmi.pkg { - err = fmt.Errorf("field `%s` wrong rel_through value `%s` cannot find table", fi.fullName, fi.relThrough) - goto end - } - fi.relThroughModelInfo = rmi - fi.relTable = rmi.table - } else { - err = fmt.Errorf("field `%s` wrong rel_through value `%s`", fi.fullName, fi.relThrough) - goto end - } - } else { - i := newM2MModelInfo(mi, mii) - if fi.relTable != "" { - i.table = fi.relTable - } - if v := mc.set(i.table, i); v != nil { - err = fmt.Errorf("the rel table name `%s` already registered, cannot be use, please change one", fi.relTable) - goto end - } - fi.relTable = i.table - fi.relThroughModelInfo = i - } - - fi.relThroughModelInfo.isThrough = true - } - } - } - } - - // check the rel filed while the relModelInfo also has filed point to current model - // if not exist, add a new field to the relModelInfo - models = mc.all() - for _, mi := range models { - for _, fi := range mi.fields.fieldsRel { - switch fi.fieldType { - case RelForeignKey, RelOneToOne, RelManyToMany: - inModel := false - for _, ffi := range fi.relModelInfo.fields.fieldsReverse { - if ffi.relModelInfo == mi { - inModel = true - break - } - } - if !inModel { - rmi := fi.relModelInfo - ffi := new(fieldInfo) - ffi.name = mi.name - ffi.column = ffi.name - ffi.fullName = rmi.fullName + "." + ffi.name - ffi.reverse = true - ffi.relModelInfo = mi - ffi.mi = rmi - if fi.fieldType == RelOneToOne { - ffi.fieldType = RelReverseOne - } else { - ffi.fieldType = RelReverseMany - } - if !rmi.fields.Add(ffi) { - added := false - for cnt := 0; cnt < 5; cnt++ { - ffi.name = fmt.Sprintf("%s%d", mi.name, cnt) - ffi.column = ffi.name - ffi.fullName = rmi.fullName + "." + ffi.name - if added = rmi.fields.Add(ffi); added { - break - } - } - if !added { - panic(fmt.Errorf("cannot generate auto reverse field info `%s` to `%s`", fi.fullName, ffi.fullName)) - } - } - } - } - } - } - - models = mc.all() - for _, mi := range models { - for _, fi := range mi.fields.fieldsRel { - switch fi.fieldType { - case RelManyToMany: - for _, ffi := range fi.relThroughModelInfo.fields.fieldsRel { - switch ffi.fieldType { - case RelOneToOne, RelForeignKey: - if ffi.relModelInfo == fi.relModelInfo { - fi.reverseFieldInfoTwo = ffi - } - if ffi.relModelInfo == mi { - fi.reverseField = ffi.name - fi.reverseFieldInfo = ffi - } - } - } - if fi.reverseFieldInfoTwo == nil { - err = fmt.Errorf("can not find m2m field for m2m model `%s`, ensure your m2m model defined correct", - fi.relThroughModelInfo.fullName) - goto end - } - } - } - } - - models = mc.all() - for _, mi := range models { - for _, fi := range mi.fields.fieldsReverse { - switch fi.fieldType { - case RelReverseOne: - found := false - mForA: - for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelOneToOne] { - if ffi.relModelInfo == mi { - found = true - fi.reverseField = ffi.name - fi.reverseFieldInfo = ffi - - ffi.reverseField = fi.name - ffi.reverseFieldInfo = fi - break mForA - } - } - if !found { - err = fmt.Errorf("reverse field `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName) - goto end - } - case RelReverseMany: - found := false - mForB: - for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelForeignKey] { - if ffi.relModelInfo == mi { - found = true - fi.reverseField = ffi.name - fi.reverseFieldInfo = ffi - - ffi.reverseField = fi.name - ffi.reverseFieldInfo = fi - - break mForB - } - } - if !found { - mForC: - for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelManyToMany] { - conditions := fi.relThrough != "" && fi.relThrough == ffi.relThrough || - fi.relTable != "" && fi.relTable == ffi.relTable || - fi.relThrough == "" && fi.relTable == "" - if ffi.relModelInfo == mi && conditions { - found = true - - fi.reverseField = ffi.reverseFieldInfoTwo.name - fi.reverseFieldInfo = ffi.reverseFieldInfoTwo - fi.relThroughModelInfo = ffi.relThroughModelInfo - fi.reverseFieldInfoTwo = ffi.reverseFieldInfo - fi.reverseFieldInfoM2M = ffi - ffi.reverseFieldInfoM2M = fi - - break mForC - } - } - } - if !found { - err = fmt.Errorf("reverse field for `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName) - goto end - } - } - } - } - -end: - if err != nil { - fmt.Println(err) - debug.PrintStack() - } - mc.done = true - return -} - -// register register models to model cache -func (mc *_modelCache) register(prefixOrSuffixStr string, prefixOrSuffix bool, models ...interface{}) (err error) { - if mc.done { - err = fmt.Errorf("register must be run before BootStrap") - return - } - - for _, model := range models { - val := reflect.ValueOf(model) - typ := reflect.Indirect(val).Type() - - if val.Kind() != reflect.Ptr { - err = fmt.Errorf(" cannot use non-ptr model struct `%s`", getFullName(typ)) - return - } - // For this case: - // u := &User{} - // registerModel(&u) - if typ.Kind() == reflect.Ptr { - err = fmt.Errorf(" only allow ptr model struct, it looks you use two reference to the struct `%s`", typ) - return - } - - table := getTableName(val) - - if prefixOrSuffixStr != "" { - if prefixOrSuffix { - table = prefixOrSuffixStr + table - } else { - table = table + prefixOrSuffixStr - } - } - - // models's fullname is pkgpath + struct name - name := getFullName(typ) - if _, ok := mc.getByFullName(name); ok { - err = fmt.Errorf(" model `%s` repeat register, must be unique\n", name) - return - } - - if _, ok := mc.get(table); ok { - err = fmt.Errorf(" table name `%s` repeat register, must be unique\n", table) - return - } - - mi := newModelInfo(val) - if mi.fields.pk == nil { - outFor: - for _, fi := range mi.fields.fieldsDB { - if strings.ToLower(fi.name) == "id" { - switch fi.addrValue.Elem().Kind() { - case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64: - fi.auto = true - fi.pk = true - mi.fields.pk = fi - break outFor - } - } - } - - if mi.fields.pk == nil { - err = fmt.Errorf(" `%s` needs a primary key field, default is to use 'id' if not set\n", name) - return - } - - } - - mi.table = table - mi.pkg = typ.PkgPath() - mi.model = model - mi.manual = true - - mc.set(table, mi) - } - return -} - -//getDbDropSQL get database scheme drop sql queries -func (mc *_modelCache) getDbDropSQL(al *alias) (queries []string, err error) { - if len(mc.cache) == 0 { - err = errors.New("no Model found, need register your model") - return - } - - Q := al.DbBaser.TableQuote() - - for _, mi := range mc.allOrdered() { - queries = append(queries, fmt.Sprintf(`DROP TABLE IF EXISTS %s%s%s`, Q, mi.table, Q)) - } - return queries, nil -} - -//getDbCreateSQL get database scheme creation sql queries -func (mc *_modelCache) getDbCreateSQL(al *alias) (queries []string, tableIndexes map[string][]dbIndex, err error) { - if len(mc.cache) == 0 { - err = errors.New("no Model found, need register your model") - return - } - - Q := al.DbBaser.TableQuote() - T := al.DbBaser.DbTypes() - sep := fmt.Sprintf("%s, %s", Q, Q) - - tableIndexes = make(map[string][]dbIndex) - - for _, mi := range mc.allOrdered() { - sql := fmt.Sprintf("-- %s\n", strings.Repeat("-", 50)) - sql += fmt.Sprintf("-- Table Structure for `%s`\n", mi.fullName) - sql += fmt.Sprintf("-- %s\n", strings.Repeat("-", 50)) - - sql += fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s%s%s (\n", Q, mi.table, Q) - - columns := make([]string, 0, len(mi.fields.fieldsDB)) - - sqlIndexes := [][]string{} - - for _, fi := range mi.fields.fieldsDB { - - column := fmt.Sprintf(" %s%s%s ", Q, fi.column, Q) - col := getColumnTyp(al, fi) - - if fi.auto { - switch al.Driver { - case DRSqlite, DRPostgres: - column += T["auto"] - default: - column += col + " " + T["auto"] - } - } else if fi.pk { - column += col + " " + T["pk"] - } else { - column += col - - if !fi.null { - column += " " + "NOT NULL" - } - - //if fi.initial.String() != "" { - // column += " DEFAULT " + fi.initial.String() - //} - - // Append attribute DEFAULT - column += getColumnDefault(fi) - - if fi.unique { - column += " " + "UNIQUE" - } - - if fi.index { - sqlIndexes = append(sqlIndexes, []string{fi.column}) - } - } - - if strings.Contains(column, "%COL%") { - column = strings.Replace(column, "%COL%", fi.column, -1) - } - - if fi.description != "" && al.Driver != DRSqlite { - column += " " + fmt.Sprintf("COMMENT '%s'", fi.description) - } - - columns = append(columns, column) - } - - if mi.model != nil { - allnames := getTableUnique(mi.addrField) - if !mi.manual && len(mi.uniques) > 0 { - allnames = append(allnames, mi.uniques) - } - for _, names := range allnames { - cols := make([]string, 0, len(names)) - for _, name := range names { - if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol { - cols = append(cols, fi.column) - } else { - panic(fmt.Errorf("cannot found column `%s` when parse UNIQUE in `%s.TableUnique`", name, mi.fullName)) - } - } - column := fmt.Sprintf(" UNIQUE (%s%s%s)", Q, strings.Join(cols, sep), Q) - columns = append(columns, column) - } - } - - sql += strings.Join(columns, ",\n") - sql += "\n)" - - if al.Driver == DRMySQL { - var engine string - if mi.model != nil { - engine = getTableEngine(mi.addrField) - } - if engine == "" { - engine = al.Engine - } - sql += " ENGINE=" + engine - } - - sql += ";" - queries = append(queries, sql) - - if mi.model != nil { - for _, names := range getTableIndex(mi.addrField) { - cols := make([]string, 0, len(names)) - for _, name := range names { - if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol { - cols = append(cols, fi.column) - } else { - panic(fmt.Errorf("cannot found column `%s` when parse INDEX in `%s.TableIndex`", name, mi.fullName)) - } - } - sqlIndexes = append(sqlIndexes, cols) - } - } - - for _, names := range sqlIndexes { - name := mi.table + "_" + strings.Join(names, "_") - cols := strings.Join(names, sep) - sql := fmt.Sprintf("CREATE INDEX %s%s%s ON %s%s%s (%s%s%s);", Q, name, Q, Q, mi.table, Q, Q, cols, Q) - - index := dbIndex{} - index.Table = mi.table - index.Name = name - index.SQL = sql - - tableIndexes[mi.table] = append(tableIndexes[mi.table], index) - } - - } - - return -} - -// ResetModelCache Clean model cache. Then you can re-RegisterModel. -// Common use this api for test case. -func ResetModelCache() { - modelCache.clean() -} diff --git a/client/orm/models_boot.go b/client/orm/models_boot.go deleted file mode 100644 index 9a0ce893..00000000 --- a/client/orm/models_boot.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -// RegisterModel register models -func RegisterModel(models ...interface{}) { - RegisterModelWithPrefix("", models...) -} - -// RegisterModelWithPrefix register models with a prefix -func RegisterModelWithPrefix(prefix string, models ...interface{}) { - if err := modelCache.register(prefix, true, models...); err != nil { - panic(err) - } -} - -// RegisterModelWithSuffix register models with a suffix -func RegisterModelWithSuffix(suffix string, models ...interface{}) { - if err := modelCache.register(suffix, false, models...); err != nil { - panic(err) - } -} - -// BootStrap bootstrap models. -// make all model parsed and can not add more models -func BootStrap() { - modelCache.bootstrap() -} diff --git a/client/orm/models_utils_test.go b/client/orm/models_utils_test.go deleted file mode 100644 index 0a6995b3..00000000 --- a/client/orm/models_utils_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 orm - -import ( - "reflect" - "testing" - - "github.com/stretchr/testify/assert" -) - -type NotApplicableModel struct { - Id int -} - -func (n *NotApplicableModel) IsApplicableTableForDB(db string) bool { - return db == "default" -} - -func Test_IsApplicableTableForDB(t *testing.T) { - assert.False(t, isApplicableTableForDB(reflect.ValueOf(&NotApplicableModel{}), "defa")) - assert.True(t, isApplicableTableForDB(reflect.ValueOf(&NotApplicableModel{}), "default")) -} diff --git a/client/orm/qb_postgres.go b/client/orm/qb_postgres.go deleted file mode 100644 index eec784df..00000000 --- a/client/orm/qb_postgres.go +++ /dev/null @@ -1,221 +0,0 @@ -package orm - -import ( - "fmt" - "strconv" - "strings" -) - -var quote string = `"` - -// PostgresQueryBuilder is the SQL build -type PostgresQueryBuilder struct { - tokens []string -} - -func processingStr(str []string) string { - s := strings.Join(str, `","`) - s = fmt.Sprintf("%s%s%s", quote, s, quote) - return s -} - -// Select will join the fields -func (qb *PostgresQueryBuilder) Select(fields ...string) QueryBuilder { - - var str string - n := len(fields) - - if fields[0] == "*" { - str = "*" - } else { - for i := 0; i < n; i++ { - sli := strings.Split(fields[i], ".") - s := strings.Join(sli, `"."`) - s = fmt.Sprintf("%s%s%s", quote, s, quote) - if n == 1 || i == n-1 { - str += s - } else { - str += s + "," - } - } - } - - qb.tokens = append(qb.tokens, "SELECT", str) - return qb -} - -// ForUpdate add the FOR UPDATE clause -func (qb *PostgresQueryBuilder) ForUpdate() QueryBuilder { - qb.tokens = append(qb.tokens, "FOR UPDATE") - return qb -} - -// From join the tables -func (qb *PostgresQueryBuilder) From(tables ...string) QueryBuilder { - str := processingStr(tables) - qb.tokens = append(qb.tokens, "FROM", str) - return qb -} - -// InnerJoin INNER JOIN the table -func (qb *PostgresQueryBuilder) InnerJoin(table string) QueryBuilder { - str := fmt.Sprintf("%s%s%s", quote, table, quote) - qb.tokens = append(qb.tokens, "INNER JOIN", str) - return qb -} - -// LeftJoin LEFT JOIN the table -func (qb *PostgresQueryBuilder) LeftJoin(table string) QueryBuilder { - str := fmt.Sprintf("%s%s%s", quote, table, quote) - qb.tokens = append(qb.tokens, "LEFT JOIN", str) - return qb -} - -// RightJoin RIGHT JOIN the table -func (qb *PostgresQueryBuilder) RightJoin(table string) QueryBuilder { - str := fmt.Sprintf("%s%s%s", quote, table, quote) - qb.tokens = append(qb.tokens, "RIGHT JOIN", str) - return qb -} - -// On join with on cond -func (qb *PostgresQueryBuilder) On(cond string) QueryBuilder { - - var str string - cond = strings.Replace(cond, " ", "", -1) - slice := strings.Split(cond, "=") - for i := 0; i < len(slice); i++ { - sli := strings.Split(slice[i], ".") - s := strings.Join(sli, `"."`) - s = fmt.Sprintf("%s%s%s", quote, s, quote) - if i == 0 { - str = s + " =" + " " - } else { - str += s - } - } - - qb.tokens = append(qb.tokens, "ON", str) - return qb -} - -// Where join the Where cond -func (qb *PostgresQueryBuilder) Where(cond string) QueryBuilder { - qb.tokens = append(qb.tokens, "WHERE", cond) - return qb -} - -// And join the and cond -func (qb *PostgresQueryBuilder) And(cond string) QueryBuilder { - qb.tokens = append(qb.tokens, "AND", cond) - return qb -} - -// Or join the or cond -func (qb *PostgresQueryBuilder) Or(cond string) QueryBuilder { - qb.tokens = append(qb.tokens, "OR", cond) - return qb -} - -// In join the IN (vals) -func (qb *PostgresQueryBuilder) In(vals ...string) QueryBuilder { - qb.tokens = append(qb.tokens, "IN", "(", strings.Join(vals, CommaSpace), ")") - return qb -} - -// OrderBy join the Order by fields -func (qb *PostgresQueryBuilder) OrderBy(fields ...string) QueryBuilder { - str := processingStr(fields) - qb.tokens = append(qb.tokens, "ORDER BY", str) - return qb -} - -// Asc join the asc -func (qb *PostgresQueryBuilder) Asc() QueryBuilder { - qb.tokens = append(qb.tokens, "ASC") - return qb -} - -// Desc join the desc -func (qb *PostgresQueryBuilder) Desc() QueryBuilder { - qb.tokens = append(qb.tokens, "DESC") - return qb -} - -// Limit join the limit num -func (qb *PostgresQueryBuilder) Limit(limit int) QueryBuilder { - qb.tokens = append(qb.tokens, "LIMIT", strconv.Itoa(limit)) - return qb -} - -// Offset join the offset num -func (qb *PostgresQueryBuilder) Offset(offset int) QueryBuilder { - qb.tokens = append(qb.tokens, "OFFSET", strconv.Itoa(offset)) - return qb -} - -// GroupBy join the Group by fields -func (qb *PostgresQueryBuilder) GroupBy(fields ...string) QueryBuilder { - str := processingStr(fields) - qb.tokens = append(qb.tokens, "GROUP BY", str) - return qb -} - -// Having join the Having cond -func (qb *PostgresQueryBuilder) Having(cond string) QueryBuilder { - qb.tokens = append(qb.tokens, "HAVING", cond) - return qb -} - -// Update join the update table -func (qb *PostgresQueryBuilder) Update(tables ...string) QueryBuilder { - str := processingStr(tables) - qb.tokens = append(qb.tokens, "UPDATE", str) - return qb -} - -// Set join the set kv -func (qb *PostgresQueryBuilder) Set(kv ...string) QueryBuilder { - qb.tokens = append(qb.tokens, "SET", strings.Join(kv, CommaSpace)) - return qb -} - -// Delete join the Delete tables -func (qb *PostgresQueryBuilder) Delete(tables ...string) QueryBuilder { - qb.tokens = append(qb.tokens, "DELETE") - if len(tables) != 0 { - str := processingStr(tables) - qb.tokens = append(qb.tokens, str) - } - return qb -} - -// InsertInto join the insert SQL -func (qb *PostgresQueryBuilder) InsertInto(table string, fields ...string) QueryBuilder { - str := fmt.Sprintf("%s%s%s", quote, table, quote) - qb.tokens = append(qb.tokens, "INSERT INTO", str) - if len(fields) != 0 { - fieldsStr := strings.Join(fields, CommaSpace) - qb.tokens = append(qb.tokens, "(", fieldsStr, ")") - } - return qb -} - -// Values join the Values(vals) -func (qb *PostgresQueryBuilder) Values(vals ...string) QueryBuilder { - valsStr := strings.Join(vals, CommaSpace) - qb.tokens = append(qb.tokens, "VALUES", "(", valsStr, ")") - return qb -} - -// Subquery join the sub as alias -func (qb *PostgresQueryBuilder) Subquery(sub string, alias string) string { - return fmt.Sprintf("(%s) AS %s", sub, alias) -} - -// String join all tokens -func (qb *PostgresQueryBuilder) String() string { - s := strings.Join(qb.tokens, " ") - qb.tokens = qb.tokens[:0] - return s -} diff --git a/client/orm/qb_tidb.go b/client/orm/qb_tidb.go deleted file mode 100644 index 772edb5d..00000000 --- a/client/orm/qb_tidb.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2015 TiDB Author. All Rights Reserved. -// -// Licensed 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 orm - -// TiDBQueryBuilder is the SQL build -type TiDBQueryBuilder struct { - MySQLQueryBuilder - tokens []string -} diff --git a/client/orm/utils_test.go b/client/orm/utils_test.go deleted file mode 100644 index 7d94cada..00000000 --- a/client/orm/utils_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "testing" -) - -func TestCamelString(t *testing.T) { - snake := []string{"pic_url", "hello_world_", "hello__World", "_HelLO_Word", "pic_url_1", "pic_url__1"} - camel := []string{"PicUrl", "HelloWorld", "HelloWorld", "HelLOWord", "PicUrl1", "PicUrl1"} - - answer := make(map[string]string) - for i, v := range snake { - answer[v] = camel[i] - } - - for _, v := range snake { - res := camelString(v) - if res != answer[v] { - t.Error("Unit Test Fail:", v, res, answer[v]) - } - } -} - -func TestSnakeString(t *testing.T) { - camel := []string{"PicUrl", "HelloWorld", "HelloWorld", "HelLOWord", "PicUrl1", "XyXX"} - snake := []string{"pic_url", "hello_world", "hello_world", "hel_l_o_word", "pic_url1", "xy_x_x"} - - answer := make(map[string]string) - for i, v := range camel { - answer[v] = snake[i] - } - - for _, v := range camel { - res := snakeString(v) - if res != answer[v] { - t.Error("Unit Test Fail:", v, res, answer[v]) - } - } -} - -func TestSnakeStringWithAcronym(t *testing.T) { - camel := []string{"ID", "PicURL", "HelloWorld", "HelloWorld", "HelLOWord", "PicUrl1", "XyXX"} - snake := []string{"id", "pic_url", "hello_world", "hello_world", "hel_lo_word", "pic_url1", "xy_xx"} - - answer := make(map[string]string) - for i, v := range camel { - answer[v] = snake[i] - } - - for _, v := range camel { - res := snakeStringWithAcronym(v) - if res != answer[v] { - t.Error("Unit Test Fail:", v, res, answer[v]) - } - } -} diff --git a/server/web/config.go b/config.go similarity index 78% rename from server/web/config.go rename to config.go index 10138e63..b6c9a99c 100644 --- a/server/web/config.go +++ b/config.go @@ -12,10 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( - "crypto/tls" "fmt" "os" "path/filepath" @@ -23,36 +22,29 @@ import ( "runtime" "strings" - "github.com/astaxie/beego" - "github.com/astaxie/beego/core/config" - "github.com/astaxie/beego/core/logs" - "github.com/astaxie/beego/server/web/session" - - "github.com/astaxie/beego/core/utils" - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/config" + "github.com/astaxie/beego/context" + "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/session" + "github.com/astaxie/beego/utils" ) // Config is the main struct for BConfig -// TODO after supporting multiple servers, remove common config to somewhere else type Config struct { - AppName string // Application name - RunMode string // Running Mode: dev | prod + AppName string //Application name + RunMode string //Running Mode: dev | prod RouterCaseSensitive bool ServerName string RecoverPanic bool - RecoverFunc func(*context.Context, *Config) + RecoverFunc func(*context.Context) CopyRequestBody bool EnableGzip bool - // MaxMemory and MaxUploadSize are used to limit the request body - // if the request is not uploading file, MaxMemory is the max size of request body - // if the request is uploading file, MaxUploadSize is the max size of request body - MaxMemory int64 - MaxUploadSize int64 - EnableErrorsShow bool - EnableErrorsRender bool - Listen Listen - WebConfig WebConfig - Log LogConfig + MaxMemory int64 + EnableErrorsShow bool + EnableErrorsRender bool + Listen Listen + WebConfig WebConfig + Log LogConfig } // Listen holds for http and https related config @@ -78,7 +70,6 @@ type Listen struct { AdminPort int EnableFcgi bool EnableStdIo bool // EnableStdIo works with EnableFcgi Use FCGI via standard I/O - ClientAuth int } // WebConfig holds web related config @@ -95,7 +86,6 @@ type WebConfig struct { TemplateLeft string TemplateRight string ViewsPath string - CommentRouterPath string EnableXSRF bool XSRFKey string XSRFExpire int @@ -121,8 +111,8 @@ type SessionConfig struct { // LogConfig holds Log related config type LogConfig struct { AccessLogs bool - EnableStaticLogs bool // log static files requests default: false - AccessLogsFormat string // access log format: JSON_FORMAT, APACHE_FORMAT or empty string + EnableStaticLogs bool //log static files requests default: false + AccessLogsFormat string //access log format: JSON_FORMAT, APACHE_FORMAT or empty string FileLineNum bool Outputs map[string]string // Store Adaptor : config } @@ -172,15 +162,15 @@ func init() { } } -func defaultRecoverPanic(ctx *context.Context, cfg *Config) { +func recoverPanic(ctx *context.Context) { if err := recover(); err != nil { if err == ErrAbort { return } - if !cfg.RecoverPanic { + if !BConfig.RecoverPanic { panic(err) } - if cfg.EnableErrorsShow { + if BConfig.EnableErrorsShow { if _, ok := ErrorMaps[fmt.Sprint(err)]; ok { exception(fmt.Sprint(err), ctx) return @@ -197,7 +187,7 @@ func defaultRecoverPanic(ctx *context.Context, cfg *Config) { logs.Critical(fmt.Sprintf("%s:%d", file, line)) stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line)) } - if cfg.RunMode == DEV && cfg.EnableErrorsRender { + if BConfig.RunMode == DEV && BConfig.EnableErrorsRender { showErr(err, ctx, stack) } if ctx.Output.Status != 0 { @@ -209,19 +199,18 @@ func defaultRecoverPanic(ctx *context.Context, cfg *Config) { } func newBConfig() *Config { - res := &Config{ + return &Config{ AppName: "beego", RunMode: PROD, RouterCaseSensitive: true, - ServerName: "beegoServer:" + beego.VERSION, + ServerName: "beegoServer:" + VERSION, RecoverPanic: true, - - CopyRequestBody: false, - EnableGzip: false, - MaxMemory: 1 << 26, // 64MB - MaxUploadSize: 1 << 30, // 1GB - EnableErrorsShow: true, - EnableErrorsRender: true, + RecoverFunc: recoverPanic, + CopyRequestBody: false, + EnableGzip: false, + MaxMemory: 1 << 26, //64MB + EnableErrorsShow: true, + EnableErrorsRender: true, Listen: Listen{ Graceful: false, ServerTimeOut: 0, @@ -242,7 +231,6 @@ func newBConfig() *Config { AdminPort: 8088, EnableFcgi: false, EnableStdIo: false, - ClientAuth: int(tls.RequireAndVerifyClientCert), }, WebConfig: WebConfig{ AutoRender: true, @@ -257,7 +245,6 @@ func newBConfig() *Config { TemplateLeft: "{{", TemplateRight: "}}", ViewsPath: "views", - CommentRouterPath: "controllers", EnableXSRF: false, XSRFKey: "beegoxsrf", XSRFExpire: 0, @@ -268,7 +255,7 @@ func newBConfig() *Config { SessionGCMaxLifetime: 3600, SessionProviderConfig: "", SessionDisableHTTPOnly: false, - SessionCookieLifeTime: 0, // set cookie default is the browser life + SessionCookieLifeTime: 0, //set cookie default is the browser life SessionAutoSetCookie: true, SessionDomain: "", SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers @@ -284,9 +271,6 @@ func newBConfig() *Config { Outputs: map[string]string{"console": ""}, }, } - - res.RecoverFunc = defaultRecoverPanic - return res } // now only support ini, next will support json. @@ -298,46 +282,18 @@ func parseConfig(appConfigPath string) (err error) { return assignConfig(AppConfig) } -// assignConfig is tricky. -// For 1.x, it use assignSingleConfig to parse the file -// but for 2.x, we use Unmarshaler method func assignConfig(ac config.Configer) error { - - parseConfigForV1(ac) - - err := ac.Unmarshaler("", BConfig) - if err != nil { - _, _ = fmt.Fprintln(os.Stderr, fmt.Sprintf("Unmarshaler config file to BConfig failed. "+ - "And if you are working on v1.x config file, please ignore this, err: %s", err)) - return err - } - - // init log - logs.Reset() - for adaptor, cfg := range BConfig.Log.Outputs { - err := logs.SetLogger(adaptor, cfg) - if err != nil { - fmt.Fprintln(os.Stderr, fmt.Sprintf("%s with the config %q got err:%s", adaptor, cfg, err.Error())) - return err - } - } - logs.SetLogFuncCall(BConfig.Log.FileLineNum) - return nil -} - -func parseConfigForV1(ac config.Configer) { for _, i := range []interface{}{BConfig, &BConfig.Listen, &BConfig.WebConfig, &BConfig.Log, &BConfig.WebConfig.Session} { assignSingleConfig(i, ac) } - // set the run mode first if envRunMode := os.Getenv("BEEGO_RUNMODE"); envRunMode != "" { BConfig.RunMode = envRunMode - } else if runMode, err := ac.String("RunMode"); runMode != "" && err == nil { + } else if runMode := ac.String("RunMode"); runMode != "" { BConfig.RunMode = runMode } - if sd, err := ac.String("StaticDir"); sd != "" && err == nil { + if sd := ac.String("StaticDir"); sd != "" { BConfig.WebConfig.StaticDir = map[string]string{} sds := strings.Fields(sd) for _, v := range sds { @@ -349,7 +305,7 @@ func parseConfigForV1(ac config.Configer) { } } - if sgz, err := ac.String("StaticExtensionsToGzip"); sgz != "" && err == nil { + if sgz := ac.String("StaticExtensionsToGzip"); sgz != "" { extensions := strings.Split(sgz, ",") fileExts := []string{} for _, ext := range extensions { @@ -375,7 +331,7 @@ func parseConfigForV1(ac config.Configer) { BConfig.WebConfig.StaticCacheFileNum = sfn } - if lo, err := ac.String("LogOutputs"); lo != "" && err == nil { + if lo := ac.String("LogOutputs"); lo != "" { // if lo is not nil or empty // means user has set his own LogOutputs // clear the default setting to BConfig.Log.Outputs @@ -389,6 +345,18 @@ func parseConfigForV1(ac config.Configer) { } } } + + //init log + logs.Reset() + for adaptor, config := range BConfig.Log.Outputs { + err := logs.SetLogger(adaptor, config) + if err != nil { + fmt.Fprintln(os.Stderr, fmt.Sprintf("%s with the config %q got err:%s", adaptor, config, err.Error())) + } + } + logs.SetLogFuncCall(BConfig.Log.FileLineNum) + + return nil } func assignSingleConfig(p interface{}, ac config.Configer) { @@ -417,7 +385,7 @@ func assignSingleConfig(p interface{}, ac config.Configer) { pf.SetBool(ac.DefaultBool(name, pf.Bool())) case reflect.Struct: default: - // do nothing here + //do nothing here } } @@ -441,7 +409,6 @@ func LoadAppConfig(adapterName, configPath string) error { } type beegoAppConfig struct { - config.BaseConfiger innerConfig config.Configer } @@ -450,11 +417,7 @@ func newAppConfig(appConfigProvider, appConfigPath string) (*beegoAppConfig, err if err != nil { return nil, err } - return &beegoAppConfig{innerConfig: ac}, nil -} - -func (b *beegoAppConfig) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error { - return b.innerConfig.Unmarshaler(prefix, obj, opt...) + return &beegoAppConfig{ac}, nil } func (b *beegoAppConfig) Set(key, val string) error { @@ -464,16 +427,16 @@ func (b *beegoAppConfig) Set(key, val string) error { return nil } -func (b *beegoAppConfig) String(key string) (string, error) { - if v, err := b.innerConfig.String(BConfig.RunMode + "::" + key); v != "" && err == nil { - return v, nil +func (b *beegoAppConfig) String(key string) string { + if v := b.innerConfig.String(BConfig.RunMode + "::" + key); v != "" { + return v } return b.innerConfig.String(key) } -func (b *beegoAppConfig) Strings(key string) ([]string, error) { - if v, err := b.innerConfig.Strings(BConfig.RunMode + "::" + key); len(v) > 0 && err == nil { - return v, nil +func (b *beegoAppConfig) Strings(key string) []string { + if v := b.innerConfig.Strings(BConfig.RunMode + "::" + key); len(v) > 0 { + return v } return b.innerConfig.Strings(key) } @@ -507,14 +470,14 @@ func (b *beegoAppConfig) Float(key string) (float64, error) { } func (b *beegoAppConfig) DefaultString(key string, defaultVal string) string { - if v, err := b.String(key); v != "" && err == nil { + if v := b.String(key); v != "" { return v } return defaultVal } func (b *beegoAppConfig) DefaultStrings(key string, defaultVal []string) []string { - if v, err := b.Strings(key); len(v) != 0 && err == nil { + if v := b.Strings(key); len(v) != 0 { return v } return defaultVal diff --git a/core/config/config.go b/config/config.go similarity index 65% rename from core/config/config.go rename to config/config.go index a4a24fff..bfd79e85 100644 --- a/core/config/config.go +++ b/config/config.go @@ -15,7 +15,7 @@ // Package config is used to parse config. // Usage: // import "github.com/astaxie/beego/config" -// Examples. +//Examples. // // cnf, err := config.NewConfig("ini", "config.conf") // @@ -37,163 +37,36 @@ // cnf.DIY(key string) (interface{}, error) // cnf.GetSection(section string) (map[string]string, error) // cnf.SaveConfigFile(filename string) error -// More docs http://beego.me/docs/module/config.md +//More docs http://beego.me/docs/module/config.md package config import ( - "context" - "errors" "fmt" "os" "reflect" - "strconv" - "strings" "time" ) // Configer defines how to get and set value from configuration raw data. type Configer interface { - // support section::key type in given key when using ini type. - Set(key, val string) error - - // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. - String(key string) (string, error) - // get string slice - Strings(key string) ([]string, error) + Set(key, val string) error //support section::key type in given key when using ini type. + String(key string) string //support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. + Strings(key string) []string //get string slice Int(key string) (int, error) Int64(key string) (int64, error) Bool(key string) (bool, error) Float(key string) (float64, error) - // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. - DefaultString(key string, defaultVal string) string - // get string slice - DefaultStrings(key string, defaultVal []string) []string + DefaultString(key string, defaultVal string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. + DefaultStrings(key string, defaultVal []string) []string //get string slice DefaultInt(key string, defaultVal int) int DefaultInt64(key string, defaultVal int64) int64 DefaultBool(key string, defaultVal bool) bool DefaultFloat(key string, defaultVal float64) float64 - - // DIY return the original value DIY(key string) (interface{}, error) - GetSection(section string) (map[string]string, error) - - Unmarshaler(prefix string, obj interface{}, opt ...DecodeOption) error - Sub(key string) (Configer, error) - OnChange(key string, fn func(value string)) SaveConfigFile(filename string) error } -type BaseConfiger struct { - // The reader should support key like "a.b.c" - reader func(ctx context.Context, key string) (string, error) -} - -func NewBaseConfiger(reader func(ctx context.Context, key string) (string, error)) BaseConfiger { - return BaseConfiger{ - reader: reader, - } -} - -func (c *BaseConfiger) Int(key string) (int, error) { - res, err := c.reader(context.TODO(), key) - if err != nil { - return 0, err - } - return strconv.Atoi(res) -} - -func (c *BaseConfiger) Int64(key string) (int64, error) { - res, err := c.reader(context.TODO(), key) - if err != nil { - return 0, err - } - return strconv.ParseInt(res, 10, 64) -} - -func (c *BaseConfiger) Bool(key string) (bool, error) { - res, err := c.reader(context.TODO(), key) - if err != nil { - return false, err - } - return ParseBool(res) -} - -func (c *BaseConfiger) Float(key string) (float64, error) { - res, err := c.reader(context.TODO(), key) - if err != nil { - return 0, err - } - return strconv.ParseFloat(res, 64) -} - -// DefaultString returns the string value for a given key. -// if err != nil or value is empty return defaultval -func (c *BaseConfiger) DefaultString(key string, defaultVal string) string { - if res, err := c.String(key); res != "" && err == nil { - return res - } - return defaultVal -} - -// DefaultStrings returns the []string value for a given key. -// if err != nil return defaultval -func (c *BaseConfiger) DefaultStrings(key string, defaultVal []string) []string { - if res, err := c.Strings(key); len(res) > 0 && err == nil { - return res - } - return defaultVal -} - -func (c *BaseConfiger) DefaultInt(key string, defaultVal int) int { - if res, err := c.Int(key); err == nil { - return res - } - return defaultVal -} - -func (c *BaseConfiger) DefaultInt64(key string, defaultVal int64) int64 { - if res, err := c.Int64(key); err == nil { - return res - } - return defaultVal -} - -func (c *BaseConfiger) DefaultBool(key string, defaultVal bool) bool { - if res, err := c.Bool(key); err == nil { - return res - } - return defaultVal -} -func (c *BaseConfiger) DefaultFloat(key string, defaultVal float64) float64 { - if res, err := c.Float(key); err == nil { - return res - } - return defaultVal -} - -func (c *BaseConfiger) String(key string) (string, error) { - return c.reader(context.TODO(), key) -} - -// Strings returns the []string value for a given key. -// Return nil if config value does not exist or is empty. -func (c *BaseConfiger) Strings(key string) ([]string, error) { - res, err := c.String(key) - if err != nil || res == "" { - return nil, err - } - return strings.Split(res, ";"), nil -} - -func (c *BaseConfiger) Sub(key string) (Configer, error) { - return nil, errors.New("unsupported operation") -} - -func (c *BaseConfiger) OnChange(key string, fn func(value string)) { - // do nothing -} - // Config is the adapter interface for parsing config file to get raw data to Configer. type Config interface { Parse(key string) (Configer, error) @@ -367,8 +240,3 @@ func ToString(x interface{}) string { // Fallback to fmt package for anything else like numeric types return fmt.Sprint(x) } - -type DecodeOption func(options decodeOptions) - -type decodeOptions struct { -} diff --git a/adapter/config/config_test.go b/config/config_test.go similarity index 100% rename from adapter/config/config_test.go rename to config/config_test.go diff --git a/core/config/env/env.go b/config/env/env.go similarity index 96% rename from core/config/env/env.go rename to config/env/env.go index d3903d74..34f094fe 100644 --- a/core/config/env/env.go +++ b/config/env/env.go @@ -21,7 +21,7 @@ import ( "os" "strings" - "github.com/astaxie/beego/core/utils" + "github.com/astaxie/beego/utils" ) var env *utils.BeeMap @@ -34,7 +34,7 @@ func init() { } } -// Get returns a value for a given key. +// Get returns a value by key. // If the key does not exist, the default value will be returned. func Get(key string, defVal string) string { if val := env.Get(key); val != nil { diff --git a/adapter/config/env/env_test.go b/config/env/env_test.go similarity index 100% rename from adapter/config/env/env_test.go rename to config/env/env_test.go diff --git a/core/config/fake.go b/config/fake.go similarity index 71% rename from core/config/fake.go rename to config/fake.go index 3f6f4682..d21ab820 100644 --- a/core/config/fake.go +++ b/config/fake.go @@ -15,14 +15,12 @@ package config import ( - "context" "errors" "strconv" "strings" ) type fakeConfigContainer struct { - BaseConfiger data map[string]string } @@ -35,14 +33,42 @@ func (c *fakeConfigContainer) Set(key, val string) error { return nil } +func (c *fakeConfigContainer) String(key string) string { + return c.getData(key) +} + +func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string { + v := c.String(key) + if v == "" { + return defaultval + } + return v +} + +func (c *fakeConfigContainer) Strings(key string) []string { + v := c.String(key) + if v == "" { + return nil + } + return strings.Split(v, ";") +} + +func (c *fakeConfigContainer) DefaultStrings(key string, defaultval []string) []string { + v := c.Strings(key) + if v == nil { + return defaultval + } + return v +} + func (c *fakeConfigContainer) Int(key string) (int, error) { return strconv.Atoi(c.getData(key)) } -func (c *fakeConfigContainer) DefaultInt(key string, defaultVal int) int { +func (c *fakeConfigContainer) DefaultInt(key string, defaultval int) int { v, err := c.Int(key) if err != nil { - return defaultVal + return defaultval } return v } @@ -51,10 +77,10 @@ func (c *fakeConfigContainer) Int64(key string) (int64, error) { return strconv.ParseInt(c.getData(key), 10, 64) } -func (c *fakeConfigContainer) DefaultInt64(key string, defaultVal int64) int64 { +func (c *fakeConfigContainer) DefaultInt64(key string, defaultval int64) int64 { v, err := c.Int64(key) if err != nil { - return defaultVal + return defaultval } return v } @@ -63,10 +89,10 @@ func (c *fakeConfigContainer) Bool(key string) (bool, error) { return ParseBool(c.getData(key)) } -func (c *fakeConfigContainer) DefaultBool(key string, defaultVal bool) bool { +func (c *fakeConfigContainer) DefaultBool(key string, defaultval bool) bool { v, err := c.Bool(key) if err != nil { - return defaultVal + return defaultval } return v } @@ -75,10 +101,10 @@ func (c *fakeConfigContainer) Float(key string) (float64, error) { return strconv.ParseFloat(c.getData(key), 64) } -func (c *fakeConfigContainer) DefaultFloat(key string, defaultVal float64) float64 { +func (c *fakeConfigContainer) DefaultFloat(key string, defaultval float64) float64 { v, err := c.Float(key) if err != nil { - return defaultVal + return defaultval } return v } @@ -98,19 +124,11 @@ func (c *fakeConfigContainer) SaveConfigFile(filename string) error { return errors.New("not implement in the fakeConfigContainer") } -func (c *fakeConfigContainer) Unmarshaler(prefix string, obj interface{}, opt ...DecodeOption) error { - return errors.New("unsupported operation") -} - var _ Configer = new(fakeConfigContainer) // NewFakeConfig return a fake Configer func NewFakeConfig() Configer { - res := &fakeConfigContainer{ + return &fakeConfigContainer{ data: make(map[string]string), } - res.BaseConfiger = NewBaseConfiger(func(ctx context.Context, key string) (string, error) { - return res.getData(key), nil - }) - return res } diff --git a/core/config/ini.go b/config/ini.go similarity index 85% rename from core/config/ini.go rename to config/ini.go index 4d17fb7a..002e5e05 100644 --- a/core/config/ini.go +++ b/config/ini.go @@ -17,7 +17,6 @@ package config import ( "bufio" "bytes" - "context" "errors" "io" "io/ioutil" @@ -27,10 +26,6 @@ import ( "strconv" "strings" "sync" - - "github.com/mitchellh/mapstructure" - - "github.com/astaxie/beego/core/logs" ) var ( @@ -70,10 +65,6 @@ func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, e keyComment: make(map[string]string), RWMutex: sync.RWMutex{}, } - - cfg.BaseConfiger = NewBaseConfiger(func(ctx context.Context, key string) (string, error) { - return cfg.getdata(key), nil - }) cfg.Lock() defer cfg.Unlock() @@ -99,7 +90,7 @@ func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, e break } - // It might be a good idea to throw a error on all unknonw errors? + //It might be a good idea to throw a error on all unknonw errors? if _, ok := err.(*os.PathError); ok { return nil, err } @@ -231,10 +222,9 @@ func (ini *IniConfig) ParseData(data []byte) (Configer, error) { return ini.parseData(dir, data) } -// IniConfigContainer is a config which represents the ini configuration. +// IniConfigContainer A Config represents the ini configuration. // When set and get value, support key as section:name type. type IniConfigContainer struct { - BaseConfiger data map[string]map[string]string // section=> key:val sectionComment map[string]string // section : comment keyComment map[string]string // id: []{comment, key...}; id 1 is for main comment. @@ -247,11 +237,11 @@ func (c *IniConfigContainer) Bool(key string) (bool, error) { } // DefaultBool returns the boolean value for a given key. -// if err != nil return defaultVal -func (c *IniConfigContainer) DefaultBool(key string, defaultVal bool) bool { +// if err != nil return defaultval +func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool { v, err := c.Bool(key) if err != nil { - return defaultVal + return defaultval } return v } @@ -262,11 +252,11 @@ func (c *IniConfigContainer) Int(key string) (int, error) { } // DefaultInt returns the integer value for a given key. -// if err != nil return defaultVal -func (c *IniConfigContainer) DefaultInt(key string, defaultVal int) int { +// if err != nil return defaultval +func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int { v, err := c.Int(key) if err != nil { - return defaultVal + return defaultval } return v } @@ -277,11 +267,11 @@ func (c *IniConfigContainer) Int64(key string) (int64, error) { } // DefaultInt64 returns the int64 value for a given key. -// if err != nil return defaultVal -func (c *IniConfigContainer) DefaultInt64(key string, defaultVal int64) int64 { +// if err != nil return defaultval +func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 { v, err := c.Int64(key) if err != nil { - return defaultVal + return defaultval } return v } @@ -292,46 +282,46 @@ func (c *IniConfigContainer) Float(key string) (float64, error) { } // DefaultFloat returns the float64 value for a given key. -// if err != nil return defaultVal -func (c *IniConfigContainer) DefaultFloat(key string, defaultVal float64) float64 { +// if err != nil return defaultval +func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 { v, err := c.Float(key) if err != nil { - return defaultVal + return defaultval } return v } // String returns the string value for a given key. -func (c *IniConfigContainer) String(key string) (string, error) { - return c.getdata(key), nil +func (c *IniConfigContainer) String(key string) string { + return c.getdata(key) } // DefaultString returns the string value for a given key. -// if err != nil return defaultVal -func (c *IniConfigContainer) DefaultString(key string, defaultVal string) string { - v, err := c.String(key) - if v == "" || err != nil { - return defaultVal +// if err != nil return defaultval +func (c *IniConfigContainer) DefaultString(key string, defaultval string) string { + v := c.String(key) + if v == "" { + return defaultval } return v } // Strings returns the []string value for a given key. // Return nil if config value does not exist or is empty. -func (c *IniConfigContainer) Strings(key string) ([]string, error) { - v, err := c.String(key) - if v == "" || err != nil { - return nil, err +func (c *IniConfigContainer) Strings(key string) []string { + v := c.String(key) + if v == "" { + return nil } - return strings.Split(v, ";"), nil + return strings.Split(v, ";") } // DefaultStrings returns the []string value for a given key. -// if err != nil return defaultVal -func (c *IniConfigContainer) DefaultStrings(key string, defaultVal []string) []string { - v, err := c.Strings(key) - if v == nil || err != nil { - return defaultVal +// if err != nil return defaultval +func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string { + v := c.Strings(key) + if v == nil { + return defaultval } return v } @@ -447,7 +437,7 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) { // Set writes a new value for key. // if write to one section, the key need be "section::key". // if the section is not existed, it panics. -func (c *IniConfigContainer) Set(key, val string) error { +func (c *IniConfigContainer) Set(key, value string) error { c.Lock() defer c.Unlock() if len(key) == 0 { @@ -470,7 +460,7 @@ func (c *IniConfigContainer) Set(key, val string) error { if _, ok := c.data[section]; !ok { c.data[section] = make(map[string]string) } - c.data[section][k] = val + c.data[section][k] = value return nil } @@ -509,20 +499,6 @@ func (c *IniConfigContainer) getdata(key string) string { return "" } -func (c *IniConfigContainer) Unmarshaler(prefix string, obj interface{}, opt ...DecodeOption) error { - if len(prefix) > 0 { - return errors.New("unsupported prefix params") - } - return mapstructure.Decode(c.data, obj) -} - func init() { Register("ini", &IniConfig{}) - - err := InitGlobalInstance("ini", "config/app.conf") - if err != nil { - logs.Warn("init global config instance failed. If you donot use this, just ignore it. ", err) - } } - -// Ignore this error diff --git a/adapter/config/ini_test.go b/config/ini_test.go similarity index 100% rename from adapter/config/ini_test.go rename to config/ini_test.go diff --git a/core/config/json/json.go b/config/json.go similarity index 71% rename from core/config/json/json.go rename to config/json.go index 672d2787..c4ef25cd 100644 --- a/core/config/json/json.go +++ b/config/json.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package json +package config import ( "encoding/json" @@ -23,11 +23,6 @@ import ( "strconv" "strings" "sync" - - "github.com/mitchellh/mapstructure" - - "github.com/astaxie/beego/core/config" - "github.com/astaxie/beego/core/logs" ) // JSONConfig is a json config parser and implements Config interface. @@ -35,7 +30,7 @@ type JSONConfig struct { } // Parse returns a ConfigContainer with parsed json config map. -func (js *JSONConfig) Parse(filename string) (config.Configer, error) { +func (js *JSONConfig) Parse(filename string) (Configer, error) { file, err := os.Open(filename) if err != nil { return nil, err @@ -50,7 +45,7 @@ func (js *JSONConfig) Parse(filename string) (config.Configer, error) { } // ParseData returns a ConfigContainer with json string -func (js *JSONConfig) ParseData(data []byte) (config.Configer, error) { +func (js *JSONConfig) ParseData(data []byte) (Configer, error) { x := &JSONConfigContainer{ data: make(map[string]interface{}), } @@ -64,72 +59,34 @@ func (js *JSONConfig) ParseData(data []byte) (config.Configer, error) { x.data["rootArray"] = wrappingArray } - x.data = config.ExpandValueEnvForMap(x.data) + x.data = ExpandValueEnvForMap(x.data) return x, nil } -// JSONConfigContainer is a config which represents the json configuration. +// JSONConfigContainer A Config represents the json configuration. // Only when get value, support key as section:name type. type JSONConfigContainer struct { data map[string]interface{} sync.RWMutex } -func (c *JSONConfigContainer) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error { - sub, err := c.sub(prefix) - if err != nil { - return err - } - return mapstructure.Decode(sub, obj) -} - -func (c *JSONConfigContainer) Sub(key string) (config.Configer, error) { - sub, err := c.sub(key) - if err != nil { - return nil, err - } - return &JSONConfigContainer{ - data: sub, - }, nil -} - -func (c *JSONConfigContainer) sub(key string) (map[string]interface{}, error) { - if key == "" { - return c.data, nil - } - value, ok := c.data[key] - if !ok { - return nil, errors.New(fmt.Sprintf("key is not found: %s", key)) - } - - res, ok := value.(map[string]interface{}) - if !ok { - return nil, errors.New(fmt.Sprintf("the type of value is invalid, key: %s", key)) - } - return res, nil -} - -func (c *JSONConfigContainer) OnChange(key string, fn func(value string)) { - logs.Warn("unsupported operation") -} - // Bool returns the boolean value for a given key. func (c *JSONConfigContainer) Bool(key string) (bool, error) { val := c.getData(key) if val != nil { - return config.ParseBool(val) + return ParseBool(val) } return false, fmt.Errorf("not exist key: %q", key) } // DefaultBool return the bool value if has no error // otherwise return the defaultval -func (c *JSONConfigContainer) DefaultBool(key string, defaultVal bool) bool { +func (c *JSONConfigContainer) DefaultBool(key string, defaultval bool) bool { if v, err := c.Bool(key); err == nil { return v } - return defaultVal + return defaultval } // Int returns the integer value for a given key. @@ -148,11 +105,11 @@ func (c *JSONConfigContainer) Int(key string) (int, error) { // DefaultInt returns the integer value for a given key. // if err != nil return defaultval -func (c *JSONConfigContainer) DefaultInt(key string, defaultVal int) int { +func (c *JSONConfigContainer) DefaultInt(key string, defaultval int) int { if v, err := c.Int(key); err == nil { return v } - return defaultVal + return defaultval } // Int64 returns the int64 value for a given key. @@ -169,11 +126,11 @@ func (c *JSONConfigContainer) Int64(key string) (int64, error) { // DefaultInt64 returns the int64 value for a given key. // if err != nil return defaultval -func (c *JSONConfigContainer) DefaultInt64(key string, defaultVal int64) int64 { +func (c *JSONConfigContainer) DefaultInt64(key string, defaultval int64) int64 { if v, err := c.Int64(key); err == nil { return v } - return defaultVal + return defaultval } // Float returns the float value for a given key. @@ -190,50 +147,50 @@ func (c *JSONConfigContainer) Float(key string) (float64, error) { // DefaultFloat returns the float64 value for a given key. // if err != nil return defaultval -func (c *JSONConfigContainer) DefaultFloat(key string, defaultVal float64) float64 { +func (c *JSONConfigContainer) DefaultFloat(key string, defaultval float64) float64 { if v, err := c.Float(key); err == nil { return v } - return defaultVal + return defaultval } // String returns the string value for a given key. -func (c *JSONConfigContainer) String(key string) (string, error) { +func (c *JSONConfigContainer) String(key string) string { val := c.getData(key) if val != nil { if v, ok := val.(string); ok { - return v, nil + return v } } - return "", nil + return "" } // DefaultString returns the string value for a given key. // if err != nil return defaultval -func (c *JSONConfigContainer) DefaultString(key string, defaultVal string) string { +func (c *JSONConfigContainer) DefaultString(key string, defaultval string) string { // TODO FIXME should not use "" to replace non existence - if v, err := c.String(key); v != "" && err == nil { + if v := c.String(key); v != "" { return v } - return defaultVal + return defaultval } // Strings returns the []string value for a given key. -func (c *JSONConfigContainer) Strings(key string) ([]string, error) { - stringVal, err := c.String(key) - if stringVal == "" || err != nil { - return nil, err +func (c *JSONConfigContainer) Strings(key string) []string { + stringVal := c.String(key) + if stringVal == "" { + return nil } - return strings.Split(stringVal, ";"), nil + return strings.Split(c.String(key), ";") } // DefaultStrings returns the []string value for a given key. // if err != nil return defaultval -func (c *JSONConfigContainer) DefaultStrings(key string, defaultVal []string) []string { - if v, err := c.Strings(key); v != nil && err == nil { +func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string { + if v := c.Strings(key); v != nil { return v } - return defaultVal + return defaultval } // GetSection returns map for the given section @@ -308,5 +265,5 @@ func (c *JSONConfigContainer) getData(key string) interface{} { } func init() { - config.Register("json", &JSONConfig{}) + Register("json", &JSONConfig{}) } diff --git a/adapter/config/json_test.go b/config/json_test.go similarity index 100% rename from adapter/config/json_test.go rename to config/json_test.go diff --git a/core/config/xml/xml.go b/config/xml/xml.go similarity index 66% rename from core/config/xml/xml.go rename to config/xml/xml.go index 70f0c23c..494242d3 100644 --- a/core/config/xml/xml.go +++ b/config/xml/xml.go @@ -26,7 +26,7 @@ // // cnf, err := config.NewConfig("xml", "config.xml") // -// More docs http://beego.me/docs/module/config.md +//More docs http://beego.me/docs/module/config.md package xml import ( @@ -39,11 +39,7 @@ import ( "strings" "sync" - "github.com/mitchellh/mapstructure" - - "github.com/astaxie/beego/core/config" - "github.com/astaxie/beego/core/logs" - + "github.com/astaxie/beego/config" "github.com/beego/x2j" ) @@ -76,55 +72,12 @@ func (xc *Config) ParseData(data []byte) (config.Configer, error) { return x, nil } -// ConfigContainer is a Config which represents the xml configuration. +// ConfigContainer A Config represents the xml configuration. type ConfigContainer struct { data map[string]interface{} sync.Mutex } -// Unmarshaler is a little be inconvenient since the xml library doesn't know type. -// So when you use -// 1 -// The "1" is a string, not int -func (c *ConfigContainer) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error { - sub, err := c.sub(prefix) - if err != nil { - return err - } - return mapstructure.Decode(sub, obj) -} - -func (c *ConfigContainer) Sub(key string) (config.Configer, error) { - sub, err := c.sub(key) - if err != nil { - return nil, err - } - - return &ConfigContainer{ - data: sub, - }, nil - -} - -func (c *ConfigContainer) sub(key string) (map[string]interface{}, error) { - if key == "" { - return c.data, nil - } - value, ok := c.data[key] - if !ok { - return nil, errors.New(fmt.Sprintf("the key is not found: %s", key)) - } - res, ok := value.(map[string]interface{}) - if !ok { - return nil, errors.New(fmt.Sprintf("the value of this key is not a structure: %s", key)) - } - return res, nil -} - -func (c *ConfigContainer) OnChange(key string, fn func(value string)) { - logs.Warn("Unsupported operation") -} - // Bool returns the boolean value for a given key. func (c *ConfigContainer) Bool(key string) (bool, error) { if v := c.data[key]; v != nil { @@ -134,11 +87,11 @@ func (c *ConfigContainer) Bool(key string) (bool, error) { } // DefaultBool return the bool value if has no error -// otherwise return the defaultVal -func (c *ConfigContainer) DefaultBool(key string, defaultVal bool) bool { +// otherwise return the defaultval +func (c *ConfigContainer) DefaultBool(key string, defaultval bool) bool { v, err := c.Bool(key) if err != nil { - return defaultVal + return defaultval } return v } @@ -149,11 +102,11 @@ func (c *ConfigContainer) Int(key string) (int, error) { } // DefaultInt returns the integer value for a given key. -// if err != nil return defaultVal -func (c *ConfigContainer) DefaultInt(key string, defaultVal int) int { +// if err != nil return defaultval +func (c *ConfigContainer) DefaultInt(key string, defaultval int) int { v, err := c.Int(key) if err != nil { - return defaultVal + return defaultval } return v } @@ -164,11 +117,11 @@ func (c *ConfigContainer) Int64(key string) (int64, error) { } // DefaultInt64 returns the int64 value for a given key. -// if err != nil return defaultVal -func (c *ConfigContainer) DefaultInt64(key string, defaultVal int64) int64 { +// if err != nil return defaultval +func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 { v, err := c.Int64(key) if err != nil { - return defaultVal + return defaultval } return v @@ -180,48 +133,48 @@ func (c *ConfigContainer) Float(key string) (float64, error) { } // DefaultFloat returns the float64 value for a given key. -// if err != nil return defaultVal -func (c *ConfigContainer) DefaultFloat(key string, defaultVal float64) float64 { +// if err != nil return defaultval +func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 { v, err := c.Float(key) if err != nil { - return defaultVal + return defaultval } return v } // String returns the string value for a given key. -func (c *ConfigContainer) String(key string) (string, error) { +func (c *ConfigContainer) String(key string) string { if v, ok := c.data[key].(string); ok { - return v, nil + return v } - return "", nil + return "" } // DefaultString returns the string value for a given key. -// if err != nil return defaultVal -func (c *ConfigContainer) DefaultString(key string, defaultVal string) string { - v, err := c.String(key) - if v == "" || err != nil { - return defaultVal +// if err != nil return defaultval +func (c *ConfigContainer) DefaultString(key string, defaultval string) string { + v := c.String(key) + if v == "" { + return defaultval } return v } // Strings returns the []string value for a given key. -func (c *ConfigContainer) Strings(key string) ([]string, error) { - v, err := c.String(key) - if v == "" || err != nil { - return nil, err +func (c *ConfigContainer) Strings(key string) []string { + v := c.String(key) + if v == "" { + return nil } - return strings.Split(v, ";"), nil + return strings.Split(v, ";") } // DefaultStrings returns the []string value for a given key. -// if err != nil return defaultVal -func (c *ConfigContainer) DefaultStrings(key string, defaultVal []string) []string { - v, err := c.Strings(key) - if v == nil || err != nil { - return defaultVal +// if err != nil return defaultval +func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string { + v := c.Strings(key) + if v == nil { + return defaultval } return v } diff --git a/adapter/config/xml/xml_test.go b/config/xml/xml_test.go similarity index 98% rename from adapter/config/xml/xml_test.go rename to config/xml/xml_test.go index ae9b209e..346c866e 100644 --- a/adapter/config/xml/xml_test.go +++ b/config/xml/xml_test.go @@ -19,7 +19,7 @@ import ( "os" "testing" - "github.com/astaxie/beego/adapter/config" + "github.com/astaxie/beego/config" ) func TestXML(t *testing.T) { diff --git a/core/config/yaml/yaml.go b/config/yaml/yaml.go similarity index 71% rename from core/config/yaml/yaml.go rename to config/yaml/yaml.go index 71daabee..5def2da3 100644 --- a/core/config/yaml/yaml.go +++ b/config/yaml/yaml.go @@ -26,7 +26,7 @@ // // cnf, err := config.NewConfig("yaml", "config.yaml") // -// More docs http://beego.me/docs/module/config.md +//More docs http://beego.me/docs/module/config.md package yaml import ( @@ -40,11 +40,8 @@ import ( "strings" "sync" + "github.com/astaxie/beego/config" "github.com/beego/goyaml2" - "gopkg.in/yaml.v2" - - "github.com/astaxie/beego/core/config" - "github.com/astaxie/beego/core/logs" ) // Config is a yaml config parser and implements Config interface. @@ -119,63 +116,12 @@ func parseYML(buf []byte) (cnf map[string]interface{}, err error) { return } -// ConfigContainer is a config which represents the yaml configuration. +// ConfigContainer A Config represents the yaml configuration. type ConfigContainer struct { data map[string]interface{} sync.RWMutex } -// Unmarshaler is similar to Sub -func (c *ConfigContainer) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error { - sub, err := c.sub(prefix) - if err != nil { - return err - } - - bytes, err := yaml.Marshal(sub) - if err != nil { - return err - } - return yaml.Unmarshal(bytes, obj) -} - -func (c *ConfigContainer) Sub(key string) (config.Configer, error) { - sub, err := c.sub(key) - if err != nil { - return nil, err - } - return &ConfigContainer{ - data: sub, - }, nil -} - -func (c *ConfigContainer) sub(key string) (map[string]interface{}, error) { - tmpData := c.data - keys := strings.Split(key, ".") - for idx, k := range keys { - if v, ok := tmpData[k]; ok { - switch v.(type) { - case map[string]interface{}: - { - tmpData = v.(map[string]interface{}) - if idx == len(keys)-1 { - return tmpData, nil - } - } - default: - return nil, errors.New(fmt.Sprintf("the key is invalid: %s", key)) - } - } - } - - return tmpData, nil -} - -func (c *ConfigContainer) OnChange(key string, fn func(value string)) { - // do nothing - logs.Warn("Unsupported operation: OnChange") -} - // Bool returns the boolean value for a given key. func (c *ConfigContainer) Bool(key string) (bool, error) { v, err := c.getData(key) @@ -186,11 +132,11 @@ func (c *ConfigContainer) Bool(key string) (bool, error) { } // DefaultBool return the bool value if has no error -// otherwise return the defaultVal -func (c *ConfigContainer) DefaultBool(key string, defaultVal bool) bool { +// otherwise return the defaultval +func (c *ConfigContainer) DefaultBool(key string, defaultval bool) bool { v, err := c.Bool(key) if err != nil { - return defaultVal + return defaultval } return v } @@ -208,11 +154,11 @@ func (c *ConfigContainer) Int(key string) (int, error) { } // DefaultInt returns the integer value for a given key. -// if err != nil return defaultVal -func (c *ConfigContainer) DefaultInt(key string, defaultVal int) int { +// if err != nil return defaultval +func (c *ConfigContainer) DefaultInt(key string, defaultval int) int { v, err := c.Int(key) if err != nil { - return defaultVal + return defaultval } return v } @@ -228,11 +174,11 @@ func (c *ConfigContainer) Int64(key string) (int64, error) { } // DefaultInt64 returns the int64 value for a given key. -// if err != nil return defaultVal -func (c *ConfigContainer) DefaultInt64(key string, defaultVal int64) int64 { +// if err != nil return defaultval +func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 { v, err := c.Int64(key) if err != nil { - return defaultVal + return defaultval } return v } @@ -252,50 +198,50 @@ func (c *ConfigContainer) Float(key string) (float64, error) { } // DefaultFloat returns the float64 value for a given key. -// if err != nil return defaultVal -func (c *ConfigContainer) DefaultFloat(key string, defaultVal float64) float64 { +// if err != nil return defaultval +func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 { v, err := c.Float(key) if err != nil { - return defaultVal + return defaultval } return v } // String returns the string value for a given key. -func (c *ConfigContainer) String(key string) (string, error) { +func (c *ConfigContainer) String(key string) string { if v, err := c.getData(key); err == nil { if vv, ok := v.(string); ok { - return vv, nil + return vv } } - return "", nil + return "" } // DefaultString returns the string value for a given key. -// if err != nil return defaultVal -func (c *ConfigContainer) DefaultString(key string, defaultVal string) string { - v, err := c.String(key) - if v == "" || err != nil { - return defaultVal +// if err != nil return defaultval +func (c *ConfigContainer) DefaultString(key string, defaultval string) string { + v := c.String(key) + if v == "" { + return defaultval } return v } // Strings returns the []string value for a given key. -func (c *ConfigContainer) Strings(key string) ([]string, error) { - v, err := c.String(key) - if v == "" || err != nil { - return nil, err +func (c *ConfigContainer) Strings(key string) []string { + v := c.String(key) + if v == "" { + return nil } - return strings.Split(v, ";"), nil + return strings.Split(v, ";") } // DefaultStrings returns the []string value for a given key. -// if err != nil return defaultVal -func (c *ConfigContainer) DefaultStrings(key string, defaultVal []string) []string { - v, err := c.Strings(key) - if v == nil || err != nil { - return defaultVal +// if err != nil return defaultval +func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string { + v := c.Strings(key) + if v == nil { + return defaultval } return v } @@ -342,7 +288,7 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) { c.RLock() defer c.RUnlock() - keys := strings.Split(c.key(key), ".") + keys := strings.Split(key, ".") tmpData := c.data for idx, k := range keys { if v, ok := tmpData[k]; ok { @@ -350,7 +296,7 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) { case map[string]interface{}: { tmpData = v.(map[string]interface{}) - if idx == len(keys)-1 { + if idx == len(keys) - 1 { return tmpData, nil } } @@ -365,10 +311,6 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) { return nil, fmt.Errorf("not exist key %q", key) } -func (c *ConfigContainer) key(key string) string { - return key -} - func init() { config.Register("yaml", &Config{}) } diff --git a/adapter/config/yaml/yaml_test.go b/config/yaml/yaml_test.go similarity index 98% rename from adapter/config/yaml/yaml_test.go rename to config/yaml/yaml_test.go index a72e435e..49cc1d1e 100644 --- a/adapter/config/yaml/yaml_test.go +++ b/config/yaml/yaml_test.go @@ -19,7 +19,7 @@ import ( "os" "testing" - "github.com/astaxie/beego/adapter/config" + "github.com/astaxie/beego/config" ) func TestYaml(t *testing.T) { diff --git a/server/web/config_test.go b/config_test.go similarity index 95% rename from server/web/config_test.go rename to config_test.go index 0129ebb4..5f71f1c3 100644 --- a/server/web/config_test.go +++ b/config_test.go @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "encoding/json" "reflect" "testing" - beeJson "github.com/astaxie/beego/core/config/json" + "github.com/astaxie/beego/config" ) func TestDefaults(t *testing.T) { @@ -35,7 +35,7 @@ func TestDefaults(t *testing.T) { func TestAssignConfig_01(t *testing.T) { _BConfig := &Config{} _BConfig.AppName = "beego_test" - jcf := &beeJson.JSONConfig{} + jcf := &config.JSONConfig{} ac, _ := jcf.ParseData([]byte(`{"AppName":"beego_json"}`)) assignSingleConfig(_BConfig, ac) if _BConfig.AppName != "beego_json" { @@ -73,7 +73,7 @@ func TestAssignConfig_02(t *testing.T) { configMap["SessionProviderConfig"] = "file" configMap["FileLineNum"] = true - jcf := &beeJson.JSONConfig{} + jcf := &config.JSONConfig{} bs, _ = json.Marshal(configMap) ac, _ := jcf.ParseData(bs) @@ -109,7 +109,7 @@ func TestAssignConfig_02(t *testing.T) { } func TestAssignConfig_03(t *testing.T) { - jcf := &beeJson.JSONConfig{} + jcf := &config.JSONConfig{} ac, _ := jcf.ParseData([]byte(`{"AppName":"beego"}`)) ac.Set("AppName", "test_app") ac.Set("RunMode", "online") diff --git a/server/web/context/acceptencoder.go b/context/acceptencoder.go similarity index 86% rename from server/web/context/acceptencoder.go rename to context/acceptencoder.go index 8ed6a853..b4e2492c 100644 --- a/server/web/context/acceptencoder.go +++ b/context/acceptencoder.go @@ -28,18 +28,18 @@ import ( ) var ( - // Default size==20B same as nginx + //Default size==20B same as nginx defaultGzipMinLength = 20 - // Content will only be compressed if content length is either unknown or greater than gzipMinLength. + //Content will only be compressed if content length is either unknown or greater than gzipMinLength. gzipMinLength = defaultGzipMinLength - // Compression level used for deflate compression. (0-9). + //The compression level used for deflate compression. (0-9). gzipCompressLevel int - // List of HTTP methods to compress. If not set, only GET requests are compressed. + //List of HTTP methods to compress. If not set, only GET requests are compressed. includedMethods map[string]bool getMethodOnly bool ) -// InitGzip initializes the gzipcompress +// InitGzip init the gzipcompress func InitGzip(minLength, compressLevel int, methods []string) { if minLength >= 0 { gzipMinLength = minLength @@ -98,9 +98,9 @@ func (ac acceptEncoder) put(wr resetWriter, level int) { } wr.Reset(nil) - // notice - // compressionLevel==BestCompression DOES NOT MATTER - // sync.Pool will not memory leak + //notice + //compressionLevel==BestCompression DOES NOT MATTER + //sync.Pool will not memory leak switch level { case gzipCompressLevel: @@ -119,10 +119,10 @@ var ( bestCompressionPool: &sync.Pool{New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, flate.BestCompression); return wr }}, } - // According to: http://tools.ietf.org/html/rfc2616#section-3.5 the deflate compress in http is zlib indeed - // deflate - // The "zlib" format defined in RFC 1950 [31] in combination with - // the "deflate" compression mechanism described in RFC 1951 [29]. + //according to the sec :http://tools.ietf.org/html/rfc2616#section-3.5 ,the deflate compress in http is zlib indeed + //deflate + //The "zlib" format defined in RFC 1950 [31] in combination with + //the "deflate" compression mechanism described in RFC 1951 [29]. deflateCompressEncoder = acceptEncoder{ name: "deflate", levelEncode: func(level int) resetWriter { wr, _ := zlib.NewWriterLevel(nil, level); return wr }, @@ -145,7 +145,7 @@ func WriteFile(encoding string, writer io.Writer, file *os.File) (bool, string, return writeLevel(encoding, writer, file, flate.BestCompression) } -// WriteBody reads writes content to writer by the specific encoding(gzip/deflate) +// WriteBody reads writes content to writer by the specific encoding(gzip/deflate) func WriteBody(encoding string, writer io.Writer, content []byte) (bool, string, error) { if encoding == "" || len(content) < gzipMinLength { _, err := writer.Write(content) @@ -154,8 +154,8 @@ func WriteBody(encoding string, writer io.Writer, content []byte) (bool, string, return writeLevel(encoding, writer, bytes.NewReader(content), gzipCompressLevel) } -// writeLevel reads from reader and writes to writer by specific encoding and compress level. -// The compress level is defined by deflate package +// writeLevel reads from reader,writes to writer by specific encoding and compress level +// the compress level is defined by deflate package func writeLevel(encoding string, writer io.Writer, reader io.Reader, level int) (bool, string, error) { var outputWriter resetWriter var err error diff --git a/server/web/context/acceptencoder_test.go b/context/acceptencoder_test.go similarity index 100% rename from server/web/context/acceptencoder_test.go rename to context/acceptencoder_test.go diff --git a/server/web/context/context.go b/context/context.go similarity index 80% rename from server/web/context/context.go rename to context/context.go index 53ed3d01..de248ed2 100644 --- a/server/web/context/context.go +++ b/context/context.go @@ -35,10 +35,10 @@ import ( "strings" "time" - "github.com/astaxie/beego/core/utils" + "github.com/astaxie/beego/utils" ) -// Commonly used mime-types +//commonly used mime-types const ( ApplicationJSON = "application/json" ApplicationXML = "application/xml" @@ -55,7 +55,7 @@ func NewContext() *Context { } // Context Http request context struct including BeegoInput, BeegoOutput, http.Request and http.ResponseWriter. -// BeegoInput and BeegoOutput provides an api to operate request and response more easily. +// BeegoInput and BeegoOutput provides some api to operate request and response more easily. type Context struct { Input *BeegoInput Output *BeegoOutput @@ -64,7 +64,7 @@ type Context struct { _xsrfToken string } -// Reset initializes Context, BeegoInput and BeegoOutput +// Reset init Context, BeegoInput and BeegoOutput func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) { ctx.Request = r if ctx.ResponseWriter == nil { @@ -76,36 +76,37 @@ func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) { ctx._xsrfToken = "" } -// Redirect redirects to localurl with http header status code. +// Redirect does redirection to localurl with http header status code. func (ctx *Context) Redirect(status int, localurl string) { http.Redirect(ctx.ResponseWriter, ctx.Request, localurl, status) } -// Abort stops the request. -// If beego.ErrorMaps exists, panic body. +// Abort stops this request. +// if beego.ErrorMaps exists, panic body. func (ctx *Context) Abort(status int, body string) { ctx.Output.SetStatus(status) panic(body) } -// WriteString writes a string to response body. +// WriteString Write string to response body. +// it sends response body. func (ctx *Context) WriteString(content string) { ctx.ResponseWriter.Write([]byte(content)) } -// GetCookie gets a cookie from a request for a given key. -// (Alias of BeegoInput.Cookie) +// GetCookie Get cookie from request by a given key. +// It's alias of BeegoInput.Cookie. func (ctx *Context) GetCookie(key string) string { return ctx.Input.Cookie(key) } -// SetCookie sets a cookie for a response. -// (Alias of BeegoOutput.Cookie) +// SetCookie Set cookie for response. +// It's alias of BeegoOutput.Cookie. func (ctx *Context) SetCookie(name string, value string, others ...interface{}) { ctx.Output.Cookie(name, value, others...) } -// GetSecureCookie gets a secure cookie from a request for a given key. +// GetSecureCookie Get secure cookie from request by a given key. func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) { val := ctx.Input.Cookie(key) if val == "" { @@ -132,7 +133,7 @@ func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) { return string(res), true } -// SetSecureCookie sets a secure cookie for a response. +// SetSecureCookie Set Secure cookie for response. func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) { vs := base64.URLEncoding.EncodeToString([]byte(value)) timestamp := strconv.FormatInt(time.Now().UnixNano(), 10) @@ -143,21 +144,21 @@ func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interf ctx.Output.Cookie(name, cookie, others...) } -// XSRFToken creates and returns an xsrf token string +// XSRFToken creates a xsrf token string and returns. func (ctx *Context) XSRFToken(key string, expire int64) string { if ctx._xsrfToken == "" { token, ok := ctx.GetSecureCookie(key, "_xsrf") if !ok { token = string(utils.RandomCreateBytes(32)) - ctx.SetSecureCookie(key, "_xsrf", token, expire, "", "", true, true) + ctx.SetSecureCookie(key, "_xsrf", token, expire) } ctx._xsrfToken = token } return ctx._xsrfToken } -// CheckXSRFCookie checks if the XSRF token in this request is valid or not. -// The token can be provided in the request header in the form "X-Xsrftoken" or "X-CsrfToken" +// CheckXSRFCookie checks xsrf token in this request is valid or not. +// the token can provided in request header "X-Xsrftoken" and "X-CsrfToken" // or in form field value named as "_xsrf". func (ctx *Context) CheckXSRFCookie() bool { token := ctx.Input.Query("_xsrf") @@ -194,8 +195,8 @@ func (ctx *Context) RenderMethodResult(result interface{}) { } } -// Response is a wrapper for the http.ResponseWriter -// Started: if true, response was already written to so the other handler will not be executed +//Response is a wrapper for the http.ResponseWriter +//started set to true if response was written to then don't execute other handler type Response struct { http.ResponseWriter Started bool @@ -209,16 +210,16 @@ func (r *Response) reset(rw http.ResponseWriter) { r.Started = false } -// Write writes the data to the connection as part of a HTTP reply, -// and sets `Started` to true. -// Started: if true, the response was already sent +// Write writes the data to the connection as part of an HTTP reply, +// and sets `started` to true. +// started means the response has sent out. func (r *Response) Write(p []byte) (int, error) { r.Started = true return r.ResponseWriter.Write(p) } -// WriteHeader sends a HTTP response header with status code, -// and sets `Started` to true. +// WriteHeader sends an HTTP response header with status code, +// and sets `started` to true. func (r *Response) WriteHeader(code int) { if r.Status > 0 { //prevent multiple response.WriteHeader calls diff --git a/server/web/context/context_test.go b/context/context_test.go similarity index 100% rename from server/web/context/context_test.go rename to context/context_test.go diff --git a/server/web/context/input.go b/context/input.go similarity index 92% rename from server/web/context/input.go rename to context/input.go index 504838a3..7b522c36 100644 --- a/server/web/context/input.go +++ b/context/input.go @@ -29,7 +29,7 @@ import ( "strings" "sync" - "github.com/astaxie/beego/server/web/session" + "github.com/astaxie/beego/session" ) // Regexes for checking the accept headers @@ -43,7 +43,7 @@ var ( ) // BeegoInput operates the http request header, data, cookie and body. -// Contains router params and current session. +// it also contains router params and current session. type BeegoInput struct { Context *Context CruSession session.Store @@ -56,7 +56,7 @@ type BeegoInput struct { RunController reflect.Type } -// NewInput returns the BeegoInput generated by context. +// NewInput return BeegoInput generated by Context. func NewInput() *BeegoInput { return &BeegoInput{ pnames: make([]string, 0, maxParam), @@ -65,7 +65,7 @@ func NewInput() *BeegoInput { } } -// Reset initializes the BeegoInput +// Reset init the BeegoInput func (input *BeegoInput) Reset(ctx *Context) { input.Context = ctx input.CruSession = nil @@ -77,27 +77,27 @@ func (input *BeegoInput) Reset(ctx *Context) { input.RequestBody = []byte{} } -// Protocol returns the request protocol name, such as HTTP/1.1 . +// Protocol returns request protocol name, such as HTTP/1.1 . func (input *BeegoInput) Protocol() string { return input.Context.Request.Proto } -// URI returns the full request url with query, string and fragment. +// URI returns full request url with query string, fragment. func (input *BeegoInput) URI() string { return input.Context.Request.RequestURI } -// URL returns the request url path (without query, string and fragment). +// URL returns request url path (without query string, fragment). func (input *BeegoInput) URL() string { - return input.Context.Request.URL.Path + return input.Context.Request.URL.EscapedPath() } -// Site returns the base site url as scheme://domain type. +// Site returns base site url as scheme://domain type. func (input *BeegoInput) Site() string { return input.Scheme() + "://" + input.Domain() } -// Scheme returns the request scheme as "http" or "https". +// Scheme returns request scheme as "http" or "https". func (input *BeegoInput) Scheme() string { if scheme := input.Header("X-Forwarded-Proto"); scheme != "" { return scheme @@ -111,13 +111,14 @@ func (input *BeegoInput) Scheme() string { return "https" } -// Domain returns the host name (alias of host method) +// Domain returns host name. +// Alias of Host method. func (input *BeegoInput) Domain() string { return input.Host() } -// Host returns the host name. -// If no host info in request, return localhost. +// Host returns host name. +// if no host info in request, return localhost. func (input *BeegoInput) Host() string { if input.Context.Request.Host != "" { if hostPart, _, err := net.SplitHostPort(input.Context.Request.Host); err == nil { @@ -133,7 +134,7 @@ func (input *BeegoInput) Method() string { return input.Context.Request.Method } -// Is returns the boolean value of this request is on given method, such as Is("POST"). +// Is returns boolean of this request is on given method, such as Is("POST"). func (input *BeegoInput) Is(method string) bool { return input.Method() == method } @@ -173,7 +174,7 @@ func (input *BeegoInput) IsPatch() bool { return input.Is("PATCH") } -// IsAjax returns boolean of is this request generated by ajax. +// IsAjax returns boolean of this request is generated by ajax. func (input *BeegoInput) IsAjax() bool { return input.Header("X-Requested-With") == "XMLHttpRequest" } @@ -250,7 +251,7 @@ func (input *BeegoInput) Refer() string { } // SubDomains returns sub domain string. -// if aa.bb.domain.com, returns aa.bb +// if aa.bb.domain.com, returns aa.bb . func (input *BeegoInput) SubDomains() string { parts := strings.Split(input.Host(), ".") if len(parts) >= 3 { @@ -305,7 +306,7 @@ func (input *BeegoInput) Params() map[string]string { return m } -// SetParam sets the param with key and value +// SetParam will set the param with key and value func (input *BeegoInput) SetParam(key, val string) { // check if already exists for i, v := range input.pnames { @@ -318,8 +319,9 @@ func (input *BeegoInput) SetParam(key, val string) { input.pnames = append(input.pnames, key) } -// ResetParams clears any of the input's params -// Used to clear parameters so they may be reset between filter passes. +// ResetParams clears any of the input's Params +// This function is used to clear parameters so they may be reset between filter +// passes. func (input *BeegoInput) ResetParams() { input.pnames = input.pnames[:0] input.pvalues = input.pvalues[:0] @@ -331,14 +333,8 @@ func (input *BeegoInput) Query(key string) string { return val } if input.Context.Request.Form == nil { - input.dataLock.Lock() - if input.Context.Request.Form == nil { - input.Context.Request.ParseForm() - } - input.dataLock.Unlock() + input.Context.Request.ParseForm() } - input.dataLock.RLock() - defer input.dataLock.RUnlock() return input.Context.Request.Form.Get(key) } @@ -361,7 +357,7 @@ func (input *BeegoInput) Cookie(key string) string { // Session returns current session item value by a given key. // if non-existed, return nil. func (input *BeegoInput) Session(key interface{}) interface{} { - return input.CruSession.Get(nil, key) + return input.CruSession.Get(key) } // CopyBody returns the raw request body data as bytes. @@ -389,7 +385,7 @@ func (input *BeegoInput) CopyBody(MaxMemory int64) []byte { return requestbody } -// Data returns the implicit data in the input +// Data return the implicit data in the input func (input *BeegoInput) Data() map[interface{}]interface{} { input.dataLock.Lock() defer input.dataLock.Unlock() @@ -410,7 +406,7 @@ func (input *BeegoInput) GetData(key interface{}) interface{} { } // SetData stores data with given key in this context. -// This data is only available in this context. +// This data are only available in this context. func (input *BeegoInput) SetData(key, val interface{}) { input.dataLock.Lock() defer input.dataLock.Unlock() @@ -420,10 +416,10 @@ func (input *BeegoInput) SetData(key, val interface{}) { input.data[key] = val } -// ParseFormOrMultiForm parseForm or parseMultiForm based on Content-type -func (input *BeegoInput) ParseFormOrMultiForm(maxMemory int64) error { +// ParseFormOrMulitForm parseForm or parseMultiForm based on Content-type +func (input *BeegoInput) ParseFormOrMulitForm(maxMemory int64) error { // Parse the body depending on the content type. - if input.IsUpload() { + if strings.Contains(input.Header("Content-Type"), "multipart/form-data") { if err := input.Context.Request.ParseMultipartForm(maxMemory); err != nil { return errors.New("Error parsing request body:" + err.Error()) } diff --git a/server/web/context/input_test.go b/context/input_test.go similarity index 95% rename from server/web/context/input_test.go rename to context/input_test.go index 3a6c2e7b..db812a0f 100644 --- a/server/web/context/input_test.go +++ b/context/input_test.go @@ -205,13 +205,3 @@ func TestParams(t *testing.T) { } } -func BenchmarkQuery(b *testing.B) { - beegoInput := NewInput() - beegoInput.Context = NewContext() - beegoInput.Context.Request, _ = http.NewRequest("POST", "http://www.example.com/?q=foo", nil) - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - beegoInput.Query("q") - } - }) -} diff --git a/server/web/context/output.go b/context/output.go similarity index 88% rename from server/web/context/output.go rename to context/output.go index a6e83681..238dcf45 100644 --- a/server/web/context/output.go +++ b/context/output.go @@ -42,12 +42,12 @@ type BeegoOutput struct { } // NewOutput returns new BeegoOutput. -// Empty when initialized +// it contains nothing now. func NewOutput() *BeegoOutput { return &BeegoOutput{} } -// Reset initializes BeegoOutput +// Reset init BeegoOutput func (output *BeegoOutput) Reset(ctx *Context) { output.Context = ctx output.Status = 0 @@ -58,9 +58,9 @@ func (output *BeegoOutput) Header(key, val string) { output.Context.ResponseWriter.Header().Set(key, val) } -// Body sets the response body content. -// if EnableGzip, content is compressed. -// Sends out response body directly. +// Body sets response body content. +// if EnableGzip, compress content string. +// it sends out response body directly. func (output *BeegoOutput) Body(content []byte) error { var encoding string var buf = &bytes.Buffer{} @@ -85,13 +85,13 @@ func (output *BeegoOutput) Body(content []byte) error { return nil } -// Cookie sets a cookie value via given key. -// others: used to set a cookie's max age time, path,domain, secure and httponly. +// Cookie sets cookie value via given key. +// others are ordered as cookie's max age time, path,domain, secure and httponly. func (output *BeegoOutput) Cookie(name string, value string, others ...interface{}) { var b bytes.Buffer fmt.Fprintf(&b, "%s=%s", sanitizeName(name), sanitizeValue(value)) - // fix cookie not work in IE + //fix cookie not work in IE if len(others) > 0 { var maxAge int64 @@ -183,7 +183,7 @@ func errorRenderer(err error) Renderer { }) } -// JSON writes json to the response body. +// JSON writes json to response body. // if encoding is true, it converts utf-8 to \u0000 type. func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) error { output.Header("Content-Type", "application/json; charset=utf-8") @@ -204,7 +204,7 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) return output.Body(content) } -// YAML writes yaml to the response body. +// YAML writes yaml to response body. func (output *BeegoOutput) YAML(data interface{}) error { output.Header("Content-Type", "application/x-yaml; charset=utf-8") var content []byte @@ -217,7 +217,7 @@ func (output *BeegoOutput) YAML(data interface{}) error { return output.Body(content) } -// JSONP writes jsonp to the response body. +// JSONP writes jsonp to response body. func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error { output.Header("Content-Type", "application/javascript; charset=utf-8") var content []byte @@ -243,7 +243,7 @@ func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error { return output.Body(callbackContent.Bytes()) } -// XML writes xml string to the response body. +// XML writes xml string to response body. func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error { output.Header("Content-Type", "application/xml; charset=utf-8") var content []byte @@ -260,7 +260,7 @@ func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error { return output.Body(content) } -// ServeFormatted serves YAML, XML or JSON, depending on the value of the Accept header +// ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header func (output *BeegoOutput) ServeFormatted(data interface{}, hasIndent bool, hasEncode ...bool) { accept := output.Context.Input.Header("Accept") switch accept { @@ -274,7 +274,7 @@ func (output *BeegoOutput) ServeFormatted(data interface{}, hasIndent bool, hasE } // Download forces response for download file. -// Prepares the download response header automatically. +// it prepares the download response header automatically. func (output *BeegoOutput) Download(file string, filename ...string) { // check get file error, file not found or other error. if _, err := os.Stat(file); err != nil { @@ -323,61 +323,61 @@ func (output *BeegoOutput) ContentType(ext string) { } } -// SetStatus sets the response status code. -// Writes response header directly. +// SetStatus sets response status code. +// It writes response header directly. func (output *BeegoOutput) SetStatus(status int) { output.Status = status } -// IsCachable returns boolean of if this request is cached. +// IsCachable returns boolean of this request is cached. // HTTP 304 means cached. func (output *BeegoOutput) IsCachable() bool { return output.Status >= 200 && output.Status < 300 || output.Status == 304 } -// IsEmpty returns boolean of if this request is empty. +// IsEmpty returns boolean of this request is empty. // HTTP 201,204 and 304 means empty. func (output *BeegoOutput) IsEmpty() bool { return output.Status == 201 || output.Status == 204 || output.Status == 304 } -// IsOk returns boolean of if this request was ok. +// IsOk returns boolean of this request runs well. // HTTP 200 means ok. func (output *BeegoOutput) IsOk() bool { return output.Status == 200 } -// IsSuccessful returns boolean of this request was successful. +// IsSuccessful returns boolean of this request runs successfully. // HTTP 2xx means ok. func (output *BeegoOutput) IsSuccessful() bool { return output.Status >= 200 && output.Status < 300 } -// IsRedirect returns boolean of if this request is redirected. +// IsRedirect returns boolean of this request is redirection header. // HTTP 301,302,307 means redirection. func (output *BeegoOutput) IsRedirect() bool { return output.Status == 301 || output.Status == 302 || output.Status == 303 || output.Status == 307 } -// IsForbidden returns boolean of if this request is forbidden. +// IsForbidden returns boolean of this request is forbidden. // HTTP 403 means forbidden. func (output *BeegoOutput) IsForbidden() bool { return output.Status == 403 } -// IsNotFound returns boolean of if this request is not found. +// IsNotFound returns boolean of this request is not found. // HTTP 404 means not found. func (output *BeegoOutput) IsNotFound() bool { return output.Status == 404 } -// IsClientError returns boolean of if this request client sends error data. +// IsClientError returns boolean of this request client sends error data. // HTTP 4xx means client error. func (output *BeegoOutput) IsClientError() bool { return output.Status >= 400 && output.Status < 500 } -// IsServerError returns boolean of if this server handler errors. +// IsServerError returns boolean of this server handler errors. // HTTP 5xx means server internal error. func (output *BeegoOutput) IsServerError() bool { return output.Status >= 500 && output.Status < 600 @@ -404,5 +404,5 @@ func stringsToJSON(str string) string { // Session sets session item value with given key. func (output *BeegoOutput) Session(name interface{}, value interface{}) { - output.Context.Input.CruSession.Set(nil, name, value) + output.Context.Input.CruSession.Set(name, value) } diff --git a/server/web/context/param/conv.go b/context/param/conv.go similarity index 95% rename from server/web/context/param/conv.go rename to context/param/conv.go index fe3388b6..c200e008 100644 --- a/server/web/context/param/conv.go +++ b/context/param/conv.go @@ -4,8 +4,8 @@ import ( "fmt" "reflect" - "github.com/astaxie/beego/core/logs" - beecontext "github.com/astaxie/beego/server/web/context" + beecontext "github.com/astaxie/beego/context" + "github.com/astaxie/beego/logs" ) // ConvertParams converts http method params to values that will be passed to the method controller as arguments diff --git a/server/web/context/param/methodparams.go b/context/param/methodparams.go similarity index 91% rename from server/web/context/param/methodparams.go rename to context/param/methodparams.go index b5ccbdd0..cd6708a2 100644 --- a/server/web/context/param/methodparams.go +++ b/context/param/methodparams.go @@ -22,7 +22,7 @@ const ( header ) -// New creates a new MethodParam with name and specific options +//New creates a new MethodParam with name and specific options func New(name string, opts ...MethodParamOption) *MethodParam { return newParam(name, nil, opts) } @@ -35,7 +35,7 @@ func newParam(name string, parser paramParser, opts []MethodParamOption) (param return } -// Make creates an array of MethodParmas or an empty array +//Make creates an array of MethodParmas or an empty array func Make(list ...*MethodParam) []*MethodParam { if len(list) > 0 { return list diff --git a/server/web/context/param/options.go b/context/param/options.go similarity index 100% rename from server/web/context/param/options.go rename to context/param/options.go diff --git a/server/web/context/param/parsers.go b/context/param/parsers.go similarity index 100% rename from server/web/context/param/parsers.go rename to context/param/parsers.go diff --git a/server/web/context/param/parsers_test.go b/context/param/parsers_test.go similarity index 98% rename from server/web/context/param/parsers_test.go rename to context/param/parsers_test.go index 81a821f1..7065a28e 100644 --- a/server/web/context/param/parsers_test.go +++ b/context/param/parsers_test.go @@ -1,10 +1,8 @@ package param -import ( - "reflect" - "testing" - "time" -) +import "testing" +import "reflect" +import "time" type testDefinition struct { strValue string diff --git a/server/web/context/renderer.go b/context/renderer.go similarity index 77% rename from server/web/context/renderer.go rename to context/renderer.go index 5a078332..36a7cb53 100644 --- a/server/web/context/renderer.go +++ b/context/renderer.go @@ -1,6 +1,6 @@ package context -// Renderer defines a http response renderer +// Renderer defines an http response renderer type Renderer interface { Render(ctx *Context) } diff --git a/adapter/context/response.go b/context/response.go similarity index 83% rename from adapter/context/response.go rename to context/response.go index 24e196a4..9c3c715a 100644 --- a/adapter/context/response.go +++ b/context/response.go @@ -1,15 +1,16 @@ package context import ( - "net/http" "strconv" + + "net/http" ) const ( - // BadRequest indicates http error 400 + //BadRequest indicates http error 400 BadRequest StatusCode = http.StatusBadRequest - // NotFound indicates http error 404 + //NotFound indicates http error 404 NotFound StatusCode = http.StatusNotFound ) diff --git a/server/web/controller.go b/controller.go similarity index 98% rename from server/web/controller.go rename to controller.go index 3a1b9837..0e8853b3 100644 --- a/server/web/controller.go +++ b/controller.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "bytes" @@ -28,10 +28,9 @@ import ( "strconv" "strings" - "github.com/astaxie/beego/server/web/session" - - "github.com/astaxie/beego/server/web/context" - "github.com/astaxie/beego/server/web/context/param" + "github.com/astaxie/beego/context" + "github.com/astaxie/beego/context/param" + "github.com/astaxie/beego/session" ) var ( @@ -622,7 +621,7 @@ func (c *Controller) SetSession(name interface{}, value interface{}) { if c.CruSession == nil { c.StartSession() } - c.CruSession.Set(nil, name, value) + c.CruSession.Set(name, value) } // GetSession gets value from session. @@ -630,7 +629,7 @@ func (c *Controller) GetSession(name interface{}) interface{} { if c.CruSession == nil { c.StartSession() } - return c.CruSession.Get(nil, name) + return c.CruSession.Get(name) } // DelSession removes value from session. @@ -638,14 +637,14 @@ func (c *Controller) DelSession(name interface{}) { if c.CruSession == nil { c.StartSession() } - c.CruSession.Delete(nil, name) + c.CruSession.Delete(name) } // SessionRegenerateID regenerates session id for this session. // the session data have no changes. func (c *Controller) SessionRegenerateID() { if c.CruSession != nil { - c.CruSession.SessionRelease(nil, c.Ctx.ResponseWriter) + c.CruSession.SessionRelease(c.Ctx.ResponseWriter) } c.CruSession = GlobalSessions.SessionRegenerateID(c.Ctx.ResponseWriter, c.Ctx.Request) c.Ctx.Input.CruSession = c.CruSession @@ -653,7 +652,7 @@ func (c *Controller) SessionRegenerateID() { // DestroySession cleans session data and session cookie. func (c *Controller) DestroySession() { - c.Ctx.Input.CruSession.Flush(nil) + c.Ctx.Input.CruSession.Flush() c.Ctx.Input.CruSession = nil GlobalSessions.SessionDestroy(c.Ctx.ResponseWriter, c.Ctx.Request) } diff --git a/server/web/controller_test.go b/controller_test.go similarity index 94% rename from server/web/controller_test.go rename to controller_test.go index 0b711e0d..1e53416d 100644 --- a/server/web/controller_test.go +++ b/controller_test.go @@ -12,18 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "math" - "os" - "path/filepath" "strconv" "testing" - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/context" + "os" + "path/filepath" ) func TestGetInt(t *testing.T) { @@ -127,10 +125,8 @@ func TestGetUint64(t *testing.T) { } func TestAdditionalViewPaths(t *testing.T) { - wkdir, err := os.Getwd() - assert.Nil(t, err) - dir1 := filepath.Join(wkdir, "_beeTmp", "TestAdditionalViewPaths") - dir2 := filepath.Join(wkdir, "_beeTmp2", "TestAdditionalViewPaths") + dir1 := "_beeTmp" + dir2 := "_beeTmp2" defer os.RemoveAll(dir1) defer os.RemoveAll(dir2) diff --git a/core/bean/context.go b/core/bean/context.go deleted file mode 100644 index 7cee2c7e..00000000 --- a/core/bean/context.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 bean - -// ApplicationContext define for future -// when we decide to support DI, IoC, this will be core API -type ApplicationContext interface { -} diff --git a/core/bean/doc.go b/core/bean/doc.go deleted file mode 100644 index f806a081..00000000 --- a/core/bean/doc.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2020 -// -// Licensed 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. - -// bean is a basic package -// it should not depend on other modules except common module, log module and config module -package bean diff --git a/core/bean/factory.go b/core/bean/factory.go deleted file mode 100644 index 1097604c..00000000 --- a/core/bean/factory.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 bean - -import ( - "context" -) - -// AutoWireBeanFactory wire the bean based on ApplicationContext and context.Context -type AutoWireBeanFactory interface { - // AutoWire will wire the bean. - AutoWire(ctx context.Context, appCtx ApplicationContext, bean interface{}) error -} diff --git a/core/bean/metadata.go b/core/bean/metadata.go deleted file mode 100644 index e2e34f55..00000000 --- a/core/bean/metadata.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 bean - -// BeanMetadata, in other words, bean's config. -// it could be read from config file -type BeanMetadata struct { - // Fields: field name => field metadata - Fields map[string]*FieldMetadata -} - -// FieldMetadata contains metadata -type FieldMetadata struct { - // default value in string format - DftValue string -} diff --git a/core/bean/tag_auto_wire_bean_factory.go b/core/bean/tag_auto_wire_bean_factory.go deleted file mode 100644 index b88a42ff..00000000 --- a/core/bean/tag_auto_wire_bean_factory.go +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 bean - -import ( - "context" - "fmt" - "reflect" - "strconv" - - "github.com/pkg/errors" - - "github.com/astaxie/beego/core/logs" -) - -const DefaultValueTagKey = "default" - -// TagAutoWireBeanFactory wire the bean based on Fields' tag -// if field's value is "zero value", we will execute injection -// see reflect.Value.IsZero() -// If field's kind is one of(reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Slice -// reflect.UnsafePointer, reflect.Array, reflect.Uintptr, reflect.Complex64, reflect.Complex128 -// reflect.Ptr, reflect.Struct), -// it will be ignored -type TagAutoWireBeanFactory struct { - // we allow user register their TypeAdapter - Adapters map[string]TypeAdapter - - // FieldTagParser is an extension point which means that you can custom how to read field's metadata from tag - FieldTagParser func(field reflect.StructField) *FieldMetadata -} - -// NewTagAutoWireBeanFactory create an instance of TagAutoWireBeanFactory -// by default, we register Time adapter, the time will be parse by using layout "2006-01-02 15:04:05" -// If you need more adapter, you can implement interface TypeAdapter -func NewTagAutoWireBeanFactory() *TagAutoWireBeanFactory { - return &TagAutoWireBeanFactory{ - Adapters: map[string]TypeAdapter{ - "Time": &TimeTypeAdapter{Layout: "2006-01-02 15:04:05"}, - }, - - FieldTagParser: func(field reflect.StructField) *FieldMetadata { - return &FieldMetadata{ - DftValue: field.Tag.Get(DefaultValueTagKey), - } - }, - } -} - -// AutoWire use value from appCtx to wire the bean, or use default value, or do nothing -func (t *TagAutoWireBeanFactory) AutoWire(ctx context.Context, appCtx ApplicationContext, bean interface{}) error { - if bean == nil { - return nil - } - - v := reflect.Indirect(reflect.ValueOf(bean)) - - bm := t.getConfig(v) - - // field name, field metadata - for fn, fm := range bm.Fields { - - fValue := v.FieldByName(fn) - if len(fm.DftValue) == 0 || !t.needInject(fValue) || !fValue.CanSet() { - continue - } - - // handle type adapter - typeName := fValue.Type().Name() - if adapter, ok := t.Adapters[typeName]; ok { - dftValue, err := adapter.DefaultValue(ctx, fm.DftValue) - if err == nil { - fValue.Set(reflect.ValueOf(dftValue)) - continue - } else { - return err - } - } - - switch fValue.Kind() { - case reflect.Bool: - if v, err := strconv.ParseBool(fm.DftValue); err != nil { - return errors.WithMessage(err, - fmt.Sprintf("can not convert the field[%s]'s default value[%s] to bool value", - fn, fm.DftValue)) - } else { - fValue.SetBool(v) - continue - } - case reflect.Int: - if err := t.setIntXValue(fm.DftValue, 0, fn, fValue); err != nil { - return err - } - continue - case reflect.Int8: - if err := t.setIntXValue(fm.DftValue, 8, fn, fValue); err != nil { - return err - } - continue - case reflect.Int16: - if err := t.setIntXValue(fm.DftValue, 16, fn, fValue); err != nil { - return err - } - continue - - case reflect.Int32: - if err := t.setIntXValue(fm.DftValue, 32, fn, fValue); err != nil { - return err - } - continue - - case reflect.Int64: - if err := t.setIntXValue(fm.DftValue, 64, fn, fValue); err != nil { - return err - } - continue - - case reflect.Uint: - if err := t.setUIntXValue(fm.DftValue, 0, fn, fValue); err != nil { - return err - } - - case reflect.Uint8: - if err := t.setUIntXValue(fm.DftValue, 8, fn, fValue); err != nil { - return err - } - continue - - case reflect.Uint16: - if err := t.setUIntXValue(fm.DftValue, 16, fn, fValue); err != nil { - return err - } - continue - case reflect.Uint32: - if err := t.setUIntXValue(fm.DftValue, 32, fn, fValue); err != nil { - return err - } - continue - - case reflect.Uint64: - if err := t.setUIntXValue(fm.DftValue, 64, fn, fValue); err != nil { - return err - } - continue - - case reflect.Float32: - if err := t.setFloatXValue(fm.DftValue, 32, fn, fValue); err != nil { - return err - } - continue - case reflect.Float64: - if err := t.setFloatXValue(fm.DftValue, 64, fn, fValue); err != nil { - return err - } - continue - - case reflect.String: - fValue.SetString(fm.DftValue) - continue - - // case reflect.Ptr: - // case reflect.Struct: - default: - logs.Warn("this field[%s] has default setting, but we don't support this type: %s", - fn, fValue.Kind().String()) - } - } - return nil -} - -func (t *TagAutoWireBeanFactory) setFloatXValue(dftValue string, bitSize int, fn string, fv reflect.Value) error { - if v, err := strconv.ParseFloat(dftValue, bitSize); err != nil { - return errors.WithMessage(err, - fmt.Sprintf("can not convert the field[%s]'s default value[%s] to float%d value", - fn, dftValue, bitSize)) - } else { - fv.SetFloat(v) - return nil - } -} - -func (t *TagAutoWireBeanFactory) setUIntXValue(dftValue string, bitSize int, fn string, fv reflect.Value) error { - if v, err := strconv.ParseUint(dftValue, 10, bitSize); err != nil { - return errors.WithMessage(err, - fmt.Sprintf("can not convert the field[%s]'s default value[%s] to uint%d value", - fn, dftValue, bitSize)) - } else { - fv.SetUint(v) - return nil - } -} - -func (t *TagAutoWireBeanFactory) setIntXValue(dftValue string, bitSize int, fn string, fv reflect.Value) error { - if v, err := strconv.ParseInt(dftValue, 10, bitSize); err != nil { - return errors.WithMessage(err, - fmt.Sprintf("can not convert the field[%s]'s default value[%s] to int%d value", - fn, dftValue, bitSize)) - } else { - fv.SetInt(v) - return nil - } -} - -func (t *TagAutoWireBeanFactory) needInject(fValue reflect.Value) bool { - return fValue.IsZero() -} - -// getConfig never return nil -func (t *TagAutoWireBeanFactory) getConfig(beanValue reflect.Value) *BeanMetadata { - fms := make(map[string]*FieldMetadata, beanValue.NumField()) - for i := 0; i < beanValue.NumField(); i++ { - // f => StructField - f := beanValue.Type().Field(i) - fms[f.Name] = t.FieldTagParser(f) - } - return &BeanMetadata{ - Fields: fms, - } -} diff --git a/core/bean/tag_auto_wire_bean_factory_test.go b/core/bean/tag_auto_wire_bean_factory_test.go deleted file mode 100644 index bcdada67..00000000 --- a/core/bean/tag_auto_wire_bean_factory_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 bean - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestTagAutoWireBeanFactory_AutoWire(t *testing.T) { - factory := NewTagAutoWireBeanFactory() - bm := &ComplicateStruct{} - err := factory.AutoWire(context.Background(), nil, bm) - assert.Nil(t, err) - assert.Equal(t, 12, bm.IntValue) - assert.Equal(t, "hello, strValue", bm.StrValue) - - assert.Equal(t, int8(8), bm.Int8Value) - assert.Equal(t, int16(16), bm.Int16Value) - assert.Equal(t, int32(32), bm.Int32Value) - assert.Equal(t, int64(64), bm.Int64Value) - - assert.Equal(t, uint(13), bm.UintValue) - assert.Equal(t, uint8(88), bm.Uint8Value) - assert.Equal(t, uint16(1616), bm.Uint16Value) - assert.Equal(t, uint32(3232), bm.Uint32Value) - assert.Equal(t, uint64(6464), bm.Uint64Value) - - assert.Equal(t, float32(32.32), bm.Float32Value) - assert.Equal(t, float64(64.64), bm.Float64Value) - - assert.True(t, bm.BoolValue) - assert.Equal(t, 0, bm.ignoreInt) - - assert.NotNil(t, bm.TimeValue) -} - -type ComplicateStruct struct { - IntValue int `default:"12"` - StrValue string `default:"hello, strValue"` - Int8Value int8 `default:"8"` - Int16Value int16 `default:"16"` - Int32Value int32 `default:"32"` - Int64Value int64 `default:"64"` - - UintValue uint `default:"13"` - Uint8Value uint8 `default:"88"` - Uint16Value uint16 `default:"1616"` - Uint32Value uint32 `default:"3232"` - Uint64Value uint64 `default:"6464"` - - Float32Value float32 `default:"32.32"` - Float64Value float64 `default:"64.64"` - - BoolValue bool `default:"true"` - - ignoreInt int `default:"11"` - - TimeValue time.Time `default:"2018-02-03 12:13:14.000"` -} diff --git a/core/bean/time_type_adapter.go b/core/bean/time_type_adapter.go deleted file mode 100644 index b0e99896..00000000 --- a/core/bean/time_type_adapter.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 bean - -import ( - "context" - "time" -) - -// TimeTypeAdapter process the time.Time -type TimeTypeAdapter struct { - Layout string -} - -// DefaultValue parse the DftValue to time.Time -// and if the DftValue == now -// time.Now() is returned -func (t *TimeTypeAdapter) DefaultValue(ctx context.Context, dftValue string) (interface{}, error) { - if dftValue == "now" { - return time.Now(), nil - } - return time.Parse(t.Layout, dftValue) -} diff --git a/core/bean/time_type_adapter_test.go b/core/bean/time_type_adapter_test.go deleted file mode 100644 index 140ef5a6..00000000 --- a/core/bean/time_type_adapter_test.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 bean - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestTimeTypeAdapter_DefaultValue(t *testing.T) { - typeAdapter := &TimeTypeAdapter{Layout: "2006-01-02 15:04:05"} - tm, err := typeAdapter.DefaultValue(context.Background(), "2018-02-03 12:34:11") - assert.Nil(t, err) - assert.NotNil(t, tm) -} diff --git a/core/bean/type_adapter.go b/core/bean/type_adapter.go deleted file mode 100644 index 5869032d..00000000 --- a/core/bean/type_adapter.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 bean - -import ( - "context" -) - -// TypeAdapter is an abstraction that define some behavior of target type -// usually, we don't use this to support basic type since golang has many restriction for basic types -// This is an important extension point -type TypeAdapter interface { - DefaultValue(ctx context.Context, dftValue string) (interface{}, error) -} diff --git a/core/config/base_config_test.go b/core/config/base_config_test.go deleted file mode 100644 index 74a669a7..00000000 --- a/core/config/base_config_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 config - -import ( - "context" - "errors" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestBaseConfiger_DefaultBool(t *testing.T) { - bc := newBaseConfier("true") - assert.True(t, bc.DefaultBool("key1", false)) - assert.True(t, bc.DefaultBool("key2", true)) -} - -func TestBaseConfiger_DefaultFloat(t *testing.T) { - bc := newBaseConfier("12.3") - assert.Equal(t, 12.3, bc.DefaultFloat("key1", 0.1)) - assert.Equal(t, 0.1, bc.DefaultFloat("key2", 0.1)) -} - -func TestBaseConfiger_DefaultInt(t *testing.T) { - bc := newBaseConfier("10") - assert.Equal(t, 10, bc.DefaultInt("key1", 8)) - assert.Equal(t, 8, bc.DefaultInt("key2", 8)) -} - -func TestBaseConfiger_DefaultInt64(t *testing.T) { - bc := newBaseConfier("64") - assert.Equal(t, int64(64), bc.DefaultInt64("key1", int64(8))) - assert.Equal(t, int64(8), bc.DefaultInt64("key2", int64(8))) -} - -func TestBaseConfiger_DefaultString(t *testing.T) { - bc := newBaseConfier("Hello") - assert.Equal(t, "Hello", bc.DefaultString("key1", "world")) - assert.Equal(t, "world", bc.DefaultString("key2", "world")) -} - -func TestBaseConfiger_DefaultStrings(t *testing.T) { - bc := newBaseConfier("Hello;world") - assert.Equal(t, []string{"Hello", "world"}, bc.DefaultStrings("key1", []string{"world"})) - assert.Equal(t, []string{"world"}, bc.DefaultStrings("key2", []string{"world"})) -} - -func newBaseConfier(str1 string) *BaseConfiger { - return &BaseConfiger{ - reader: func(ctx context.Context, key string) (string, error) { - if key == "key1" { - return str1, nil - } else { - return "", errors.New("mock error") - } - - }, - } -} diff --git a/core/config/config_test.go b/core/config/config_test.go deleted file mode 100644 index 15d6ffa6..00000000 --- a/core/config/config_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2016 beego Author. All Rights Reserved. -// -// Licensed 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 config - -import ( - "os" - "testing" -) - -func TestExpandValueEnv(t *testing.T) { - - testCases := []struct { - item string - want string - }{ - {"", ""}, - {"$", "$"}, - {"{", "{"}, - {"{}", "{}"}, - {"${}", ""}, - {"${|}", ""}, - {"${}", ""}, - {"${{}}", ""}, - {"${{||}}", "}"}, - {"${pwd||}", ""}, - {"${pwd||}", ""}, - {"${pwd||}", ""}, - {"${pwd||}}", "}"}, - {"${pwd||{{||}}}", "{{||}}"}, - {"${GOPATH}", os.Getenv("GOPATH")}, - {"${GOPATH||}", os.Getenv("GOPATH")}, - {"${GOPATH||root}", os.Getenv("GOPATH")}, - {"${GOPATH_NOT||root}", "root"}, - {"${GOPATH_NOT||||root}", "||root"}, - } - - for _, c := range testCases { - if got := ExpandValueEnv(c.item); got != c.want { - t.Errorf("expand value error, item %q want %q, got %q", c.item, c.want, got) - } - } - -} diff --git a/core/config/env/env_test.go b/core/config/env/env_test.go deleted file mode 100644 index 3f1d4dba..00000000 --- a/core/config/env/env_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// Copyright 2017 Faissal Elamraoui. All Rights Reserved. -// -// Licensed 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 env - -import ( - "os" - "testing" -) - -func TestEnvGet(t *testing.T) { - gopath := Get("GOPATH", "") - if gopath != os.Getenv("GOPATH") { - t.Error("expected GOPATH not empty.") - } - - noExistVar := Get("NOEXISTVAR", "foo") - if noExistVar != "foo" { - t.Errorf("expected NOEXISTVAR to equal foo, got %s.", noExistVar) - } -} - -func TestEnvMustGet(t *testing.T) { - gopath, err := MustGet("GOPATH") - if err != nil { - t.Error(err) - } - - if gopath != os.Getenv("GOPATH") { - t.Errorf("expected GOPATH to be the same, got %s.", gopath) - } - - _, err = MustGet("NOEXISTVAR") - if err == nil { - t.Error("expected error to be non-nil") - } -} - -func TestEnvSet(t *testing.T) { - Set("MYVAR", "foo") - myVar := Get("MYVAR", "bar") - if myVar != "foo" { - t.Errorf("expected MYVAR to equal foo, got %s.", myVar) - } -} - -func TestEnvMustSet(t *testing.T) { - err := MustSet("FOO", "bar") - if err != nil { - t.Error(err) - } - - fooVar := os.Getenv("FOO") - if fooVar != "bar" { - t.Errorf("expected FOO variable to equal bar, got %s.", fooVar) - } -} - -func TestEnvGetAll(t *testing.T) { - envMap := GetAll() - if len(envMap) == 0 { - t.Error("expected environment not empty.") - } -} diff --git a/core/config/error.go b/core/config/error.go deleted file mode 100644 index e4636c45..00000000 --- a/core/config/error.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 config - -import ( - "github.com/pkg/errors" -) - -// now not all implementation return those error codes -var ( - KeyNotFoundError = errors.New("the key is not found") - InvalidValueTypeError = errors.New("the value is not expected type") -) diff --git a/core/config/etcd/config.go b/core/config/etcd/config.go deleted file mode 100644 index 6c3d33d4..00000000 --- a/core/config/etcd/config.go +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 etcd - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/coreos/etcd/clientv3" - grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" - "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" - "google.golang.org/grpc" - - "github.com/astaxie/beego/core/config" - "github.com/astaxie/beego/core/logs" -) - -type EtcdConfiger struct { - prefix string - client *clientv3.Client - config.BaseConfiger -} - -func newEtcdConfiger(client *clientv3.Client, prefix string) *EtcdConfiger { - res := &EtcdConfiger{ - client: client, - prefix: prefix, - } - - res.BaseConfiger = config.NewBaseConfiger(res.reader) - return res -} - -// reader is an general implementation that read config from etcd. -func (e *EtcdConfiger) reader(ctx context.Context, key string) (string, error) { - resp, err := get(e.client, e.prefix+key) - if err != nil { - return "", err - } - - if resp.Count > 0 { - return string(resp.Kvs[0].Value), nil - } - - return "", nil -} - -// Set do nothing and return an error -// I think write data to remote config center is not a good practice -func (e *EtcdConfiger) Set(key, val string) error { - return errors.New("Unsupported operation") -} - -// DIY return the original response from etcd -// be careful when you decide to use this -func (e *EtcdConfiger) DIY(key string) (interface{}, error) { - return get(e.client, key) -} - -// GetSection in this implementation, we use section as prefix -func (e *EtcdConfiger) GetSection(section string) (map[string]string, error) { - var ( - resp *clientv3.GetResponse - err error - ) - - resp, err = e.client.Get(context.TODO(), e.prefix+section, clientv3.WithPrefix()) - - if err != nil { - return nil, errors.WithMessage(err, "GetSection failed") - } - res := make(map[string]string, len(resp.Kvs)) - for _, kv := range resp.Kvs { - res[string(kv.Key)] = string(kv.Value) - } - return res, nil -} - -func (e *EtcdConfiger) SaveConfigFile(filename string) error { - return errors.New("Unsupported operation") -} - -// Unmarshaler is not very powerful because we lost the type information when we get configuration from etcd -// for example, when we got "5", we are not sure whether it's int 5, or it's string "5" -// TODO(support more complicated decoder) -func (e *EtcdConfiger) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error { - res, err := e.GetSection(prefix) - if err != nil { - return errors.WithMessage(err, fmt.Sprintf("could not read config with prefix: %s", prefix)) - } - - prefixLen := len(e.prefix + prefix) - m := make(map[string]string, len(res)) - for k, v := range res { - m[k[prefixLen:]] = v - } - return mapstructure.Decode(m, obj) -} - -// Sub return an sub configer. -func (e *EtcdConfiger) Sub(key string) (config.Configer, error) { - return newEtcdConfiger(e.client, e.prefix+key), nil -} - -// TODO remove this before release v2.0.0 -func (e *EtcdConfiger) OnChange(key string, fn func(value string)) { - - buildOptsFunc := func() []clientv3.OpOption { - return []clientv3.OpOption{} - } - - rch := e.client.Watch(context.Background(), e.prefix+key, buildOptsFunc()...) - go func() { - for { - for resp := range rch { - if err := resp.Err(); err != nil { - logs.Error("listen to key but got error callback", err) - break - } - - for _, e := range resp.Events { - if e.Kv == nil { - continue - } - fn(string(e.Kv.Value)) - } - } - time.Sleep(time.Second) - rch = e.client.Watch(context.Background(), e.prefix+key, buildOptsFunc()...) - } - }() - -} - -type EtcdConfigerProvider struct { -} - -// Parse = ParseData([]byte(key)) -// key must be json -func (provider *EtcdConfigerProvider) Parse(key string) (config.Configer, error) { - return provider.ParseData([]byte(key)) -} - -// ParseData try to parse key as clientv3.Config, using this to build etcdClient -func (provider *EtcdConfigerProvider) ParseData(data []byte) (config.Configer, error) { - cfg := &clientv3.Config{} - err := json.Unmarshal(data, cfg) - if err != nil { - return nil, errors.WithMessage(err, "parse data to etcd config failed, please check your input") - } - - cfg.DialOptions = []grpc.DialOption{ - grpc.WithBlock(), - grpc.WithUnaryInterceptor(grpc_prometheus.UnaryClientInterceptor), - grpc.WithStreamInterceptor(grpc_prometheus.StreamClientInterceptor), - } - client, err := clientv3.New(*cfg) - if err != nil { - return nil, errors.WithMessage(err, "create etcd client failed") - } - - return newEtcdConfiger(client, ""), nil -} - -func get(client *clientv3.Client, key string) (*clientv3.GetResponse, error) { - var ( - resp *clientv3.GetResponse - err error - ) - resp, err = client.Get(context.Background(), key) - - if err != nil { - return nil, errors.WithMessage(err, fmt.Sprintf("read config from etcd with key %s failed", key)) - } - return resp, err -} - -func init() { - config.Register("json", &EtcdConfigerProvider{}) -} diff --git a/core/config/etcd/config_test.go b/core/config/etcd/config_test.go deleted file mode 100644 index 6d0bb793..00000000 --- a/core/config/etcd/config_test.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 etcd - -import ( - "encoding/json" - "os" - "testing" - "time" - - "github.com/coreos/etcd/clientv3" - "github.com/stretchr/testify/assert" -) - -func TestEtcdConfigerProvider_Parse(t *testing.T) { - provider := &EtcdConfigerProvider{} - cfger, err := provider.Parse(readEtcdConfig()) - assert.Nil(t, err) - assert.NotNil(t, cfger) -} - -func TestEtcdConfiger(t *testing.T) { - - provider := &EtcdConfigerProvider{} - cfger, _ := provider.Parse(readEtcdConfig()) - - subCfger, err := cfger.Sub("sub.") - assert.Nil(t, err) - assert.NotNil(t, subCfger) - - subSubCfger, err := subCfger.Sub("sub.") - assert.NotNil(t, subSubCfger) - assert.Nil(t, err) - - str, err := subSubCfger.String("key1") - assert.Nil(t, err) - assert.Equal(t, "sub.sub.key", str) - - // we cannot test it - subSubCfger.OnChange("watch", func(value string) { - // do nothing - }) - - defStr := cfger.DefaultString("not_exit", "default value") - assert.Equal(t, "default value", defStr) - - defInt64 := cfger.DefaultInt64("not_exit", -1) - assert.Equal(t, int64(-1), defInt64) - - defInt := cfger.DefaultInt("not_exit", -2) - assert.Equal(t, -2, defInt) - - defFlt := cfger.DefaultFloat("not_exit", 12.3) - assert.Equal(t, 12.3, defFlt) - - defBl := cfger.DefaultBool("not_exit", true) - assert.True(t, defBl) - - defStrs := cfger.DefaultStrings("not_exit", []string{"hello"}) - assert.Equal(t, []string{"hello"}, defStrs) - - fl, err := cfger.Float("current.float") - assert.Nil(t, err) - assert.Equal(t, 1.23, fl) - - bl, err := cfger.Bool("current.bool") - assert.Nil(t, err) - assert.True(t, bl) - - it, err := cfger.Int("current.int") - assert.Nil(t, err) - assert.Equal(t, 11, it) - - str, err = cfger.String("current.string") - assert.Nil(t, err) - assert.Equal(t, "hello", str) - - tn := &TestEntity{} - err = cfger.Unmarshaler("current.serialize.", tn) - assert.Nil(t, err) - assert.Equal(t, "test", tn.Name) -} - -type TestEntity struct { - Name string `yaml:"name"` - Sub SubEntity `yaml:"sub"` -} - -type SubEntity struct { - SubName string `yaml:"subName"` -} - -func readEtcdConfig() string { - addr := os.Getenv("ETCD_ADDR") - if addr == "" { - addr = "localhost:2379" - } - - obj := clientv3.Config{ - Endpoints: []string{addr}, - DialTimeout: 3 * time.Second, - } - cfg, _ := json.Marshal(obj) - return string(cfg) -} diff --git a/core/config/global.go b/core/config/global.go deleted file mode 100644 index 5491fe2c..00000000 --- a/core/config/global.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 config - -// We use this to simply application's development -// for most users, they only need to use those methods -var globalInstance Configer - - -// InitGlobalInstance will ini the global instance -// If you want to use specific implementation, don't forget to import it. -// e.g. _ import "github.com/astaxie/beego/core/config/etcd" -// err := InitGlobalInstance("etcd", "someconfig") -func InitGlobalInstance(name string, cfg string) error { - var err error - globalInstance, err = NewConfig(name, cfg) - return err -} - -// support section::key type in given key when using ini type. -func Set(key, val string) error { - return globalInstance.Set(key, val) -} - -// support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. -func String(key string) (string, error) { - return globalInstance.String(key) -} - -// get string slice -func Strings(key string) ([]string, error) { - return globalInstance.Strings(key) -} -func Int(key string) (int, error) { - return globalInstance.Int(key) -} -func Int64(key string) (int64, error) { - return globalInstance.Int64(key) -} -func Bool(key string) (bool, error) { - return globalInstance.Bool(key) -} -func Float(key string) (float64, error) { - return globalInstance.Float(key) -} - -// support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. -func DefaultString(key string, defaultVal string) string { - return globalInstance.DefaultString(key, defaultVal) -} - -// get string slice -func DefaultStrings(key string, defaultVal []string) []string { - return globalInstance.DefaultStrings(key, defaultVal) -} -func DefaultInt(key string, defaultVal int) int { - return globalInstance.DefaultInt(key, defaultVal) -} -func DefaultInt64(key string, defaultVal int64) int64 { - return globalInstance.DefaultInt64(key, defaultVal) -} -func DefaultBool(key string, defaultVal bool) bool { - return globalInstance.DefaultBool(key, defaultVal) -} -func DefaultFloat(key string, defaultVal float64) float64 { - return globalInstance.DefaultFloat(key, defaultVal) -} - -// DIY return the original value -func DIY(key string) (interface{}, error) { - return globalInstance.DIY(key) -} - -func GetSection(section string) (map[string]string, error) { - return globalInstance.GetSection(section) -} - -func Unmarshaler(prefix string, obj interface{}, opt ...DecodeOption) error { - return globalInstance.Unmarshaler(prefix, obj, opt...) -} -func Sub(key string) (Configer, error) { - return globalInstance.Sub(key) -} - -func OnChange(key string, fn func(value string)) { - globalInstance.OnChange(key, fn) -} - -func SaveConfigFile(filename string) error { - return globalInstance.SaveConfigFile(filename) -} diff --git a/core/config/global_test.go b/core/config/global_test.go deleted file mode 100644 index ff01b043..00000000 --- a/core/config/global_test.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 config - -import ( - "os" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGlobalInstance(t *testing.T) { - cfgStr := ` -appname = beeapi -httpport = 8080 -mysqlport = 3600 -PI = 3.1415926 -runmode = "dev" -autorender = false -copyrequestbody = true -session= on -cookieon= off -newreg = OFF -needlogin = ON -enableSession = Y -enableCookie = N -developer="tom;jerry" -flag = 1 -path1 = ${GOPATH} -path2 = ${GOPATH||/home/go} -[demo] -key1="asta" -key2 = "xie" -CaseInsensitive = true -peers = one;two;three -password = ${GOPATH} -` - path := os.TempDir() + string(os.PathSeparator) + "test_global_instance.ini" - f, err := os.Create(path) - if err != nil { - t.Fatal(err) - } - _, err = f.WriteString(cfgStr) - if err != nil { - f.Close() - t.Fatal(err) - } - f.Close() - defer os.Remove(path) - - err = InitGlobalInstance("ini", path) - assert.Nil(t, err) - - val, err := String("appname") - assert.Nil(t, err) - assert.Equal(t, "beeapi", val) - - val = DefaultString("appname__", "404") - assert.Equal(t, "404", val) - - vi, err := Int("httpport") - assert.Nil(t, err) - assert.Equal(t, 8080, vi) - vi = DefaultInt("httpport__", 404) - assert.Equal(t, 404, vi) - - vi64, err := Int64("mysqlport") - assert.Nil(t, err) - assert.Equal(t, int64(3600), vi64) - vi64 = DefaultInt64("mysqlport__", 404) - assert.Equal(t, int64(404), vi64) - - vf, err := Float("PI") - assert.Nil(t, err) - assert.Equal(t, 3.1415926, vf) - vf = DefaultFloat("PI__", 4.04) - assert.Equal(t, 4.04, vf) - - vb, err := Bool("copyrequestbody") - assert.Nil(t, err) - assert.True(t, vb) - - vb = DefaultBool("copyrequestbody__", false) - assert.False(t, vb) - - vss := DefaultStrings("developer__", []string{"tom", ""}) - assert.Equal(t, []string{"tom", ""}, vss) - - vss, err = Strings("developer") - assert.Nil(t, err) - assert.Equal(t, []string{"tom", "jerry"}, vss) -} diff --git a/core/config/ini_test.go b/core/config/ini_test.go deleted file mode 100644 index 7daa0a6e..00000000 --- a/core/config/ini_test.go +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 config - -import ( - "fmt" - "io/ioutil" - "os" - "strings" - "testing" -) - -func TestIni(t *testing.T) { - - var ( - inicontext = ` -;comment one -#comment two -appname = beeapi -httpport = 8080 -mysqlport = 3600 -PI = 3.1415976 -runmode = "dev" -autorender = false -copyrequestbody = true -session= on -cookieon= off -newreg = OFF -needlogin = ON -enableSession = Y -enableCookie = N -flag = 1 -path1 = ${GOPATH} -path2 = ${GOPATH||/home/go} -[demo] -key1="asta" -key2 = "xie" -CaseInsensitive = true -peers = one;two;three -password = ${GOPATH} -` - - keyValue = map[string]interface{}{ - "appname": "beeapi", - "httpport": 8080, - "mysqlport": int64(3600), - "pi": 3.1415976, - "runmode": "dev", - "autorender": false, - "copyrequestbody": true, - "session": true, - "cookieon": false, - "newreg": false, - "needlogin": true, - "enableSession": true, - "enableCookie": false, - "flag": true, - "path1": os.Getenv("GOPATH"), - "path2": os.Getenv("GOPATH"), - "demo::key1": "asta", - "demo::key2": "xie", - "demo::CaseInsensitive": true, - "demo::peers": []string{"one", "two", "three"}, - "demo::password": os.Getenv("GOPATH"), - "null": "", - "demo2::key1": "", - "error": "", - "emptystrings": []string{}, - } - ) - - f, err := os.Create("testini.conf") - if err != nil { - t.Fatal(err) - } - _, err = f.WriteString(inicontext) - if err != nil { - f.Close() - t.Fatal(err) - } - f.Close() - defer os.Remove("testini.conf") - iniconf, err := NewConfig("ini", "testini.conf") - if err != nil { - t.Fatal(err) - } - for k, v := range keyValue { - var err error - var value interface{} - switch v.(type) { - case int: - value, err = iniconf.Int(k) - case int64: - value, err = iniconf.Int64(k) - case float64: - value, err = iniconf.Float(k) - case bool: - value, err = iniconf.Bool(k) - case []string: - value, err = iniconf.Strings(k) - case string: - value, err = iniconf.String(k) - default: - value, err = iniconf.DIY(k) - } - if err != nil { - t.Fatalf("get key %q value fail,err %s", k, err) - } else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) { - t.Fatalf("get key %q value, want %v got %v .", k, v, value) - } - - } - if err = iniconf.Set("name", "astaxie"); err != nil { - t.Fatal(err) - } - res, _ := iniconf.String("name") - if res != "astaxie" { - t.Fatal("get name error") - } - -} - -func TestIniSave(t *testing.T) { - - const ( - inicontext = ` -app = app -;comment one -#comment two -# comment three -appname = beeapi -httpport = 8080 -# DB Info -# enable db -[dbinfo] -# db type name -# suport mysql,sqlserver -name = mysql -` - - saveResult = ` -app=app -#comment one -#comment two -# comment three -appname=beeapi -httpport=8080 - -# DB Info -# enable db -[dbinfo] -# db type name -# suport mysql,sqlserver -name=mysql -` - ) - cfg, err := NewConfigData("ini", []byte(inicontext)) - if err != nil { - t.Fatal(err) - } - name := "newIniConfig.ini" - if err := cfg.SaveConfigFile(name); err != nil { - t.Fatal(err) - } - defer os.Remove(name) - - if data, err := ioutil.ReadFile(name); err != nil { - t.Fatal(err) - } else { - cfgData := string(data) - datas := strings.Split(saveResult, "\n") - for _, line := range datas { - if !strings.Contains(cfgData, line+"\n") { - t.Fatalf("different after save ini config file. need contains %q", line) - } - } - - } -} diff --git a/core/config/json/json_test.go b/core/config/json/json_test.go deleted file mode 100644 index 386cfdf1..00000000 --- a/core/config/json/json_test.go +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 json - -import ( - "fmt" - "os" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/core/config" -) - -func TestJsonStartsWithArray(t *testing.T) { - - const jsoncontextwitharray = `[ - { - "url": "user", - "serviceAPI": "http://www.test.com/user" - }, - { - "url": "employee", - "serviceAPI": "http://www.test.com/employee" - } -]` - f, err := os.Create("testjsonWithArray.conf") - if err != nil { - t.Fatal(err) - } - _, err = f.WriteString(jsoncontextwitharray) - if err != nil { - f.Close() - t.Fatal(err) - } - f.Close() - defer os.Remove("testjsonWithArray.conf") - jsonconf, err := config.NewConfig("json", "testjsonWithArray.conf") - if err != nil { - t.Fatal(err) - } - rootArray, err := jsonconf.DIY("rootArray") - if err != nil { - t.Error("array does not exist as element") - } - rootArrayCasted := rootArray.([]interface{}) - if rootArrayCasted == nil { - t.Error("array from root is nil") - } else { - elem := rootArrayCasted[0].(map[string]interface{}) - if elem["url"] != "user" || elem["serviceAPI"] != "http://www.test.com/user" { - t.Error("array[0] values are not valid") - } - - elem2 := rootArrayCasted[1].(map[string]interface{}) - if elem2["url"] != "employee" || elem2["serviceAPI"] != "http://www.test.com/employee" { - t.Error("array[1] values are not valid") - } - } -} - -func TestJson(t *testing.T) { - - var ( - jsoncontext = `{ -"appname": "beeapi", -"testnames": "foo;bar", -"httpport": 8080, -"mysqlport": 3600, -"PI": 3.1415976, -"runmode": "dev", -"autorender": false, -"copyrequestbody": true, -"session": "on", -"cookieon": "off", -"newreg": "OFF", -"needlogin": "ON", -"enableSession": "Y", -"enableCookie": "N", -"flag": 1, -"path1": "${GOPATH}", -"path2": "${GOPATH||/home/go}", -"database": { - "host": "host", - "port": "port", - "database": "database", - "username": "username", - "password": "${GOPATH}", - "conns":{ - "maxconnection":12, - "autoconnect":true, - "connectioninfo":"info", - "root": "${GOPATH}" - } - } -}` - keyValue = map[string]interface{}{ - "appname": "beeapi", - "testnames": []string{"foo", "bar"}, - "httpport": 8080, - "mysqlport": int64(3600), - "PI": 3.1415976, - "runmode": "dev", - "autorender": false, - "copyrequestbody": true, - "session": true, - "cookieon": false, - "newreg": false, - "needlogin": true, - "enableSession": true, - "enableCookie": false, - "flag": true, - "path1": os.Getenv("GOPATH"), - "path2": os.Getenv("GOPATH"), - "database::host": "host", - "database::port": "port", - "database::database": "database", - "database::password": os.Getenv("GOPATH"), - "database::conns::maxconnection": 12, - "database::conns::autoconnect": true, - "database::conns::connectioninfo": "info", - "database::conns::root": os.Getenv("GOPATH"), - "unknown": "", - } - ) - - f, err := os.Create("testjson.conf") - if err != nil { - t.Fatal(err) - } - _, err = f.WriteString(jsoncontext) - if err != nil { - f.Close() - t.Fatal(err) - } - f.Close() - defer os.Remove("testjson.conf") - jsonconf, err := config.NewConfig("json", "testjson.conf") - if err != nil { - t.Fatal(err) - } - - for k, v := range keyValue { - var err error - var value interface{} - switch v.(type) { - case int: - value, err = jsonconf.Int(k) - case int64: - value, err = jsonconf.Int64(k) - case float64: - value, err = jsonconf.Float(k) - case bool: - value, err = jsonconf.Bool(k) - case []string: - value, err = jsonconf.Strings(k) - case string: - value, err = jsonconf.String(k) - default: - value, err = jsonconf.DIY(k) - } - if err != nil { - t.Fatalf("get key %q value fatal,%v err %s", k, v, err) - } else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) { - t.Fatalf("get key %q value, want %v got %v .", k, v, value) - } - - } - if err = jsonconf.Set("name", "astaxie"); err != nil { - t.Fatal(err) - } - - res, _ := jsonconf.String("name") - if res != "astaxie" { - t.Fatal("get name error") - } - - if db, err := jsonconf.DIY("database"); err != nil { - t.Fatal(err) - } else if m, ok := db.(map[string]interface{}); !ok { - t.Log(db) - t.Fatal("db not map[string]interface{}") - } else { - if m["host"].(string) != "host" { - t.Fatal("get host err") - } - } - - if _, err := jsonconf.Int("unknown"); err == nil { - t.Error("unknown keys should return an error when expecting an Int") - } - - if _, err := jsonconf.Int64("unknown"); err == nil { - t.Error("unknown keys should return an error when expecting an Int64") - } - - if _, err := jsonconf.Float("unknown"); err == nil { - t.Error("unknown keys should return an error when expecting a Float") - } - - if _, err := jsonconf.DIY("unknown"); err == nil { - t.Error("unknown keys should return an error when expecting an interface{}") - } - - if val, _ := jsonconf.String("unknown"); val != "" { - t.Error("unknown keys should return an empty string when expecting a String") - } - - if _, err := jsonconf.Bool("unknown"); err == nil { - t.Error("unknown keys should return an error when expecting a Bool") - } - - if !jsonconf.DefaultBool("unknown", true) { - t.Error("unknown keys with default value wrong") - } - - sub, err := jsonconf.Sub("database") - assert.Nil(t, err) - assert.NotNil(t, sub) - - sub, err = sub.Sub("conns") - assert.Nil(t, err) - - maxCon, _ := sub.Int("maxconnection") - assert.Equal(t, 12, maxCon) - - dbCfg := &DatabaseConfig{} - err = sub.Unmarshaler("", dbCfg) - assert.Nil(t, err) - assert.Equal(t, 12, dbCfg.MaxConnection) - assert.True(t, dbCfg.Autoconnect) - assert.Equal(t, "info", dbCfg.Connectioninfo) -} - -type DatabaseConfig struct { - MaxConnection int `json:"maxconnection"` - Autoconnect bool `json:"autoconnect"` - Connectioninfo string `json:"connectioninfo"` -} diff --git a/core/config/toml/toml.go b/core/config/toml/toml.go deleted file mode 100644 index 96e1a200..00000000 --- a/core/config/toml/toml.go +++ /dev/null @@ -1,357 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 toml - -import ( - "io/ioutil" - "os" - "strings" - - "github.com/pelletier/go-toml" - - "github.com/astaxie/beego/core/config" -) - -const keySeparator = "." - -type Config struct { - tree *toml.Tree -} - -// Parse accepts filename as the parameter -func (c *Config) Parse(filename string) (config.Configer, error) { - ctx, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - return c.ParseData(ctx) -} - -func (c *Config) ParseData(data []byte) (config.Configer, error) { - t, err := toml.LoadBytes(data) - if err != nil { - return nil, err - } - return &configContainer{ - t: t, - }, nil - -} - -// configContainer support key looks like "a.b.c" -type configContainer struct { - t *toml.Tree -} - -// Set put key, val -func (c *configContainer) Set(key, val string) error { - path := strings.Split(key, keySeparator) - sub, err := subTree(c.t, path[0:len(path)-1]) - if err != nil { - return err - } - sub.Set(path[len(path)-1], val) - return nil -} - -// String return the value. -// return error if key not found or value is invalid type -func (c *configContainer) String(key string) (string, error) { - res, err := c.get(key) - - if err != nil { - return "", err - } - - if res == nil { - return "", config.KeyNotFoundError - } - - if str, ok := res.(string); ok { - return str, nil - } else { - return "", config.InvalidValueTypeError - } -} - -// Strings return []string -// return error if key not found or value is invalid type -func (c *configContainer) Strings(key string) ([]string, error) { - val, err := c.get(key) - - if err != nil { - return []string{}, err - } - if val == nil { - return []string{}, config.KeyNotFoundError - } - if arr, ok := val.([]interface{}); ok { - res := make([]string, 0, len(arr)) - for _, ele := range arr { - if str, ok := ele.(string); ok { - res = append(res, str) - } else { - return []string{}, config.InvalidValueTypeError - } - } - return res, nil - } else { - return []string{}, config.InvalidValueTypeError - } -} - -// Int return int value -// return error if key not found or value is invalid type -func (c *configContainer) Int(key string) (int, error) { - val, err := c.Int64(key) - return int(val), err -} - -// Int64 return int64 value -// return error if key not found or value is invalid type -func (c *configContainer) Int64(key string) (int64, error) { - res, err := c.get(key) - if err != nil { - return 0, err - } - if res == nil { - return 0, config.KeyNotFoundError - } - if i, ok := res.(int); ok { - return int64(i), nil - } else if i64, ok := res.(int64); ok { - return i64, nil - } else { - return 0, config.InvalidValueTypeError - } -} - -// bool return bool value -// return error if key not found or value is invalid type -func (c *configContainer) Bool(key string) (bool, error) { - - res, err := c.get(key) - - if err != nil { - return false, err - } - - if res == nil { - return false, config.KeyNotFoundError - } - if b, ok := res.(bool); ok { - return b, nil - } else { - return false, config.InvalidValueTypeError - } -} - -// Float return float value -// return error if key not found or value is invalid type -func (c *configContainer) Float(key string) (float64, error) { - res, err := c.get(key) - if err != nil { - return 0, err - } - - if res == nil { - return 0, config.KeyNotFoundError - } - - if f, ok := res.(float64); ok { - return f, nil - } else { - return 0, config.InvalidValueTypeError - } -} - -// DefaultString return string value -// return default value if key not found or value is invalid type -func (c *configContainer) DefaultString(key string, defaultVal string) string { - res, err := c.get(key) - if err != nil { - return defaultVal - } - if str, ok := res.(string); ok { - return str - } else { - return defaultVal - } -} - -// DefaultStrings return []string -// return default value if key not found or value is invalid type -func (c *configContainer) DefaultStrings(key string, defaultVal []string) []string { - val, err := c.get(key) - if err != nil { - return defaultVal - } - if arr, ok := val.([]interface{}); ok { - res := make([]string, 0, len(arr)) - for _, ele := range arr { - if str, ok := ele.(string); ok { - res = append(res, str) - } else { - return defaultVal - } - } - return res - } else { - return defaultVal - } -} - -// DefaultInt return int value -// return default value if key not found or value is invalid type -func (c *configContainer) DefaultInt(key string, defaultVal int) int { - return int(c.DefaultInt64(key, int64(defaultVal))) -} - -// DefaultInt64 return int64 value -// return default value if key not found or value is invalid type -func (c *configContainer) DefaultInt64(key string, defaultVal int64) int64 { - res, err := c.get(key) - if err != nil { - return defaultVal - } - if i, ok := res.(int); ok { - return int64(i) - } else if i64, ok := res.(int64); ok { - return i64 - } else { - return defaultVal - } -} - -// DefaultBool return bool value -// return default value if key not found or value is invalid type -func (c *configContainer) DefaultBool(key string, defaultVal bool) bool { - res, err := c.get(key) - if err != nil { - return defaultVal - } - if b, ok := res.(bool); ok { - return b - } else { - return defaultVal - } -} - -// DefaultFloat return float value -// return default value if key not found or value is invalid type -func (c *configContainer) DefaultFloat(key string, defaultVal float64) float64 { - res, err := c.get(key) - if err != nil { - return defaultVal - } - if f, ok := res.(float64); ok { - return f - } else { - return defaultVal - } -} - -// DIY returns the original value -func (c *configContainer) DIY(key string) (interface{}, error) { - return c.get(key) -} - -// GetSection return error if the value is not valid toml doc -func (c *configContainer) GetSection(section string) (map[string]string, error) { - val, err := subTree(c.t, strings.Split(section, keySeparator)) - if err != nil { - return map[string]string{}, err - } - m := val.ToMap() - res := make(map[string]string, len(m)) - for k, v := range m { - res[k] = config.ToString(v) - } - return res, nil -} - -func (c *configContainer) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error { - if len(prefix) > 0 { - t, err := subTree(c.t, strings.Split(prefix, keySeparator)) - if err != nil { - return err - } - return t.Unmarshal(obj) - } - return c.t.Unmarshal(obj) -} - -// Sub return sub configer -// return error if key not found or the value is not a sub doc -func (c *configContainer) Sub(key string) (config.Configer, error) { - val, err := subTree(c.t, strings.Split(key, keySeparator)) - if err != nil { - return nil, err - } - return &configContainer{ - t: val, - }, nil -} - -// OnChange do nothing -func (c *configContainer) OnChange(key string, fn func(value string)) { - // do nothing -} - -// SaveConfigFile create or override the file -func (c *configContainer) SaveConfigFile(filename string) error { - // Write configuration file by filename. - f, err := os.Create(filename) - if err != nil { - return err - } - defer f.Close() - - _, err = c.t.WriteTo(f) - return err -} - -func (c *configContainer) get(key string) (interface{}, error) { - if len(key) == 0 { - return nil, config.KeyNotFoundError - } - - segs := strings.Split(key, keySeparator) - t, err := subTree(c.t, segs[0:len(segs)-1]) - - if err != nil { - return nil, err - } - return t.Get(segs[len(segs)-1]), nil -} - -func subTree(t *toml.Tree, path []string) (*toml.Tree, error) { - res := t - for i := 0; i < len(path); i++ { - if subTree, ok := res.Get(path[i]).(*toml.Tree); ok { - res = subTree - } else { - return nil, config.InvalidValueTypeError - } - } - if res == nil { - return nil, config.KeyNotFoundError - } - return res, nil -} - -func init() { - config.Register("toml", &Config{}) -} diff --git a/core/config/toml/toml_test.go b/core/config/toml/toml_test.go deleted file mode 100644 index 20726f0d..00000000 --- a/core/config/toml/toml_test.go +++ /dev/null @@ -1,379 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 toml - -import ( - "fmt" - "os" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/core/config" -) - -func TestConfig_Parse(t *testing.T) { - // file not found - cfg := &Config{} - _, err := cfg.Parse("invalid_file_name.txt") - assert.NotNil(t, err) -} - -func TestConfig_ParseData(t *testing.T) { - data := ` -name="Tom" -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - assert.Nil(t, err) - assert.NotNil(t, c) -} - -func TestConfigContainer_Bool(t *testing.T) { - data := ` -Man=true -Woman="true" -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - assert.Nil(t, err) - assert.NotNil(t, c) - - val, err := c.Bool("Man") - assert.Nil(t, err) - assert.True(t, val) - - _, err = c.Bool("Woman") - assert.NotNil(t, err) - assert.Equal(t, config.InvalidValueTypeError, err) -} - -func TestConfigContainer_DefaultBool(t *testing.T) { - data := ` -Man=true -Woman="false" -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - assert.Nil(t, err) - assert.NotNil(t, c) - - val := c.DefaultBool("Man11", true) - assert.True(t, val) - - val = c.DefaultBool("Man", false) - assert.True(t, val) - - val = c.DefaultBool("Woman", true) - assert.True(t, val) -} - -func TestConfigContainer_DefaultFloat(t *testing.T) { - data := ` -Price=12.3 -PriceInvalid="12.3" -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - assert.Nil(t, err) - assert.NotNil(t, c) - - val := c.DefaultFloat("Price", 11.2) - assert.Equal(t, 12.3, val) - - val = c.DefaultFloat("Price11", 11.2) - assert.Equal(t, 11.2, val) - - val = c.DefaultFloat("PriceInvalid", 11.2) - assert.Equal(t, 11.2, val) -} - -func TestConfigContainer_DefaultInt(t *testing.T) { - data := ` -Age=12 -AgeInvalid="13" -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - assert.Nil(t, err) - assert.NotNil(t, c) - - val := c.DefaultInt("Age", 11) - assert.Equal(t, 12, val) - - val = c.DefaultInt("Price11", 11) - assert.Equal(t, 11, val) - - val = c.DefaultInt("PriceInvalid", 11) - assert.Equal(t, 11, val) -} - -func TestConfigContainer_DefaultString(t *testing.T) { - data := ` -Name="Tom" -NameInvalid=13 -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - assert.Nil(t, err) - assert.NotNil(t, c) - - val := c.DefaultString("Name", "Jerry") - assert.Equal(t, "Tom", val) - - val = c.DefaultString("Name11", "Jerry") - assert.Equal(t, "Jerry", val) - - val = c.DefaultString("NameInvalid", "Jerry") - assert.Equal(t, "Jerry", val) -} - -func TestConfigContainer_DefaultStrings(t *testing.T) { - data := ` -Name=["Tom", "Jerry"] -NameInvalid="Tom" -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - assert.Nil(t, err) - assert.NotNil(t, c) - - val := c.DefaultStrings("Name", []string{"Jerry"}) - assert.Equal(t, []string{"Tom", "Jerry"}, val) - - val = c.DefaultStrings("Name11", []string{"Jerry"}) - assert.Equal(t, []string{"Jerry"}, val) - - val = c.DefaultStrings("NameInvalid", []string{"Jerry"}) - assert.Equal(t, []string{"Jerry"}, val) -} - -func TestConfigContainer_DIY(t *testing.T) { - data := ` -Name=["Tom", "Jerry"] -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - assert.Nil(t, err) - assert.NotNil(t, c) - - _, err = c.DIY("Name") - assert.Nil(t, err) -} - -func TestConfigContainer_Float(t *testing.T) { - data := ` -Price=12.3 -PriceInvalid="12.3" -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - assert.Nil(t, err) - assert.NotNil(t, c) - - val, err := c.Float("Price") - assert.Nil(t, err) - assert.Equal(t, 12.3, val) - - _, err = c.Float("Price11") - assert.Equal(t, config.KeyNotFoundError, err) - - _, err = c.Float("PriceInvalid") - assert.Equal(t, config.InvalidValueTypeError, err) -} - -func TestConfigContainer_Int(t *testing.T) { - data := ` -Age=12 -AgeInvalid="13" -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - assert.Nil(t, err) - assert.NotNil(t, c) - - val, err := c.Int("Age") - assert.Nil(t, err) - assert.Equal(t, 12, val) - - _, err = c.Int("Age11") - assert.Equal(t, config.KeyNotFoundError, err) - - _, err = c.Int("AgeInvalid") - assert.Equal(t, config.InvalidValueTypeError, err) -} - -func TestConfigContainer_GetSection(t *testing.T) { - data := ` -[servers] - - # You can indent as you please. Tabs or spaces. TOML don't care. - [servers.alpha] - ip = "10.0.0.1" - dc = "eqdc10" - - [servers.beta] - ip = "10.0.0.2" - dc = "eqdc10" -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - assert.Nil(t, err) - assert.NotNil(t, c) - - m, err := c.GetSection("servers") - assert.Nil(t, err) - assert.NotNil(t, m) - assert.Equal(t, 2, len(m)) -} - -func TestConfigContainer_String(t *testing.T) { - data := ` -Name="Tom" -NameInvalid=13 -[Person] -Name="Jerry" -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - assert.Nil(t, err) - assert.NotNil(t, c) - - val, err := c.String("Name") - assert.Nil(t, err) - assert.Equal(t, "Tom", val) - - _, err = c.String("Name11") - assert.Equal(t, config.KeyNotFoundError, err) - - _, err = c.String("NameInvalid") - assert.Equal(t, config.InvalidValueTypeError, err) - - val, err = c.String("Person.Name") - assert.Nil(t, err) - assert.Equal(t, "Jerry", val) -} - -func TestConfigContainer_Strings(t *testing.T) { - data := ` -Name=["Tom", "Jerry"] -NameInvalid="Tom" -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - assert.Nil(t, err) - assert.NotNil(t, c) - - val, err := c.Strings("Name") - assert.Nil(t, err) - assert.Equal(t, []string{"Tom", "Jerry"}, val) - - _, err = c.Strings("Name11") - assert.Equal(t, config.KeyNotFoundError, err) - - _, err = c.Strings("NameInvalid") - assert.Equal(t, config.InvalidValueTypeError, err) -} - -func TestConfigContainer_Set(t *testing.T) { - data := ` -Name=["Tom", "Jerry"] -NameInvalid="Tom" -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - assert.Nil(t, err) - assert.NotNil(t, c) - - err = c.Set("Age", "11") - assert.Nil(t, err) - age, err := c.String("Age") - assert.Nil(t, err) - assert.Equal(t, "11", age) -} - -func TestConfigContainer_SubAndMushall(t *testing.T) { - data := ` -[servers] - - # You can indent as you please. Tabs or spaces. TOML don't care. - [servers.alpha] - ip = "10.0.0.1" - dc = "eqdc10" - - [servers.beta] - ip = "10.0.0.2" - dc = "eqdc10" -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - assert.Nil(t, err) - assert.NotNil(t, c) - - sub, err := c.Sub("servers") - assert.Nil(t, err) - assert.NotNil(t, sub) - - sub, err = sub.Sub("alpha") - assert.Nil(t, err) - assert.NotNil(t, sub) - ip, err := sub.String("ip") - assert.Nil(t, err) - assert.Equal(t, "10.0.0.1", ip) - - svr := &Server{} - err = sub.Unmarshaler("", svr) - assert.Nil(t, err) - assert.Equal(t, "10.0.0.1", svr.Ip) - - svr = &Server{} - err = c.Unmarshaler("servers.alpha", svr) - assert.Nil(t, err) - assert.Equal(t, "10.0.0.1", svr.Ip) -} - -func TestConfigContainer_SaveConfigFile(t *testing.T) { - filename := "test_config.toml" - path := os.TempDir() + string(os.PathSeparator) + filename - data := ` -[servers] - - # You can indent as you please. Tabs or spaces. TOML don't care. - [servers.alpha] - ip = "10.0.0.1" - dc = "eqdc10" - - [servers.beta] - ip = "10.0.0.2" - dc = "eqdc10" -` - cfg := &Config{} - c, err := cfg.ParseData([]byte(data)) - - fmt.Println(path) - - assert.Nil(t, err) - assert.NotNil(t, c) - - sub, err := c.Sub("servers") - assert.Nil(t, err) - - err = sub.SaveConfigFile(path) - assert.Nil(t, err) -} - -type Server struct { - Ip string `toml:"ip"` -} diff --git a/core/config/xml/xml_test.go b/core/config/xml/xml_test.go deleted file mode 100644 index c6cf970d..00000000 --- a/core/config/xml/xml_test.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 xml - -import ( - "fmt" - "os" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/core/config" -) - -func TestXML(t *testing.T) { - - var ( - // xml parse should incluce in tags - xmlcontext = ` - -beeapi -8080 -3600 -3.1415976 -dev -false -true -${GOPATH} -${GOPATH||/home/go} - -1 -MySection - - -` - keyValue = map[string]interface{}{ - "appname": "beeapi", - "httpport": 8080, - "mysqlport": int64(3600), - "PI": 3.1415976, - "runmode": "dev", - "autorender": false, - "copyrequestbody": true, - "path1": os.Getenv("GOPATH"), - "path2": os.Getenv("GOPATH"), - "error": "", - "emptystrings": []string{}, - } - ) - - f, err := os.Create("testxml.conf") - if err != nil { - t.Fatal(err) - } - _, err = f.WriteString(xmlcontext) - if err != nil { - f.Close() - t.Fatal(err) - } - f.Close() - defer os.Remove("testxml.conf") - - xmlconf, err := config.NewConfig("xml", "testxml.conf") - if err != nil { - t.Fatal(err) - } - - var xmlsection map[string]string - xmlsection, err = xmlconf.GetSection("mysection") - if err != nil { - t.Fatal(err) - } - - if len(xmlsection) == 0 { - t.Error("section should not be empty") - } - - for k, v := range keyValue { - - var ( - value interface{} - err error - ) - - switch v.(type) { - case int: - value, err = xmlconf.Int(k) - case int64: - value, err = xmlconf.Int64(k) - case float64: - value, err = xmlconf.Float(k) - case bool: - value, err = xmlconf.Bool(k) - case []string: - value, err = xmlconf.Strings(k) - case string: - value, err = xmlconf.String(k) - default: - value, err = xmlconf.DIY(k) - } - if err != nil { - t.Errorf("get key %q value fatal,%v err %s", k, v, err) - } else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) { - t.Errorf("get key %q value, want %v got %v .", k, v, value) - } - - } - - if err = xmlconf.Set("name", "astaxie"); err != nil { - t.Fatal(err) - } - - res, _ := xmlconf.String("name") - if res != "astaxie" { - t.Fatal("get name error") - } - - sub, err := xmlconf.Sub("mysection") - assert.Nil(t, err) - assert.NotNil(t, sub) - name, err := sub.String("name") - assert.Nil(t, err) - assert.Equal(t, "MySection", name) - - id, err := sub.Int("id") - assert.Nil(t, err) - assert.Equal(t, 1, id) - - sec := &Section{} - - err = sub.Unmarshaler("", sec) - assert.Nil(t, err) - assert.Equal(t, "MySection", sec.Name) - - sec = &Section{} - - err = xmlconf.Unmarshaler("mysection", sec) - assert.Nil(t, err) - assert.Equal(t, "MySection", sec.Name) - -} - -type Section struct { - Name string `xml:"name"` -} diff --git a/core/config/yaml/yaml_test.go b/core/config/yaml/yaml_test.go deleted file mode 100644 index d18317db..00000000 --- a/core/config/yaml/yaml_test.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 yaml - -import ( - "fmt" - "os" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/core/config" -) - -func TestYaml(t *testing.T) { - - var ( - yamlcontext = ` -"appname": beeapi -"httpport": 8080 -"mysqlport": 3600 -"PI": 3.1415976 -"runmode": dev -"autorender": false -"copyrequestbody": true -"PATH": GOPATH -"path1": ${GOPATH} -"path2": ${GOPATH||/home/go} -"empty": "" -"user": - "name": "tom" - "age": 13 -` - - keyValue = map[string]interface{}{ - "appname": "beeapi", - "httpport": 8080, - "mysqlport": int64(3600), - "PI": 3.1415976, - "runmode": "dev", - "autorender": false, - "copyrequestbody": true, - "PATH": "GOPATH", - "path1": os.Getenv("GOPATH"), - "path2": os.Getenv("GOPATH"), - "error": "", - "emptystrings": []string{}, - } - ) - f, err := os.Create("testyaml.conf") - if err != nil { - t.Fatal(err) - } - _, err = f.WriteString(yamlcontext) - if err != nil { - f.Close() - t.Fatal(err) - } - f.Close() - defer os.Remove("testyaml.conf") - yamlconf, err := config.NewConfig("yaml", "testyaml.conf") - if err != nil { - t.Fatal(err) - } - - res, _ := yamlconf.String("appname") - if res != "beeapi" { - t.Fatal("appname not equal to beeapi") - } - - for k, v := range keyValue { - - var ( - value interface{} - err error - ) - - switch v.(type) { - case int: - value, err = yamlconf.Int(k) - case int64: - value, err = yamlconf.Int64(k) - case float64: - value, err = yamlconf.Float(k) - case bool: - value, err = yamlconf.Bool(k) - case []string: - value, err = yamlconf.Strings(k) - case string: - value, err = yamlconf.String(k) - default: - value, err = yamlconf.DIY(k) - } - if err != nil { - t.Errorf("get key %q value fatal,%v err %s", k, v, err) - } else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) { - t.Errorf("get key %q value, want %v got %v .", k, v, value) - } - - } - - if err = yamlconf.Set("name", "astaxie"); err != nil { - t.Fatal(err) - } - res, _ = yamlconf.String("name") - if res != "astaxie" { - t.Fatal("get name error") - } - - sub, err := yamlconf.Sub("user") - assert.Nil(t, err) - assert.NotNil(t, sub) - name, err := sub.String("name") - assert.Nil(t, err) - assert.Equal(t, "tom", name) - - age, err := sub.Int("age") - assert.Nil(t, err) - assert.Equal(t, 13, age) - - user := &User{} - - err = sub.Unmarshaler("", user) - assert.Nil(t, err) - assert.Equal(t, "tom", user.Name) - assert.Equal(t, 13, user.Age) - - user = &User{} - - err = yamlconf.Unmarshaler("user", user) - assert.Nil(t, err) - assert.Equal(t, "tom", user.Name) - assert.Equal(t, 13, user.Age) -} - -type User struct { - Name string `yaml:"name"` - Age int `yaml:"age"` -} diff --git a/core/governor/command.go b/core/governor/command.go deleted file mode 100644 index 75df5815..00000000 --- a/core/governor/command.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 governor - -import ( - "github.com/pkg/errors" -) - -// Command is an experimental interface -// We try to use this to decouple modules -// All other modules depends on this, and they register the command they support -// We may change the API in the future, so be careful about this. -type Command interface { - Execute(params ...interface{}) *Result -} - -var CommandNotFound = errors.New("Command not found") - -type Result struct { - // Status is the same as http.Status - Status int - Error error - Content interface{} -} - -func (r *Result) IsSuccess() bool { - return r.Status >= 200 && r.Status < 300 -} - -// CommandRegistry stores all commands -// name => command -type moduleCommands map[string]Command - -// Get returns command with the name -func (m moduleCommands) Get(name string) Command { - c, ok := m[name] - if ok { - return c - } - return &doNothingCommand{} -} - -// module name => moduleCommand -type commandRegistry map[string]moduleCommands - -// Get returns module's commands -func (c commandRegistry) Get(moduleName string) moduleCommands { - if mcs, ok := c[moduleName]; ok { - return mcs - } - res := make(moduleCommands) - c[moduleName] = res - return res -} - -var cmdRegistry = make(commandRegistry) - -// RegisterCommand is not thread-safe -// do not use it in concurrent case -func RegisterCommand(module string, commandName string, command Command) { - cmdRegistry.Get(module)[commandName] = command -} - -func GetCommand(module string, cmdName string) Command { - return cmdRegistry.Get(module).Get(cmdName) -} - -type doNothingCommand struct{} - -func (d *doNothingCommand) Execute(params ...interface{}) *Result { - return &Result{ - Status: 404, - Error: CommandNotFound, - } -} diff --git a/core/governor/profile_test.go b/core/governor/profile_test.go deleted file mode 100644 index 530b0637..00000000 --- a/core/governor/profile_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 governor - -import ( - "os" - "testing" -) - -func TestProcessInput(t *testing.T) { - ProcessInput("lookup goroutine", os.Stdout) - ProcessInput("lookup heap", os.Stdout) - ProcessInput("lookup threadcreate", os.Stdout) - ProcessInput("lookup block", os.Stdout) - ProcessInput("gc summary", os.Stdout) -} diff --git a/core/logs/access_log_test.go b/core/logs/access_log_test.go deleted file mode 100644 index f78a00a0..00000000 --- a/core/logs/access_log_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 logs - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestAccessLog_format(t *testing.T) { - alc := &AccessLogRecord{ - RequestTime: time.Date(2020, 9, 19, 21, 21, 21, 11, time.UTC), - } - - res := alc.format(apacheFormat) - println(res) - assert.Equal(t, " - - [19/Sep/2020 09:21:21] \" 0 0\" 0.000000 ", res) - - res = alc.format(jsonFormat) - assert.Equal(t, - "{\"remote_addr\":\"\",\"request_time\":\"2020-09-19T21:21:21.000000011Z\",\"request_method\":\"\",\"request\":\"\",\"server_protocol\":\"\",\"host\":\"\",\"status\":0,\"body_bytes_sent\":0,\"elapsed_time\":0,\"http_referrer\":\"\",\"http_user_agent\":\"\",\"remote_user\":\"\"}\n", res) - - AccessLog(alc, jsonFormat) -} diff --git a/core/logs/conn_test.go b/core/logs/conn_test.go deleted file mode 100644 index ca9ea1c7..00000000 --- a/core/logs/conn_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 logs - -import ( - "net" - "os" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -// ConnTCPListener takes a TCP listener and accepts n TCP connections -// Returns connections using connChan -func connTCPListener(t *testing.T, n int, ln net.Listener, connChan chan<- net.Conn) { - - // Listen and accept n incoming connections - for i := 0; i < n; i++ { - conn, err := ln.Accept() - if err != nil { - t.Log("Error accepting connection: ", err.Error()) - os.Exit(1) - } - - // Send accepted connection to channel - connChan <- conn - } - ln.Close() - close(connChan) -} - -func TestConn(t *testing.T) { - log := NewLogger(1000) - log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`) - log.Informational("informational") -} - -// need to rewrite this test, it's not stable -func TestReconnect(t *testing.T) { - // Setup connection listener - newConns := make(chan net.Conn) - connNum := 2 - ln, err := net.Listen("tcp", ":6002") - if err != nil { - t.Log("Error listening:", err.Error()) - os.Exit(1) - } - go connTCPListener(t, connNum, ln, newConns) - - // Setup logger - log := NewLogger(1000) - log.SetPrefix("test") - log.SetLogger(AdapterConn, `{"net":"tcp","reconnect":true,"level":6,"addr":":6002"}`) - log.Informational("informational 1") - - // Refuse first connection - first := <-newConns - first.Close() - - // Send another log after conn closed - log.Informational("informational 2") - - // Check if there was a second connection attempt - select { - case second := <-newConns: - second.Close() - default: - t.Error("Did not reconnect") - } -} - -func TestConnWriter_Format(t *testing.T) { - lg := &LogMsg{ - Level: LevelDebug, - Msg: "Hello, world", - When: time.Date(2020, 9, 19, 20, 12, 37, 9, time.UTC), - FilePath: "/user/home/main.go", - LineNumber: 13, - Prefix: "Cus", - } - cw := NewConn().(*connWriter) - res := cw.Format(lg) - assert.Equal(t, "[D] Cus Hello, world", res) -} diff --git a/core/logs/es/index.go b/core/logs/es/index.go deleted file mode 100644 index 0dafef4c..00000000 --- a/core/logs/es/index.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 es - -import ( - "fmt" - - "github.com/astaxie/beego/core/logs" -) - -// IndexNaming generate the index name -type IndexNaming interface { - IndexName(lm *logs.LogMsg) string -} - -var indexNaming IndexNaming = &defaultIndexNaming{} - -// SetIndexNaming will register global IndexNaming -func SetIndexNaming(i IndexNaming) { - indexNaming = i -} - -type defaultIndexNaming struct{} - -func (d *defaultIndexNaming) IndexName(lm *logs.LogMsg) string { - return fmt.Sprintf("%04d.%02d.%02d", lm.When.Year(), lm.When.Month(), lm.When.Day()) -} diff --git a/core/logs/es/index_test.go b/core/logs/es/index_test.go deleted file mode 100644 index 03e7a911..00000000 --- a/core/logs/es/index_test.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 es - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/core/logs" -) - -func TestDefaultIndexNaming_IndexName(t *testing.T) { - tm := time.Date(2020, 9, 12, 1, 34, 45, 234, time.UTC) - lm := &logs.LogMsg{ - When: tm, - } - - res := (&defaultIndexNaming{}).IndexName(lm) - assert.Equal(t, "2020.09.12", res) -} diff --git a/core/logs/formatter.go b/core/logs/formatter.go deleted file mode 100644 index 67500b2b..00000000 --- a/core/logs/formatter.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 logs - -import ( - "path" - "strconv" -) - -var formatterMap = make(map[string]LogFormatter, 4) - -type LogFormatter interface { - Format(lm *LogMsg) string -} - -// PatternLogFormatter provides a quick format method -// for example: -// tes := &PatternLogFormatter{Pattern: "%F:%n|%w %t>> %m", WhenFormat: "2006-01-02"} -// RegisterFormatter("tes", tes) -// SetGlobalFormatter("tes") -type PatternLogFormatter struct { - Pattern string - WhenFormat string -} - -func (p *PatternLogFormatter) getWhenFormatter() string { - s := p.WhenFormat - if s == "" { - s = "2006/01/02 15:04:05.123" // default style - } - return s -} - -func (p *PatternLogFormatter) Format(lm *LogMsg) string { - return p.ToString(lm) -} - -// RegisterFormatter register an formatter. Usually you should use this to extend your custom formatter -// for example: -// RegisterFormatter("my-fmt", &MyFormatter{}) -// logs.SetFormatter(Console, `{"formatter": "my-fmt"}`) -func RegisterFormatter(name string, fmtr LogFormatter) { - formatterMap[name] = fmtr -} - -func GetFormatter(name string) (LogFormatter, bool) { - res, ok := formatterMap[name] - return res, ok -} - -// 'w' when, 'm' msg,'f' filename,'F' full path,'n' line number -// 'l' level number, 't' prefix of level type, 'T' full name of level type -func (p *PatternLogFormatter) ToString(lm *LogMsg) string { - s := []rune(p.Pattern) - m := map[rune]string{ - 'w': lm.When.Format(p.getWhenFormatter()), - 'm': lm.Msg, - 'n': strconv.Itoa(lm.LineNumber), - 'l': strconv.Itoa(lm.Level), - 't': levelPrefix[lm.Level-1], - 'T': levelNames[lm.Level-1], - 'F': lm.FilePath, - } - _, m['f'] = path.Split(lm.FilePath) - res := "" - for i := 0; i < len(s)-1; i++ { - if s[i] == '%' { - if k, ok := m[s[i+1]]; ok { - res += k - i++ - continue - } - } - res += string(s[i]) - } - return res -} diff --git a/core/logs/formatter_test.go b/core/logs/formatter_test.go deleted file mode 100644 index a97765ac..00000000 --- a/core/logs/formatter_test.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 logs - -import ( - "encoding/json" - "errors" - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -type CustomFormatter struct{} - -func (c *CustomFormatter) Format(lm *LogMsg) string { - return "hello, msg: " + lm.Msg -} - -type TestLogger struct { - Formatter string `json:"formatter"` - Expected string - formatter LogFormatter -} - -func (t *TestLogger) Init(config string) error { - er := json.Unmarshal([]byte(config), t) - t.formatter, _ = GetFormatter(t.Formatter) - return er -} - -func (t *TestLogger) WriteMsg(lm *LogMsg) error { - msg := t.formatter.Format(lm) - if msg != t.Expected { - return errors.New("not equal") - } - return nil -} - -func (t *TestLogger) Destroy() { - panic("implement me") -} - -func (t *TestLogger) Flush() { - panic("implement me") -} - -func (t *TestLogger) SetFormatter(f LogFormatter) { - panic("implement me") -} - -func TestCustomFormatter(t *testing.T) { - RegisterFormatter("custom", &CustomFormatter{}) - tl := &TestLogger{ - Expected: "hello, msg: world", - } - assert.Nil(t, tl.Init(`{"formatter": "custom"}`)) - assert.Nil(t, tl.WriteMsg(&LogMsg{ - Msg: "world", - })) -} - -func TestPatternLogFormatter(t *testing.T) { - tes := &PatternLogFormatter{ - Pattern: "%F:%n|%w%t>> %m", - WhenFormat: "2006-01-02", - } - when := time.Now() - lm := &LogMsg{ - Msg: "message", - FilePath: "/User/go/beego/main.go", - Level: LevelWarn, - LineNumber: 10, - When: when, - } - got := tes.ToString(lm) - want := lm.FilePath + ":" + strconv.Itoa(lm.LineNumber) + "|" + - when.Format(tes.WhenFormat) + levelPrefix[lm.Level-1] + ">> " + lm.Msg - if got != want { - t.Errorf("want %s, got %s", want, got) - } -} diff --git a/core/logs/jianliao_test.go b/core/logs/jianliao_test.go deleted file mode 100644 index a1b2d076..00000000 --- a/core/logs/jianliao_test.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 logs - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestJLWriter_Format(t *testing.T) { - lg := &LogMsg{ - Level: LevelDebug, - Msg: "Hello, world", - When: time.Date(2020, 9, 19, 20, 12, 37, 9, time.UTC), - FilePath: "/user/home/main.go", - LineNumber: 13, - Prefix: "Cus", - } - jl := newJLWriter().(*JLWriter) - res := jl.Format(lg) - assert.Equal(t, "2020-09-19 20:12:37 [D] Cus Hello, world", res) -} diff --git a/core/logs/log_msg.go b/core/logs/log_msg.go deleted file mode 100644 index f96fa72f..00000000 --- a/core/logs/log_msg.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 logs - -import ( - "fmt" - "path" - "time" -) - -type LogMsg struct { - Level int - Msg string - When time.Time - FilePath string - LineNumber int - Args []interface{} - Prefix string - enableFullFilePath bool - enableFuncCallDepth bool -} - -// OldStyleFormat you should never invoke this -func (lm *LogMsg) OldStyleFormat() string { - msg := lm.Msg - - if len(lm.Args) > 0 { - lm.Msg = fmt.Sprintf(lm.Msg, lm.Args...) - } - - msg = lm.Prefix + " " + msg - - if lm.enableFuncCallDepth { - filePath := lm.FilePath - if !lm.enableFullFilePath { - _, filePath = path.Split(filePath) - } - msg = fmt.Sprintf("[%s:%d] %s", filePath, lm.LineNumber, msg) - } - - msg = levelPrefix[lm.Level] + " " + msg - return msg -} diff --git a/core/logs/log_msg_test.go b/core/logs/log_msg_test.go deleted file mode 100644 index f213ed42..00000000 --- a/core/logs/log_msg_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 logs - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestLogMsg_OldStyleFormat(t *testing.T) { - lg := &LogMsg{ - Level: LevelDebug, - Msg: "Hello, world", - When: time.Date(2020, 9, 19, 20, 12, 37, 9, time.UTC), - FilePath: "/user/home/main.go", - LineNumber: 13, - Prefix: "Cus", - } - res := lg.OldStyleFormat() - assert.Equal(t, "[D] Cus Hello, world", res) - - lg.enableFuncCallDepth = true - res = lg.OldStyleFormat() - assert.Equal(t, "[D] [main.go:13] Cus Hello, world", res) - - lg.enableFullFilePath = true - - res = lg.OldStyleFormat() - assert.Equal(t, "[D] [/user/home/main.go:13] Cus Hello, world", res) -} diff --git a/core/logs/log_test.go b/core/logs/log_test.go deleted file mode 100644 index 66f59108..00000000 --- a/core/logs/log_test.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 logs - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestBeeLogger_DelLogger(t *testing.T) { - prefix := "My-Cus" - l := GetLogger(prefix) - assert.NotNil(t, l) -} diff --git a/core/logs/slack.go b/core/logs/slack.go deleted file mode 100644 index b6e2f170..00000000 --- a/core/logs/slack.go +++ /dev/null @@ -1,82 +0,0 @@ -package logs - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - - "github.com/pkg/errors" -) - -// SLACKWriter implements beego LoggerInterface and is used to send jiaoliao webhook -type SLACKWriter struct { - WebhookURL string `json:"webhookurl"` - Level int `json:"level"` - formatter LogFormatter - Formatter string `json:"formatter"` -} - -// newSLACKWriter creates jiaoliao writer. -func newSLACKWriter() Logger { - res := &SLACKWriter{Level: LevelTrace} - res.formatter = res - return res -} - -func (s *SLACKWriter) Format(lm *LogMsg) string { - text := fmt.Sprintf("{\"text\": \"%s %s\"}", lm.When.Format("2006-01-02 15:04:05"), lm.OldStyleFormat()) - return text -} - -func (s *SLACKWriter) SetFormatter(f LogFormatter) { - s.formatter = f -} - -// Init SLACKWriter with json config string -func (s *SLACKWriter) Init(config string) error { - res := json.Unmarshal([]byte(config), s) - - if res == nil && len(s.Formatter) > 0 { - fmtr, ok := GetFormatter(s.Formatter) - if !ok { - return errors.New(fmt.Sprintf("the formatter with name: %s not found", s.Formatter)) - } - s.formatter = fmtr - } - - return res -} - -// WriteMsg write message in smtp writer. -// Sends an email with subject and only this message. -func (s *SLACKWriter) WriteMsg(lm *LogMsg) error { - if lm.Level > s.Level { - return nil - } - msg := s.Format(lm) - form := url.Values{} - form.Add("payload", msg) - - resp, err := http.PostForm(s.WebhookURL, form) - if err != nil { - return err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode) - } - return nil -} - -// Flush implementing method. empty. -func (s *SLACKWriter) Flush() { -} - -// Destroy implementing method. empty. -func (s *SLACKWriter) Destroy() { -} - -func init() { - Register(AdapterSlack, newSLACKWriter) -} diff --git a/core/utils/caller_test.go b/core/utils/caller_test.go deleted file mode 100644 index 0675f0aa..00000000 --- a/core/utils/caller_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 utils - -import ( - "strings" - "testing" -) - -func TestGetFuncName(t *testing.T) { - name := GetFuncName(TestGetFuncName) - t.Log(name) - if !strings.HasSuffix(name, ".TestGetFuncName") { - t.Error("get func name error") - } -} diff --git a/core/utils/debug_test.go b/core/utils/debug_test.go deleted file mode 100644 index efb8924e..00000000 --- a/core/utils/debug_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 utils - -import ( - "testing" -) - -type mytype struct { - next *mytype - prev *mytype -} - -func TestPrint(t *testing.T) { - Display("v1", 1, "v2", 2, "v3", 3) -} - -func TestPrintPoint(t *testing.T) { - var v1 = new(mytype) - var v2 = new(mytype) - - v1.prev = nil - v1.next = v2 - - v2.prev = v1 - v2.next = nil - - Display("v1", v1, "v2", v2) -} - -func TestPrintString(t *testing.T) { - str := GetDisplayString("v1", 1, "v2", 2) - println(str) -} diff --git a/core/utils/kv.go b/core/utils/kv.go deleted file mode 100644 index f4e6c4d4..00000000 --- a/core/utils/kv.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2020 beego-dev -// -// Licensed 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 utils - -type KV interface { - GetKey() interface{} - GetValue() interface{} -} - -// SimpleKV is common structure to store key-value pairs. -// When you need something like Pair, you can use this -type SimpleKV struct { - Key interface{} - Value interface{} -} - -var _ KV = new(SimpleKV) - -func (s *SimpleKV) GetKey() interface{} { - return s.Key -} - -func (s *SimpleKV) GetValue() interface{} { - return s.Value -} - -// KVs interface -type KVs interface { - GetValueOr(key interface{}, defValue interface{}) interface{} - Contains(key interface{}) bool - IfContains(key interface{}, action func(value interface{})) KVs -} - -// SimpleKVs will store SimpleKV collection as map -type SimpleKVs struct { - kvs map[interface{}]interface{} -} - -var _ KVs = new(SimpleKVs) - -// GetValueOr returns the value for a given key, if non-existant -// it returns defValue -func (kvs *SimpleKVs) GetValueOr(key interface{}, defValue interface{}) interface{} { - v, ok := kvs.kvs[key] - if ok { - return v - } - return defValue -} - -// Contains checks if a key exists -func (kvs *SimpleKVs) Contains(key interface{}) bool { - _, ok := kvs.kvs[key] - return ok -} - -// IfContains invokes the action on a key if it exists -func (kvs *SimpleKVs) IfContains(key interface{}, action func(value interface{})) KVs { - v, ok := kvs.kvs[key] - if ok { - action(v) - } - return kvs -} - -// NewKVs creates the *KVs instance -func NewKVs(kvs ...KV) KVs { - res := &SimpleKVs{ - kvs: make(map[interface{}]interface{}, len(kvs)), - } - for _, kv := range kvs { - res.kvs[kv.GetKey()] = kv.GetValue() - } - return res -} diff --git a/core/utils/kv_test.go b/core/utils/kv_test.go deleted file mode 100644 index 4c9643dc..00000000 --- a/core/utils/kv_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020 beego-dev -// -// Licensed 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 utils - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestKVs(t *testing.T) { - key := "my-key" - kvs := NewKVs(&SimpleKV{ - Key: key, - Value: 12, - }) - - assert.True(t, kvs.Contains(key)) - - v := kvs.GetValueOr(key, 13) - assert.Equal(t, 12, v) - - v = kvs.GetValueOr(`key-not-exists`, 8546) - assert.Equal(t, 8546, v) - -} diff --git a/core/utils/mail_test.go b/core/utils/mail_test.go deleted file mode 100644 index c38356a2..00000000 --- a/core/utils/mail_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 utils - -import "testing" - -func TestMail(t *testing.T) { - config := `{"username":"astaxie@gmail.com","password":"astaxie","host":"smtp.gmail.com","port":587}` - mail := NewEMail(config) - if mail.Username != "astaxie@gmail.com" { - t.Fatal("email parse get username error") - } - if mail.Password != "astaxie" { - t.Fatal("email parse get password error") - } - if mail.Host != "smtp.gmail.com" { - t.Fatal("email parse get host error") - } - if mail.Port != 587 { - t.Fatal("email parse get port error") - } - mail.To = []string{"xiemengjun@gmail.com"} - mail.From = "astaxie@gmail.com" - mail.Subject = "hi, just from beego!" - mail.Text = "Text Body is, of course, supported!" - mail.HTML = "

Fancy Html is supported, too!

" - mail.AttachFile("/Users/astaxie/github/beego/beego.go") - mail.Send() -} diff --git a/core/utils/pagination/doc.go b/core/utils/pagination/doc.go deleted file mode 100644 index b9c604b9..00000000 --- a/core/utils/pagination/doc.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -Package pagination provides utilities to setup a paginator within the -context of a http request. - -Usage - -In your beego.Controller: - - package controllers - - import "github.com/astaxie/beego/core/utils/pagination" - - type PostsController struct { - beego.Controller - } - - func (this *PostsController) ListAllPosts() { - // sets this.Data["paginator"] with the current offset (from the url query param) - postsPerPage := 20 - paginator := pagination.SetPaginator(this.Ctx, postsPerPage, CountPosts()) - - // fetch the next 20 posts - this.Data["posts"] = ListPostsByOffsetAndLimit(paginator.Offset(), postsPerPage) - } - - -In your view templates: - - {{if .paginator.HasPages}} - - {{end}} - -See also - -http://beego.me/docs/mvc/view/page.md - -*/ -package pagination diff --git a/core/utils/rand_test.go b/core/utils/rand_test.go deleted file mode 100644 index 6c238b5e..00000000 --- a/core/utils/rand_test.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2016 beego Author. All Rights Reserved. -// -// Licensed 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 utils - -import "testing" - -func TestRand_01(t *testing.T) { - bs0 := RandomCreateBytes(16) - bs1 := RandomCreateBytes(16) - - t.Log(string(bs0), string(bs1)) - if string(bs0) == string(bs1) { - t.FailNow() - } - - bs0 = RandomCreateBytes(4, []byte(`a`)...) - - if string(bs0) != "aaaa" { - t.FailNow() - } -} diff --git a/core/utils/safemap_test.go b/core/utils/safemap_test.go deleted file mode 100644 index 65085195..00000000 --- a/core/utils/safemap_test.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 utils - -import "testing" - -var safeMap *BeeMap - -func TestNewBeeMap(t *testing.T) { - safeMap = NewBeeMap() - if safeMap == nil { - t.Fatal("expected to return non-nil BeeMap", "got", safeMap) - } -} - -func TestSet(t *testing.T) { - safeMap = NewBeeMap() - if ok := safeMap.Set("astaxie", 1); !ok { - t.Error("expected", true, "got", false) - } -} - -func TestReSet(t *testing.T) { - safeMap := NewBeeMap() - if ok := safeMap.Set("astaxie", 1); !ok { - t.Error("expected", true, "got", false) - } - // set diff value - if ok := safeMap.Set("astaxie", -1); !ok { - t.Error("expected", true, "got", false) - } - - // set same value - if ok := safeMap.Set("astaxie", -1); ok { - t.Error("expected", false, "got", true) - } -} - -func TestCheck(t *testing.T) { - if exists := safeMap.Check("astaxie"); !exists { - t.Error("expected", true, "got", false) - } -} - -func TestGet(t *testing.T) { - if val := safeMap.Get("astaxie"); val.(int) != 1 { - t.Error("expected value", 1, "got", val) - } -} - -func TestDelete(t *testing.T) { - safeMap.Delete("astaxie") - if exists := safeMap.Check("astaxie"); exists { - t.Error("expected element to be deleted") - } -} - -func TestItems(t *testing.T) { - safeMap := NewBeeMap() - safeMap.Set("astaxie", "hello") - for k, v := range safeMap.Items() { - key := k.(string) - value := v.(string) - if key != "astaxie" { - t.Error("expected the key should be astaxie") - } - if value != "hello" { - t.Error("expected the value should be hello") - } - } -} - -func TestCount(t *testing.T) { - if count := safeMap.Count(); count != 0 { - t.Error("expected count to be", 0, "got", count) - } -} diff --git a/core/utils/slice_test.go b/core/utils/slice_test.go deleted file mode 100644 index 142dec96..00000000 --- a/core/utils/slice_test.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 utils - -import ( - "testing" -) - -func TestInSlice(t *testing.T) { - sl := []string{"A", "b"} - if !InSlice("A", sl) { - t.Error("should be true") - } - if InSlice("B", sl) { - t.Error("should be false") - } -} diff --git a/core/utils/time.go b/core/utils/time.go deleted file mode 100644 index 579b292a..00000000 --- a/core/utils/time.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 utils - -import ( - "fmt" - "time" -) - -// short string format -func ToShortTimeFormat(d time.Duration) string { - - u := uint64(d) - if u < uint64(time.Second) { - switch { - case u == 0: - return "0" - case u < uint64(time.Microsecond): - return fmt.Sprintf("%.2fns", float64(u)) - case u < uint64(time.Millisecond): - return fmt.Sprintf("%.2fus", float64(u)/1000) - default: - return fmt.Sprintf("%.2fms", float64(u)/1000/1000) - } - } else { - switch { - case u < uint64(time.Minute): - return fmt.Sprintf("%.2fs", float64(u)/1000/1000/1000) - case u < uint64(time.Hour): - return fmt.Sprintf("%.2fm", float64(u)/1000/1000/1000/60) - default: - return fmt.Sprintf("%.2fh", float64(u)/1000/1000/1000/60/60) - } - } - -} diff --git a/core/validation/validation_test.go b/core/validation/validation_test.go deleted file mode 100644 index bca4f560..00000000 --- a/core/validation/validation_test.go +++ /dev/null @@ -1,634 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 validation - -import ( - "regexp" - "testing" - "time" -) - -func TestRequired(t *testing.T) { - valid := Validation{} - - if valid.Required(nil, "nil").Ok { - t.Error("nil object should be false") - } - if !valid.Required(true, "bool").Ok { - t.Error("Bool value should always return true") - } - if !valid.Required(false, "bool").Ok { - t.Error("Bool value should always return true") - } - if valid.Required("", "string").Ok { - t.Error("\"'\" string should be false") - } - if valid.Required(" ", "string").Ok { - t.Error("\" \" string should be false") // For #2361 - } - if valid.Required("\n", "string").Ok { - t.Error("new line string should be false") // For #2361 - } - if !valid.Required("astaxie", "string").Ok { - t.Error("string should be true") - } - if valid.Required(0, "zero").Ok { - t.Error("Integer should not be equal 0") - } - if !valid.Required(1, "int").Ok { - t.Error("Integer except 0 should be true") - } - if !valid.Required(time.Now(), "time").Ok { - t.Error("time should be true") - } - if valid.Required([]string{}, "emptySlice").Ok { - t.Error("empty slice should be false") - } - if !valid.Required([]interface{}{"ok"}, "slice").Ok { - t.Error("slice should be true") - } -} - -func TestMin(t *testing.T) { - valid := Validation{} - - if valid.Min(-1, 0, "min0").Ok { - t.Error("-1 is less than the minimum value of 0 should be false") - } - if !valid.Min(1, 0, "min0").Ok { - t.Error("1 is greater or equal than the minimum value of 0 should be true") - } -} - -func TestMax(t *testing.T) { - valid := Validation{} - - if valid.Max(1, 0, "max0").Ok { - t.Error("1 is greater than the minimum value of 0 should be false") - } - if !valid.Max(-1, 0, "max0").Ok { - t.Error("-1 is less or equal than the maximum value of 0 should be true") - } -} - -func TestRange(t *testing.T) { - valid := Validation{} - - if valid.Range(-1, 0, 1, "range0_1").Ok { - t.Error("-1 is between 0 and 1 should be false") - } - if !valid.Range(1, 0, 1, "range0_1").Ok { - t.Error("1 is between 0 and 1 should be true") - } -} - -func TestMinSize(t *testing.T) { - valid := Validation{} - - if valid.MinSize("", 1, "minSize1").Ok { - t.Error("the length of \"\" is less than the minimum value of 1 should be false") - } - if !valid.MinSize("ok", 1, "minSize1").Ok { - t.Error("the length of \"ok\" is greater or equal than the minimum value of 1 should be true") - } - if valid.MinSize([]string{}, 1, "minSize1").Ok { - t.Error("the length of empty slice is less than the minimum value of 1 should be false") - } - if !valid.MinSize([]interface{}{"ok"}, 1, "minSize1").Ok { - t.Error("the length of [\"ok\"] is greater or equal than the minimum value of 1 should be true") - } -} - -func TestMaxSize(t *testing.T) { - valid := Validation{} - - if valid.MaxSize("ok", 1, "maxSize1").Ok { - t.Error("the length of \"ok\" is greater than the maximum value of 1 should be false") - } - if !valid.MaxSize("", 1, "maxSize1").Ok { - t.Error("the length of \"\" is less or equal than the maximum value of 1 should be true") - } - if valid.MaxSize([]interface{}{"ok", false}, 1, "maxSize1").Ok { - t.Error("the length of [\"ok\", false] is greater than the maximum value of 1 should be false") - } - if !valid.MaxSize([]string{}, 1, "maxSize1").Ok { - t.Error("the length of empty slice is less or equal than the maximum value of 1 should be true") - } -} - -func TestLength(t *testing.T) { - valid := Validation{} - - if valid.Length("", 1, "length1").Ok { - t.Error("the length of \"\" must equal 1 should be false") - } - if !valid.Length("1", 1, "length1").Ok { - t.Error("the length of \"1\" must equal 1 should be true") - } - if valid.Length([]string{}, 1, "length1").Ok { - t.Error("the length of empty slice must equal 1 should be false") - } - if !valid.Length([]interface{}{"ok"}, 1, "length1").Ok { - t.Error("the length of [\"ok\"] must equal 1 should be true") - } -} - -func TestAlpha(t *testing.T) { - valid := Validation{} - - if valid.Alpha("a,1-@ $", "alpha").Ok { - t.Error("\"a,1-@ $\" are valid alpha characters should be false") - } - if !valid.Alpha("abCD", "alpha").Ok { - t.Error("\"abCD\" are valid alpha characters should be true") - } -} - -func TestNumeric(t *testing.T) { - valid := Validation{} - - if valid.Numeric("a,1-@ $", "numeric").Ok { - t.Error("\"a,1-@ $\" are valid numeric characters should be false") - } - if !valid.Numeric("1234", "numeric").Ok { - t.Error("\"1234\" are valid numeric characters should be true") - } -} - -func TestAlphaNumeric(t *testing.T) { - valid := Validation{} - - if valid.AlphaNumeric("a,1-@ $", "alphaNumeric").Ok { - t.Error("\"a,1-@ $\" are valid alpha or numeric characters should be false") - } - if !valid.AlphaNumeric("1234aB", "alphaNumeric").Ok { - t.Error("\"1234aB\" are valid alpha or numeric characters should be true") - } -} - -func TestMatch(t *testing.T) { - valid := Validation{} - - if valid.Match("suchuangji@gmail", regexp.MustCompile(`^\w+@\w+\.\w+$`), "match").Ok { - t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be false") - } - if !valid.Match("suchuangji@gmail.com", regexp.MustCompile(`^\w+@\w+\.\w+$`), "match").Ok { - t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be true") - } -} - -func TestNoMatch(t *testing.T) { - valid := Validation{} - - if valid.NoMatch("123@gmail", regexp.MustCompile(`[^\w\d]`), "nomatch").Ok { - t.Error("\"123@gmail\" not match \"[^\\w\\d]\" should be false") - } - if !valid.NoMatch("123gmail", regexp.MustCompile(`[^\w\d]`), "match").Ok { - t.Error("\"123@gmail\" not match \"[^\\w\\d@]\" should be true") - } -} - -func TestAlphaDash(t *testing.T) { - valid := Validation{} - - if valid.AlphaDash("a,1-@ $", "alphaDash").Ok { - t.Error("\"a,1-@ $\" are valid alpha or numeric or dash(-_) characters should be false") - } - if !valid.AlphaDash("1234aB-_", "alphaDash").Ok { - t.Error("\"1234aB\" are valid alpha or numeric or dash(-_) characters should be true") - } -} - -func TestEmail(t *testing.T) { - valid := Validation{} - - if valid.Email("not@a email", "email").Ok { - t.Error("\"not@a email\" is a valid email address should be false") - } - if !valid.Email("suchuangji@gmail.com", "email").Ok { - t.Error("\"suchuangji@gmail.com\" is a valid email address should be true") - } - if valid.Email("@suchuangji@gmail.com", "email").Ok { - t.Error("\"@suchuangji@gmail.com\" is a valid email address should be false") - } - if valid.Email("suchuangji@gmail.com ok", "email").Ok { - t.Error("\"suchuangji@gmail.com ok\" is a valid email address should be false") - } -} - -func TestIP(t *testing.T) { - valid := Validation{} - - if valid.IP("11.255.255.256", "IP").Ok { - t.Error("\"11.255.255.256\" is a valid ip address should be false") - } - if !valid.IP("01.11.11.11", "IP").Ok { - t.Error("\"suchuangji@gmail.com\" is a valid ip address should be true") - } -} - -func TestBase64(t *testing.T) { - valid := Validation{} - - if valid.Base64("suchuangji@gmail.com", "base64").Ok { - t.Error("\"suchuangji@gmail.com\" are a valid base64 characters should be false") - } - if !valid.Base64("c3VjaHVhbmdqaUBnbWFpbC5jb20=", "base64").Ok { - t.Error("\"c3VjaHVhbmdqaUBnbWFpbC5jb20=\" are a valid base64 characters should be true") - } -} - -func TestMobile(t *testing.T) { - valid := Validation{} - - validMobiles := []string{ - "19800008888", - "18800008888", - "18000008888", - "8618300008888", - "+8614700008888", - "17300008888", - "+8617100008888", - "8617500008888", - "8617400008888", - "16200008888", - "16500008888", - "16600008888", - "16700008888", - "13300008888", - "14900008888", - "15300008888", - "17300008888", - "17700008888", - "18000008888", - "18900008888", - "19100008888", - "19900008888", - "19300008888", - "13000008888", - "13100008888", - "13200008888", - "14500008888", - "15500008888", - "15600008888", - "16600008888", - "17100008888", - "17500008888", - "17600008888", - "18500008888", - "18600008888", - "13400008888", - "13500008888", - "13600008888", - "13700008888", - "13800008888", - "13900008888", - "14700008888", - "15000008888", - "15100008888", - "15200008888", - "15800008888", - "15900008888", - "17200008888", - "17800008888", - "18200008888", - "18300008888", - "18400008888", - "18700008888", - "18800008888", - "19800008888", - } - - for _, m := range validMobiles { - if !valid.Mobile(m, "mobile").Ok { - t.Error(m + " is a valid mobile phone number should be true") - } - } -} - -func TestTel(t *testing.T) { - valid := Validation{} - - if valid.Tel("222-00008888", "telephone").Ok { - t.Error("\"222-00008888\" is a valid telephone number should be false") - } - if !valid.Tel("022-70008888", "telephone").Ok { - t.Error("\"022-70008888\" is a valid telephone number should be true") - } - if !valid.Tel("02270008888", "telephone").Ok { - t.Error("\"02270008888\" is a valid telephone number should be true") - } - if !valid.Tel("70008888", "telephone").Ok { - t.Error("\"70008888\" is a valid telephone number should be true") - } -} - -func TestPhone(t *testing.T) { - valid := Validation{} - - if valid.Phone("222-00008888", "phone").Ok { - t.Error("\"222-00008888\" is a valid phone number should be false") - } - if !valid.Mobile("+8614700008888", "phone").Ok { - t.Error("\"+8614700008888\" is a valid phone number should be true") - } - if !valid.Tel("02270008888", "phone").Ok { - t.Error("\"02270008888\" is a valid phone number should be true") - } -} - -func TestZipCode(t *testing.T) { - valid := Validation{} - - if valid.ZipCode("", "zipcode").Ok { - t.Error("\"00008888\" is a valid zipcode should be false") - } - if !valid.ZipCode("536000", "zipcode").Ok { - t.Error("\"536000\" is a valid zipcode should be true") - } -} - -func TestValid(t *testing.T) { - type user struct { - ID int - Name string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"` - Age int `valid:"Required;Range(1, 140)"` - } - valid := Validation{} - - u := user{Name: "test@/test/;com", Age: 40} - b, err := valid.Valid(u) - if err != nil { - t.Fatal(err) - } - if !b { - t.Error("validation should be passed") - } - - uptr := &user{Name: "test", Age: 40} - valid.Clear() - b, err = valid.Valid(uptr) - if err != nil { - t.Fatal(err) - } - if b { - t.Error("validation should not be passed") - } - if len(valid.Errors) != 1 { - t.Fatalf("valid errors len should be 1 but got %d", len(valid.Errors)) - } - if valid.Errors[0].Key != "Name.Match" { - t.Errorf("Message key should be `Name.Match` but got %s", valid.Errors[0].Key) - } - - u = user{Name: "test@/test/;com", Age: 180} - valid.Clear() - b, err = valid.Valid(u) - if err != nil { - t.Fatal(err) - } - if b { - t.Error("validation should not be passed") - } - if len(valid.Errors) != 1 { - t.Fatalf("valid errors len should be 1 but got %d", len(valid.Errors)) - } - if valid.Errors[0].Key != "Age.Range." { - t.Errorf("Message key should be `Age.Range` but got %s", valid.Errors[0].Key) - } -} - -func TestRecursiveValid(t *testing.T) { - type User struct { - ID int - Name string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"` - Age int `valid:"Required;Range(1, 140)"` - } - - type AnonymouseUser struct { - ID2 int - Name2 string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"` - Age2 int `valid:"Required;Range(1, 140)"` - } - - type Account struct { - Password string `valid:"Required"` - U User - AnonymouseUser - } - valid := Validation{} - - u := Account{Password: "abc123_", U: User{}} - b, err := valid.RecursiveValid(u) - if err != nil { - t.Fatal(err) - } - if b { - t.Error("validation should not be passed") - } -} - -func TestSkipValid(t *testing.T) { - type User struct { - ID int - - Email string `valid:"Email"` - ReqEmail string `valid:"Required;Email"` - - IP string `valid:"IP"` - ReqIP string `valid:"Required;IP"` - - Mobile string `valid:"Mobile"` - ReqMobile string `valid:"Required;Mobile"` - - Tel string `valid:"Tel"` - ReqTel string `valid:"Required;Tel"` - - Phone string `valid:"Phone"` - ReqPhone string `valid:"Required;Phone"` - - ZipCode string `valid:"ZipCode"` - ReqZipCode string `valid:"Required;ZipCode"` - } - - u := User{ - ReqEmail: "a@a.com", - ReqIP: "127.0.0.1", - ReqMobile: "18888888888", - ReqTel: "02088888888", - ReqPhone: "02088888888", - ReqZipCode: "510000", - } - - valid := Validation{} - b, err := valid.Valid(u) - if err != nil { - t.Fatal(err) - } - if b { - t.Fatal("validation should not be passed") - } - - valid = Validation{RequiredFirst: true} - b, err = valid.Valid(u) - if err != nil { - t.Fatal(err) - } - if !b { - t.Fatal("validation should be passed") - } -} - -func TestPointer(t *testing.T) { - type User struct { - ID int - - Email *string `valid:"Email"` - ReqEmail *string `valid:"Required;Email"` - } - - u := User{ - ReqEmail: nil, - Email: nil, - } - - valid := Validation{} - b, err := valid.Valid(u) - if err != nil { - t.Fatal(err) - } - if b { - t.Fatal("validation should not be passed") - } - - validEmail := "a@a.com" - u = User{ - ReqEmail: &validEmail, - Email: nil, - } - - valid = Validation{RequiredFirst: true} - b, err = valid.Valid(u) - if err != nil { - t.Fatal(err) - } - if !b { - t.Fatal("validation should be passed") - } - - u = User{ - ReqEmail: &validEmail, - Email: nil, - } - - valid = Validation{} - b, err = valid.Valid(u) - if err != nil { - t.Fatal(err) - } - if b { - t.Fatal("validation should not be passed") - } - - invalidEmail := "a@a" - u = User{ - ReqEmail: &validEmail, - Email: &invalidEmail, - } - - valid = Validation{RequiredFirst: true} - b, err = valid.Valid(u) - if err != nil { - t.Fatal(err) - } - if b { - t.Fatal("validation should not be passed") - } - - u = User{ - ReqEmail: &validEmail, - Email: &invalidEmail, - } - - valid = Validation{} - b, err = valid.Valid(u) - if err != nil { - t.Fatal(err) - } - if b { - t.Fatal("validation should not be passed") - } -} - -func TestCanSkipAlso(t *testing.T) { - type User struct { - ID int - - Email string `valid:"Email"` - ReqEmail string `valid:"Required;Email"` - MatchRange int `valid:"Range(10, 20)"` - } - - u := User{ - ReqEmail: "a@a.com", - Email: "", - MatchRange: 0, - } - - valid := Validation{RequiredFirst: true} - b, err := valid.Valid(u) - if err != nil { - t.Fatal(err) - } - if b { - t.Fatal("validation should not be passed") - } - - valid = Validation{RequiredFirst: true} - valid.CanSkipAlso("Range") - b, err = valid.Valid(u) - if err != nil { - t.Fatal(err) - } - if !b { - t.Fatal("validation should be passed") - } - -} - -func TestFieldNoEmpty(t *testing.T) { - type User struct { - Name string `json:"name" valid:"Match(/^[a-zA-Z][a-zA-Z0-9._-]{0,31}$/)"` - } - u := User{ - Name: "*", - } - - valid := Validation{} - b, err := valid.Valid(u) - if err != nil { - t.Fatal(err) - } - if b { - t.Fatal("validation should be passed") - } - if len(valid.Errors) == 0 { - t.Fatal("validation should be passed") - } - validErr := valid.Errors[0] - if len(validErr.Field) == 0 { - t.Fatal("validation should be passed") - } -} diff --git a/doc.go b/doc.go index 6975885a..8825bd29 100644 --- a/doc.go +++ b/doc.go @@ -1,15 +1,17 @@ -// Copyright 2020 -// -// Licensed 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 beego provide a MVC framework +beego: an open-source, high-performance, modular, full-stack web framework +It is used for rapid development of RESTful APIs, web apps and backend services in Go. +beego is inspired by Tornado, Sinatra and Flask with the added benefit of some Go-specific features such as interfaces and struct embedding. + + package main + import "github.com/astaxie/beego" + + func main() { + beego.Run() + } + +more information: http://beego.me +*/ package beego diff --git a/server/web/error.go b/error.go similarity index 94% rename from server/web/error.go rename to error.go index b5ef1d2d..e5e9fd47 100644 --- a/server/web/error.go +++ b/error.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "fmt" @@ -23,14 +23,12 @@ import ( "strconv" "strings" - "github.com/astaxie/beego" - "github.com/astaxie/beego/core/utils" - - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/context" + "github.com/astaxie/beego/utils" ) const ( - errorTypeHandler = iota + errorTypeHandler = iota errorTypeController ) @@ -92,7 +90,7 @@ func showErr(err interface{}, ctx *context.Context, stack string) { "RequestURL": ctx.Input.URI(), "RemoteAddr": ctx.Input.IP(), "Stack": stack, - "BeegoVersion": beego.VERSION, + "BeegoVersion": VERSION, "GoVersion": runtime.Version(), } t.Execute(ctx.ResponseWriter, data) @@ -361,25 +359,11 @@ func gatewayTimeout(rw http.ResponseWriter, r *http.Request) { ) } -// show 413 Payload Too Large -func payloadTooLarge(rw http.ResponseWriter, r *http.Request) { - responseError(rw, r, - 413, - `
The page you have requested is unavailable. -
Perhaps you are here because:

-
    -
    The request entity is larger than limits defined by server. -
    Please change the request entity and try again. -
- `, - ) -} - func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errContent string) { t, _ := template.New("beegoerrortemp").Parse(errtpl) data := M{ "Title": http.StatusText(errCode), - "BeegoVersion": beego.VERSION, + "BeegoVersion": VERSION, "Content": template.HTML(errContent), } t.Execute(rw, data) @@ -389,7 +373,7 @@ func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errCont // usage: // beego.ErrorHandler("404",NotFound) // beego.ErrorHandler("500",InternalServerError) -func ErrorHandler(code string, h http.HandlerFunc) *HttpServer { +func ErrorHandler(code string, h http.HandlerFunc) *App { ErrorMaps[code] = &errorInfo{ errorType: errorTypeHandler, handler: h, @@ -401,7 +385,7 @@ func ErrorHandler(code string, h http.HandlerFunc) *HttpServer { // ErrorController registers ControllerInterface to each http err code string. // usage: // beego.ErrorController(&controllers.ErrorController{}) -func ErrorController(c ControllerInterface) *HttpServer { +func ErrorController(c ControllerInterface) *App { reflectVal := reflect.ValueOf(c) rt := reflectVal.Type() ct := reflect.Indirect(reflectVal).Type() diff --git a/server/web/error_test.go b/error_test.go similarity index 99% rename from server/web/error_test.go rename to error_test.go index 2685a985..378aa953 100644 --- a/server/web/error_test.go +++ b/error_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "net/http" diff --git a/adapter/filter.go b/filter.go similarity index 79% rename from adapter/filter.go rename to filter.go index 283d8879..9cc6e913 100644 --- a/adapter/filter.go +++ b/filter.go @@ -12,13 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -package adapter +package beego -import ( - "github.com/astaxie/beego/adapter/context" - "github.com/astaxie/beego/server/web" - beecontext "github.com/astaxie/beego/server/web/context" -) +import "github.com/astaxie/beego/context" // FilterFunc defines a filter function which is invoked before the controller handler is executed. type FilterFunc func(*context.Context) @@ -26,11 +22,23 @@ type FilterFunc func(*context.Context) // FilterRouter defines a filter operation which is invoked before the controller handler is executed. // It can match the URL against a pattern, and execute a filter function // when a request with a matching URL arrives. -type FilterRouter web.FilterRouter +type FilterRouter struct { + filterFunc FilterFunc + tree *Tree + pattern string + returnOnOutput bool + resetParams bool +} // ValidRouter checks if the current request is matched by this filter. // If the request is matched, the values of the URL parameters defined // by the filter pattern are also returned. func (f *FilterRouter) ValidRouter(url string, ctx *context.Context) bool { - return (*web.FilterRouter)(f).ValidRouter(url, (*beecontext.Context)(ctx)) + isOk := f.tree.Match(url, ctx) + if isOk != nil { + if b, ok := isOk.(bool); ok { + return b + } + } + return false } diff --git a/server/web/filter_test.go b/filter_test.go similarity index 97% rename from server/web/filter_test.go rename to filter_test.go index 11f575d6..4ca4d2b8 100644 --- a/server/web/filter_test.go +++ b/filter_test.go @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "net/http" "net/http/httptest" "testing" - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/context" ) var FilterUser = func(ctx *context.Context) { diff --git a/server/web/flash.go b/flash.go similarity index 99% rename from server/web/flash.go rename to flash.go index 55f6435d..a6485a17 100644 --- a/server/web/flash.go +++ b/flash.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "fmt" diff --git a/server/web/flash_test.go b/flash_test.go similarity index 99% rename from server/web/flash_test.go rename to flash_test.go index 2deef54e..d5e9608d 100644 --- a/server/web/flash_test.go +++ b/flash_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "net/http" diff --git a/server/web/fs.go b/fs.go similarity index 99% rename from server/web/fs.go rename to fs.go index 5457457a..41cc6f6e 100644 --- a/server/web/fs.go +++ b/fs.go @@ -1,4 +1,4 @@ -package web +package beego import ( "net/http" diff --git a/go.mod b/go.mod index 7527aa47..ec500f51 100644 --- a/go.mod +++ b/go.mod @@ -7,53 +7,34 @@ require ( github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 github.com/casbin/casbin v1.7.0 github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 - github.com/coreos/etcd v3.3.25+incompatible - github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect - github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808 // indirect github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a // indirect github.com/elastic/go-elasticsearch/v6 v6.8.5 github.com/elazarl/go-bindata-assetfs v1.0.0 - github.com/go-kit/kit v0.9.0 github.com/go-redis/redis v6.14.2+incompatible - github.com/go-redis/redis/v7 v7.4.0 github.com/go-sql-driver/mysql v1.5.0 - github.com/gogo/protobuf v1.3.1 + github.com/gogo/protobuf v1.1.1 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect github.com/gomodule/redigo v2.0.0+incompatible - github.com/google/go-cmp v0.5.0 // indirect - github.com/google/uuid v1.1.1 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/hashicorp/golang-lru v0.5.4 github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6 github.com/lib/pq v1.0.0 github.com/mattn/go-sqlite3 v2.0.3+incompatible - github.com/mitchellh/mapstructure v1.3.3 - github.com/opentracing/opentracing-go v1.2.0 - github.com/pelletier/go-toml v1.8.1 - github.com/pkg/errors v0.9.1 + github.com/pelletier/go-toml v1.2.0 // indirect github.com/prometheus/client_golang v1.7.0 github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c // indirect github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b // indirect - go.etcd.io/etcd v3.3.25+incompatible // indirect - go.uber.org/zap v1.15.0 // indirect - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect - golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 // indirect - golang.org/x/text v0.3.3 // indirect + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 golang.org/x/tools v0.0.0-20200117065230-39095c1d176c - google.golang.org/grpc v1.26.0 gopkg.in/yaml.v2 v2.2.8 - honnef.co/go/tools v0.0.1-2020.1.5 // indirect ) replace golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 => github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85 replace gopkg.in/yaml.v2 v2.2.1 => github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d -go 1.14 +go 1.13 diff --git a/go.sum b/go.sum index 994d1ec4..c7b861ac 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,3 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= @@ -21,22 +20,10 @@ github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVx github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= github.com/casbin/casbin v1.7.0 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ= github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/coreos/etcd v0.5.0-alpha.5 h1:0Qi6Jzjk2CDuuGlIeecpu+em2nrjhOgz2wsIwCmQHmc= -github.com/coreos/etcd v3.3.25+incompatible h1:0GQEw6h3YnuOVdtwygkIfJ+Omx0tZ8/QkVyXI4LkbeY= -github.com/coreos/etcd v3.3.25+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= -github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d h1:OMrhQqj1QCyDT2sxHCDjE+k8aMdn2ngTCGG7g4wrdLo= github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U= github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808 h1:8s2l8TVUwMXl6tZMe3+hPCRJ25nQXiA3d1x622JtOqc= @@ -54,45 +41,30 @@ github.com/elastic/go-elasticsearch/v6 v6.8.5 h1:U2HtkBseC1FNBmDr0TR2tKltL6FxoY+ github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0= github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= -github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= -github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d h1:xy93KVe+KrIIwWDEAfQBdIfsiHJkepbYsDr+VY3g9/o= github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -100,18 +72,11 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pO github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -119,10 +84,7 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -136,8 +98,6 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= -github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -146,26 +106,19 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/pingcap/tidb v2.0.11+incompatible/go.mod h1:I8C6jrPINP2rrVunTRd7C9fRRhQrtR43S1/CL5ix/yQ= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -174,7 +127,6 @@ github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -184,7 +136,6 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo= github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0 h1:QIF48X1cihydXibm+4wfAc0r/qyPyuFiPFRNphdMpEE= @@ -208,137 +159,52 @@ github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2K github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b h1:0Ve0/CCjiAiyKddUMUn3RwIGlq2iTW4GuVzyoKBYO/8= github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU= -go.etcd.io/etcd v0.5.0-alpha.5 h1:VOolFSo3XgsmnYDLozjvZ6JL6AAwIDu1Yx1y+4EYLDo= -go.etcd.io/etcd v3.3.25+incompatible h1:V1RzkZJj9LqsJRy+TUBgpWSbZXITLB819lstuTFoZOY= -go.etcd.io/etcd v3.3.25+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= -go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= -go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4= -golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200117065230-39095c1d176c h1:FodBYPZKH5tAN2O60HlglMwXGAeV/4k+NKbli79M/2c= golang.org/x/tools v0.0.0-20200117065230-39095c1d176c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200815165600-90abf76919f3 h1:0aScV/0rLmANzEYIhjCOi2pTvDyhZNduBUMD2q3iqs4= -golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1 h1:SfXqXS5hkufcdZ/mHtYCh53P2b+92WQq/DZcKLgsFRs= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= @@ -349,9 +215,3 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.5 h1:nI5egYTGJakVyOryqLs1cQO5dO0ksin5XXs2pspk75k= -honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= diff --git a/server/web/grace/grace.go b/grace/grace.go similarity index 100% rename from server/web/grace/grace.go rename to grace/grace.go diff --git a/server/web/grace/server.go b/grace/server.go similarity index 98% rename from server/web/grace/server.go rename to grace/server.go index 13fa6e34..008a6171 100644 --- a/server/web/grace/server.go +++ b/grace/server.go @@ -29,8 +29,8 @@ type Server struct { terminalChan chan error } -// Serve accepts incoming connections on the Listener l -// and creates a new service goroutine for each. +// Serve accepts incoming connections on the Listener l, +// creating a new service goroutine for each. // The service goroutines read requests and then call srv.Handler to reply to them. func (srv *Server) Serve() (err error) { srv.state = StateRunning diff --git a/server/web/hooks.go b/hooks.go similarity index 84% rename from server/web/hooks.go rename to hooks.go index 58e2c0f3..b8671d35 100644 --- a/server/web/hooks.go +++ b/hooks.go @@ -1,4 +1,4 @@ -package web +package beego import ( "encoding/json" @@ -6,9 +6,9 @@ import ( "net/http" "path/filepath" - "github.com/astaxie/beego/core/logs" - "github.com/astaxie/beego/server/web/context" - "github.com/astaxie/beego/server/web/session" + "github.com/astaxie/beego/context" + "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/session" ) // register MIME type with content type @@ -34,7 +34,6 @@ func registerDefaultErrorHandler() error { "504": gatewayTimeout, "417": invalidxsrf, "422": missingxsrf, - "413": payloadTooLarge, } for e, h := range m { if _, ok := ErrorMaps[e]; !ok { @@ -47,9 +46,9 @@ func registerDefaultErrorHandler() error { func registerSession() error { if BConfig.WebConfig.Session.SessionOn { var err error - sessionConfig, err := AppConfig.String("sessionConfig") + sessionConfig := AppConfig.String("sessionConfig") conf := new(session.ManagerConfig) - if sessionConfig == "" || err != nil { + if sessionConfig == "" { conf.CookieName = BConfig.WebConfig.Session.SessionName conf.EnableSetCookie = BConfig.WebConfig.Session.SessionAutoSetCookie conf.Gclifetime = BConfig.WebConfig.Session.SessionGCMaxLifetime @@ -85,6 +84,13 @@ func registerTemplate() error { return nil } +func registerAdmin() error { + if BConfig.Listen.EnableAdmin { + go beeAdminApp.Run() + } + return nil +} + func registerGzip() error { if BConfig.EnableGzip { context.InitGzip( @@ -95,13 +101,3 @@ func registerGzip() error { } return nil } - -func registerCommentRouter() error { - if BConfig.RunMode == DEV { - if err := parserPkg(filepath.Join(WorkPath, BConfig.WebConfig.CommentRouterPath)); err != nil { - return err - } - } - - return nil -} diff --git a/client/httplib/README.md b/httplib/README.md similarity index 100% rename from client/httplib/README.md rename to httplib/README.md diff --git a/client/httplib/httplib.go b/httplib/httplib.go similarity index 85% rename from client/httplib/httplib.go rename to httplib/httplib.go index f8ab80a1..e094a6a6 100644 --- a/client/httplib/httplib.go +++ b/httplib/httplib.go @@ -34,7 +34,6 @@ package httplib import ( "bytes" "compress/gzip" - "context" "crypto/tls" "encoding/json" "encoding/xml" @@ -67,11 +66,6 @@ var defaultSetting = BeegoHTTPSettings{ var defaultCookieJar http.CookieJar var settingMutex sync.Mutex -// it will be the last filter and execute request.Do -var doRequestFilter = func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) { - return req.doRequest(ctx) -} - // createDefaultCookie creates a global cookiejar to store cookies. func createDefaultCookie() { settingMutex.Lock() @@ -79,14 +73,14 @@ func createDefaultCookie() { defaultCookieJar, _ = cookiejar.New(nil) } -// SetDefaultSetting overwrites default settings +// SetDefaultSetting Overwrite default settings func SetDefaultSetting(setting BeegoHTTPSettings) { settingMutex.Lock() defer settingMutex.Unlock() defaultSetting = setting } -// NewBeegoRequest returns *BeegoHttpRequest with specific method +// NewBeegoRequest return *BeegoHttpRequest with specific method func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest { var resp http.Response u, err := url.Parse(rawurl) @@ -150,11 +144,9 @@ type BeegoHTTPSettings struct { Gzip bool DumpBody bool Retries int // if set to -1 means will retry forever - RetryDelay time.Duration - FilterChains []FilterChain } -// BeegoHTTPRequest provides more useful methods than http.Request for requesting a url. +// BeegoHTTPRequest provides more useful methods for requesting one url than http.Request. type BeegoHTTPRequest struct { url string req *http.Request @@ -166,12 +158,12 @@ type BeegoHTTPRequest struct { dump []byte } -// GetRequest returns the request object +// GetRequest return the request object func (b *BeegoHTTPRequest) GetRequest() *http.Request { return b.req } -// Setting changes request settings +// Setting Change request settings func (b *BeegoHTTPRequest) Setting(setting BeegoHTTPSettings) *BeegoHTTPRequest { b.setting = setting return b @@ -202,27 +194,21 @@ func (b *BeegoHTTPRequest) Debug(isdebug bool) *BeegoHTTPRequest { } // Retries sets Retries times. -// default is 0 (never retry) -// -1 retry indefinitely (forever) -// Other numbers specify the exact retry amount +// default is 0 means no retried. +// -1 means retried forever. +// others means retried times. func (b *BeegoHTTPRequest) Retries(times int) *BeegoHTTPRequest { b.setting.Retries = times return b } -// RetryDelay sets the time to sleep between reconnection attempts -func (b *BeegoHTTPRequest) RetryDelay(delay time.Duration) *BeegoHTTPRequest { - b.setting.RetryDelay = delay - return b -} - -// DumpBody sets the DumbBody field +// DumpBody setting whether need to Dump the Body. func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest { b.setting.DumpBody = isdump return b } -// DumpRequest returns the DumpRequest +// DumpRequest return the DumpRequest func (b *BeegoHTTPRequest) DumpRequest() []byte { return b.dump } @@ -234,13 +220,13 @@ func (b *BeegoHTTPRequest) SetTimeout(connectTimeout, readWriteTimeout time.Dura return b } -// SetTLSClientConfig sets TLS connection configuration if visiting HTTPS url. +// SetTLSClientConfig sets tls connection configurations if visiting https url. func (b *BeegoHTTPRequest) SetTLSClientConfig(config *tls.Config) *BeegoHTTPRequest { b.setting.TLSClientConfig = config return b } -// Header adds header item string in request. +// Header add header item string in request. func (b *BeegoHTTPRequest) Header(key, value string) *BeegoHTTPRequest { b.req.Header.Set(key, value) return b @@ -252,7 +238,7 @@ func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest { return b } -// SetProtocolVersion sets the protocol version for incoming requests. +// SetProtocolVersion Set the protocol version for incoming requests. // Client requests always use HTTP/1.1. func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest { if len(vers) == 0 { @@ -269,19 +255,19 @@ func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest { return b } -// SetCookie adds a cookie to the request. +// SetCookie add cookie into request. func (b *BeegoHTTPRequest) SetCookie(cookie *http.Cookie) *BeegoHTTPRequest { b.req.Header.Add("Cookie", cookie.String()) return b } -// SetTransport sets the transport field +// SetTransport set the setting transport func (b *BeegoHTTPRequest) SetTransport(transport http.RoundTripper) *BeegoHTTPRequest { b.setting.Transport = transport return b } -// SetProxy sets the HTTP proxy +// SetProxy set the http proxy // example: // // func(req *http.Request) (*url.URL, error) { @@ -302,18 +288,6 @@ func (b *BeegoHTTPRequest) SetCheckRedirect(redirect func(req *http.Request, via return b } -// SetFilters will use the filter as the invocation filters -func (b *BeegoHTTPRequest) SetFilters(fcs ...FilterChain) *BeegoHTTPRequest { - b.setting.FilterChains = fcs - return b -} - -// AddFilters adds filter -func (b *BeegoHTTPRequest) AddFilters(fcs ...FilterChain) *BeegoHTTPRequest { - b.setting.FilterChains = append(b.setting.FilterChains, fcs...) - return b -} - // Param adds query param in to request. // params build query string as ?key1=value1&key2=value2... func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest { @@ -325,14 +299,14 @@ func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest { return b } -// PostFile adds a post file to the request +// PostFile add a post file to the request func (b *BeegoHTTPRequest) PostFile(formname, filename string) *BeegoHTTPRequest { b.files[formname] = filename return b } // Body adds request raw body. -// Supports string and []byte. +// it supports string and []byte. func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest { switch t := data.(type) { case string: @@ -347,7 +321,7 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest { return b } -// XMLBody adds the request raw body encoded in XML. +// XMLBody adds request raw body encoding by XML. func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) { if b.req.Body == nil && obj != nil { byts, err := xml.Marshal(obj) @@ -361,7 +335,7 @@ func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) { return b, nil } -// YAMLBody adds the request raw body encoded in YAML. +// YAMLBody adds request raw body encoding by YAML. func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error) { if b.req.Body == nil && obj != nil { byts, err := yaml.Marshal(obj) @@ -375,7 +349,7 @@ func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error) return b, nil } -// JSONBody adds the request raw body encoded in JSON. +// JSONBody adds request raw body encoding by JSON. func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) { if b.req.Body == nil && obj != nil { byts, err := json.Marshal(obj) @@ -416,7 +390,7 @@ func (b *BeegoHTTPRequest) buildURL(paramBody string) { if err != nil { log.Println("Httplib:", err) } - // iocopy + //iocopy _, err = io.Copy(fileWriter, fh) fh.Close() if err != nil { @@ -457,23 +431,8 @@ func (b *BeegoHTTPRequest) getResponse() (*http.Response, error) { return resp, nil } -// DoRequest executes client.Do +// DoRequest will do the client.Do func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) { - return b.DoRequestWithCtx(context.Background()) -} - -func (b *BeegoHTTPRequest) DoRequestWithCtx(ctx context.Context) (resp *http.Response, err error) { - - root := doRequestFilter - if len(b.setting.FilterChains) > 0 { - for i := len(b.setting.FilterChains) - 1; i >= 0; i-- { - root = b.setting.FilterChains[i](root) - } - } - return root(ctx, b) -} - -func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response, err error) { var paramBody string if len(b.params) > 0 { var buf bytes.Buffer @@ -553,19 +512,17 @@ func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response, // retries default value is 0, it will run once. // retries equal to -1, it will run forever until success // retries is setted, it will retries fixed times. - // Sleeps for a 400ms inbetween calls to reduce spam for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ { resp, err = client.Do(b.req) if err == nil { break } - time.Sleep(b.setting.RetryDelay) } return resp, err } // String returns the body string in response. -// Calls Response inner. +// it calls Response inner. func (b *BeegoHTTPRequest) String() (string, error) { data, err := b.Bytes() if err != nil { @@ -576,7 +533,7 @@ func (b *BeegoHTTPRequest) String() (string, error) { } // Bytes returns the body []byte in response. -// Calls Response inner. +// it calls Response inner. func (b *BeegoHTTPRequest) Bytes() ([]byte, error) { if b.body != nil { return b.body, nil @@ -602,7 +559,7 @@ func (b *BeegoHTTPRequest) Bytes() ([]byte, error) { } // ToFile saves the body data in response to one file. -// Calls Response inner. +// it calls Response inner. func (b *BeegoHTTPRequest) ToFile(filename string) error { resp, err := b.getResponse() if err != nil { @@ -625,7 +582,7 @@ func (b *BeegoHTTPRequest) ToFile(filename string) error { return err } -// Check if the file directory exists. If it doesn't then it's created +//Check that the file directory exists, there is no automatically created func pathExistAndMkdir(filename string) (err error) { filename = path.Dir(filename) _, err = os.Stat(filename) @@ -641,8 +598,8 @@ func pathExistAndMkdir(filename string) (err error) { return err } -// ToJSON returns the map that marshals from the body bytes as json in response. -// Calls Response inner. +// ToJSON returns the map that marshals from the body bytes as json in response . +// it calls Response inner. func (b *BeegoHTTPRequest) ToJSON(v interface{}) error { data, err := b.Bytes() if err != nil { @@ -652,7 +609,7 @@ func (b *BeegoHTTPRequest) ToJSON(v interface{}) error { } // ToXML returns the map that marshals from the body bytes as xml in response . -// Calls Response inner. +// it calls Response inner. func (b *BeegoHTTPRequest) ToXML(v interface{}) error { data, err := b.Bytes() if err != nil { @@ -662,7 +619,7 @@ func (b *BeegoHTTPRequest) ToXML(v interface{}) error { } // ToYAML returns the map that marshals from the body bytes as yaml in response . -// Calls Response inner. +// it calls Response inner. func (b *BeegoHTTPRequest) ToYAML(v interface{}) error { data, err := b.Bytes() if err != nil { @@ -671,7 +628,7 @@ func (b *BeegoHTTPRequest) ToYAML(v interface{}) error { return yaml.Unmarshal(data, v) } -// Response executes request client gets response manually. +// Response executes request client gets response mannually. func (b *BeegoHTTPRequest) Response() (*http.Response, error) { return b.getResponse() } diff --git a/adapter/httplib/httplib_test.go b/httplib/httplib_test.go similarity index 86% rename from adapter/httplib/httplib_test.go rename to httplib/httplib_test.go index e7605c87..dd2a4f1c 100644 --- a/adapter/httplib/httplib_test.go +++ b/httplib/httplib_test.go @@ -15,7 +15,6 @@ package httplib import ( - "errors" "io/ioutil" "net" "net/http" @@ -34,34 +33,6 @@ func TestResponse(t *testing.T) { t.Log(resp) } -func TestDoRequest(t *testing.T) { - req := Get("https://goolnk.com/33BD2j") - retryAmount := 1 - req.Retries(1) - req.RetryDelay(1400 * time.Millisecond) - retryDelay := 1400 * time.Millisecond - - req.SetCheckRedirect(func(redirectReq *http.Request, redirectVia []*http.Request) error { - return errors.New("Redirect triggered") - }) - - startTime := time.Now().UnixNano() / int64(time.Millisecond) - - _, err := req.Response() - if err == nil { - t.Fatal("Response should have yielded an error") - } - - endTime := time.Now().UnixNano() / int64(time.Millisecond) - elapsedTime := endTime - startTime - delayedTime := int64(retryAmount) * retryDelay.Milliseconds() - - if elapsedTime < delayedTime { - t.Errorf("Not enough retries. Took %dms. Delay was meant to take %dms", elapsedTime, delayedTime) - } - -} - func TestGet(t *testing.T) { req := Get("http://httpbin.org/get") b, err := req.Bytes() @@ -98,7 +69,7 @@ func TestSimplePost(t *testing.T) { } } -// func TestPostFile(t *testing.T) { +//func TestPostFile(t *testing.T) { // v := "smallfish" // req := Post("http://httpbin.org/post") // req.Debug(true) @@ -115,7 +86,7 @@ func TestSimplePost(t *testing.T) { // if n == -1 { // t.Fatal(v + " not found in post") // } -// } +//} func TestSimplePut(t *testing.T) { str, err := Put("http://httpbin.org/put").String() diff --git a/adapter/log.go b/log.go similarity index 88% rename from adapter/log.go rename to log.go index 9d07ec1a..cc4c0f81 100644 --- a/adapter/log.go +++ b/log.go @@ -12,27 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -package adapter +package beego import ( "strings" - "github.com/astaxie/beego/core/logs" - - webLog "github.com/astaxie/beego/core/logs" + "github.com/astaxie/beego/logs" ) // Log levels to control the logging output. // Deprecated: use github.com/astaxie/beego/logs instead. const ( - LevelEmergency = webLog.LevelEmergency - LevelAlert = webLog.LevelAlert - LevelCritical = webLog.LevelCritical - LevelError = webLog.LevelError - LevelWarning = webLog.LevelWarning - LevelNotice = webLog.LevelNotice - LevelInformational = webLog.LevelInformational - LevelDebug = webLog.LevelDebug + LevelEmergency = iota + LevelAlert + LevelCritical + LevelError + LevelWarning + LevelNotice + LevelInformational + LevelDebug ) // BeeLogger references the used application logger. diff --git a/core/logs/README.md b/logs/README.md similarity index 100% rename from core/logs/README.md rename to logs/README.md diff --git a/core/logs/access_log.go b/logs/accesslog.go similarity index 89% rename from core/logs/access_log.go rename to logs/accesslog.go index 10455fe9..3ff9e20f 100644 --- a/core/logs/access_log.go +++ b/logs/accesslog.go @@ -16,9 +16,9 @@ package logs import ( "bytes" + "strings" "encoding/json" "fmt" - "strings" "time" ) @@ -28,7 +28,7 @@ const ( jsonFormat = "JSON_FORMAT" ) -// AccessLogRecord is astruct for holding access log data. +// AccessLogRecord struct for holding access log data. type AccessLogRecord struct { RemoteAddr string `json:"remote_addr"` RequestTime time.Time `json:"request_time"` @@ -63,17 +63,7 @@ func disableEscapeHTML(i interface{}) { // AccessLog - Format and print access log. func AccessLog(r *AccessLogRecord, format string) { - msg := r.format(format) - lm := &LogMsg{ - Msg: strings.TrimSpace(msg), - When: time.Now(), - Level: levelLoggerImpl, - } - beeLogger.writeMsg(lm) -} - -func (r *AccessLogRecord) format(format string) string { - msg := "" + var msg string switch format { case apacheFormat: timeFormatted := r.RequestTime.Format("02/Jan/2006 03:04:05") @@ -89,5 +79,5 @@ func (r *AccessLogRecord) format(format string) string { msg = string(jsonData) } } - return msg + beeLogger.writeMsg(levelLoggerImpl, strings.TrimSpace(msg)) } diff --git a/core/logs/alils/alils.go b/logs/alils/alils.go similarity index 69% rename from core/logs/alils/alils.go rename to logs/alils/alils.go index 484d31e4..867ff4cb 100644 --- a/core/logs/alils/alils.go +++ b/logs/alils/alils.go @@ -2,20 +2,18 @@ package alils import ( "encoding/json" - "fmt" "strings" "sync" + "time" + "github.com/astaxie/beego/logs" "github.com/gogo/protobuf/proto" - "github.com/pkg/errors" - - "github.com/astaxie/beego/core/logs" ) const ( - // CacheSize sets the flush size + // CacheSize set the flush size CacheSize int = 64 - // Delimiter defines the topic delimiter + // Delimiter define the topic delimiter Delimiter string = "##" ) @@ -30,11 +28,10 @@ type Config struct { Source string `json:"source"` Level int `json:"level"` FlushWhen int `json:"flush_when"` - Formatter string `json:"formatter"` } // aliLSWriter implements LoggerInterface. -// Writes messages in keep-live tcp connection. +// it writes messages in keep-live tcp connection. type aliLSWriter struct { store *LogStore group []*LogGroup @@ -42,23 +39,19 @@ type aliLSWriter struct { groupMap map[string]*LogGroup lock *sync.Mutex Config - formatter logs.LogFormatter } -// NewAliLS creates a new Logger +// NewAliLS create a new Logger func NewAliLS() logs.Logger { alils := new(aliLSWriter) alils.Level = logs.LevelTrace - alils.formatter = alils return alils } -// Init parses config and initializes struct -func (c *aliLSWriter) Init(config string) error { - err := json.Unmarshal([]byte(config), c) - if err != nil { - return err - } +// Init parse config and init struct +func (c *aliLSWriter) Init(jsonConfig string) (err error) { + + json.Unmarshal([]byte(jsonConfig), c) if c.FlushWhen > CacheSize { c.FlushWhen = CacheSize @@ -71,13 +64,11 @@ func (c *aliLSWriter) Init(config string) error { AccessKeySecret: c.KeySecret, } - store, err := prj.GetLogStore(c.LogStore) + c.store, err = prj.GetLogStore(c.LogStore) if err != nil { return err } - c.store = store - // Create default Log Group c.group = append(c.group, &LogGroup{ Topic: proto.String(""), @@ -107,29 +98,14 @@ func (c *aliLSWriter) Init(config string) error { c.lock = &sync.Mutex{} - if len(c.Formatter) > 0 { - fmtr, ok := logs.GetFormatter(c.Formatter) - if !ok { - return errors.New(fmt.Sprintf("the formatter with name: %s not found", c.Formatter)) - } - c.formatter = fmtr - } - return nil } -func (c *aliLSWriter) Format(lm *logs.LogMsg) string { - return lm.OldStyleFormat() -} +// WriteMsg write message in connection. +// if connection is down, try to re-connect. +func (c *aliLSWriter) WriteMsg(when time.Time, msg string, level int) (err error) { -func (c *aliLSWriter) SetFormatter(f logs.LogFormatter) { - c.formatter = f -} - -// WriteMsg writes a message in connection. -// If connection is down, try to re-connect. -func (c *aliLSWriter) WriteMsg(lm *logs.LogMsg) error { - if lm.Level > c.Level { + if level > c.Level { return nil } @@ -139,30 +115,31 @@ func (c *aliLSWriter) WriteMsg(lm *logs.LogMsg) error { if c.withMap { // Topic,LogGroup - strs := strings.SplitN(lm.Msg, Delimiter, 2) + strs := strings.SplitN(msg, Delimiter, 2) if len(strs) == 2 { pos := strings.LastIndex(strs[0], " ") topic = strs[0][pos+1 : len(strs[0])] + content = strs[0][0:pos] + strs[1] lg = c.groupMap[topic] } // send to empty Topic if lg == nil { + content = msg lg = c.group[0] } } else { + content = msg lg = c.group[0] } - content = c.formatter.Format(lm) - c1 := &LogContent{ Key: proto.String("msg"), Value: proto.String(content), } l := &Log{ - Time: proto.Uint32(uint32(lm.When.Unix())), + Time: proto.Uint32(uint32(when.Unix())), Contents: []*LogContent{ c1, }, @@ -175,6 +152,7 @@ func (c *aliLSWriter) WriteMsg(lm *logs.LogMsg) error { if len(lg.Logs) >= c.FlushWhen { c.flush(lg) } + return nil } diff --git a/core/logs/alils/config.go b/logs/alils/config.go similarity index 61% rename from core/logs/alils/config.go rename to logs/alils/config.go index d0b67c24..e8c24448 100755 --- a/core/logs/alils/config.go +++ b/logs/alils/config.go @@ -4,10 +4,10 @@ const ( version = "0.5.0" // SDK version signatureMethod = "hmac-sha1" // Signature method - // OffsetNewest is the log head offset, i.e. the offset that will be + // OffsetNewest stands for the log head offset, i.e. the offset that will be // assigned to the next message that will be produced to the shard. OffsetNewest = "end" - // OffsetOldest is the the oldest offset available on the logstore for a + // OffsetOldest stands for the oldest offset available on the logstore for a // shard. OffsetOldest = "begin" ) diff --git a/core/logs/alils/log.pb.go b/logs/alils/log.pb.go similarity index 95% rename from core/logs/alils/log.pb.go rename to logs/alils/log.pb.go index b18fb9b7..601b0d78 100755 --- a/core/logs/alils/log.pb.go +++ b/logs/alils/log.pb.go @@ -31,13 +31,13 @@ type Log struct { // Reset the Log func (m *Log) Reset() { *m = Log{} } -// String returns the Compact Log +// String return the Compact Log func (m *Log) String() string { return proto.CompactTextString(m) } // ProtoMessage not implemented func (*Log) ProtoMessage() {} -// GetTime returns the Log's Time +// GetTime return the Log's Time func (m *Log) GetTime() uint32 { if m != nil && m.Time != nil { return *m.Time @@ -45,7 +45,7 @@ func (m *Log) GetTime() uint32 { return 0 } -// GetContents returns the Log's Contents +// GetContents return the Log's Contents func (m *Log) GetContents() []*LogContent { if m != nil { return m.Contents @@ -53,7 +53,7 @@ func (m *Log) GetContents() []*LogContent { return nil } -// LogContent defines the Log content struct +// LogContent define the Log content struct type LogContent struct { Key *string `protobuf:"bytes,1,req,name=Key" json:"Key,omitempty"` Value *string `protobuf:"bytes,2,req,name=Value" json:"Value,omitempty"` @@ -63,13 +63,13 @@ type LogContent struct { // Reset LogContent func (m *LogContent) Reset() { *m = LogContent{} } -// String returns the compact text +// String return the compact text func (m *LogContent) String() string { return proto.CompactTextString(m) } // ProtoMessage not implemented func (*LogContent) ProtoMessage() {} -// GetKey returns the key +// GetKey return the Key func (m *LogContent) GetKey() string { if m != nil && m.Key != nil { return *m.Key @@ -77,7 +77,7 @@ func (m *LogContent) GetKey() string { return "" } -// GetValue returns the value +// GetValue return the Value func (m *LogContent) GetValue() string { if m != nil && m.Value != nil { return *m.Value @@ -85,7 +85,7 @@ func (m *LogContent) GetValue() string { return "" } -// LogGroup defines the logs struct +// LogGroup define the logs struct type LogGroup struct { Logs []*Log `protobuf:"bytes,1,rep,name=Logs" json:"Logs,omitempty"` Reserved *string `protobuf:"bytes,2,opt,name=Reserved" json:"Reserved,omitempty"` @@ -97,13 +97,13 @@ type LogGroup struct { // Reset LogGroup func (m *LogGroup) Reset() { *m = LogGroup{} } -// String returns the compact text +// String return the compact text func (m *LogGroup) String() string { return proto.CompactTextString(m) } // ProtoMessage not implemented func (*LogGroup) ProtoMessage() {} -// GetLogs returns the loggroup logs +// GetLogs return the loggroup logs func (m *LogGroup) GetLogs() []*Log { if m != nil { return m.Logs @@ -111,8 +111,7 @@ func (m *LogGroup) GetLogs() []*Log { return nil } -// GetReserved returns Reserved. An empty string is returned -// if an error occurs +// GetReserved return Reserved func (m *LogGroup) GetReserved() string { if m != nil && m.Reserved != nil { return *m.Reserved @@ -120,8 +119,7 @@ func (m *LogGroup) GetReserved() string { return "" } -// GetTopic returns Topic. An empty string is returned -// if an error occurs +// GetTopic return Topic func (m *LogGroup) GetTopic() string { if m != nil && m.Topic != nil { return *m.Topic @@ -129,8 +127,7 @@ func (m *LogGroup) GetTopic() string { return "" } -// GetSource returns source. An empty string is returned -// if an error occurs +// GetSource return Source func (m *LogGroup) GetSource() string { if m != nil && m.Source != nil { return *m.Source @@ -138,7 +135,7 @@ func (m *LogGroup) GetSource() string { return "" } -// LogGroupList defines the LogGroups +// LogGroupList define the LogGroups type LogGroupList struct { LogGroups []*LogGroup `protobuf:"bytes,1,rep,name=logGroups" json:"logGroups,omitempty"` XXXUnrecognized []byte `json:"-"` @@ -147,13 +144,13 @@ type LogGroupList struct { // Reset LogGroupList func (m *LogGroupList) Reset() { *m = LogGroupList{} } -// String returns compact text +// String return compact text func (m *LogGroupList) String() string { return proto.CompactTextString(m) } // ProtoMessage not implemented func (*LogGroupList) ProtoMessage() {} -// GetLogGroups returns the LogGroups +// GetLogGroups return the LogGroups func (m *LogGroupList) GetLogGroups() []*LogGroup { if m != nil { return m.LogGroups @@ -161,7 +158,7 @@ func (m *LogGroupList) GetLogGroups() []*LogGroup { return nil } -// Marshal marshals the logs to byte slice +// Marshal the logs to byte slice func (m *Log) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -356,7 +353,7 @@ func encodeVarintLog(data []byte, offset int, v uint64) int { return offset + 1 } -// Size returns the log's size +// Size return the log's size func (m *Log) Size() (n int) { var l int _ = l @@ -375,7 +372,7 @@ func (m *Log) Size() (n int) { return n } -// Size returns LogContent size based on Key and Value +// Size return LogContent size based on Key and Value func (m *LogContent) Size() (n int) { var l int _ = l @@ -393,7 +390,7 @@ func (m *LogContent) Size() (n int) { return n } -// Size returns LogGroup size based on Logs +// Size return LogGroup size based on Logs func (m *LogGroup) Size() (n int) { var l int _ = l @@ -421,7 +418,7 @@ func (m *LogGroup) Size() (n int) { return n } -// Size returns LogGroupList size +// Size return LogGroupList size func (m *LogGroupList) Size() (n int) { var l int _ = l @@ -451,7 +448,7 @@ func sozLog(x uint64) (n int) { return sovLog((x << 1) ^ (x >> 63)) } -// Unmarshal unmarshals data to log +// Unmarshal data to log func (m *Log) Unmarshal(data []byte) error { var hasFields [1]uint64 l := len(data) @@ -560,7 +557,7 @@ func (m *Log) Unmarshal(data []byte) error { return nil } -// Unmarshal unmarshals data to LogContent +// Unmarshal data to LogContent func (m *LogContent) Unmarshal(data []byte) error { var hasFields [1]uint64 l := len(data) @@ -682,7 +679,7 @@ func (m *LogContent) Unmarshal(data []byte) error { return nil } -// Unmarshal unmarshals data to LogGroup +// Unmarshal data to LogGroup func (m *LogGroup) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -856,7 +853,7 @@ func (m *LogGroup) Unmarshal(data []byte) error { return nil } -// Unmarshal unmarshals data to LogGroupList +// Unmarshal data to LogGroupList func (m *LogGroupList) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 diff --git a/core/logs/alils/log_config.go b/logs/alils/log_config.go similarity index 91% rename from core/logs/alils/log_config.go rename to logs/alils/log_config.go index 7daeb864..e8564efb 100755 --- a/core/logs/alils/log_config.go +++ b/logs/alils/log_config.go @@ -1,6 +1,6 @@ package alils -// InputDetail defines log detail +// InputDetail define log detail type InputDetail struct { LogType string `json:"logType"` LogPath string `json:"logPath"` @@ -15,13 +15,13 @@ type InputDetail struct { TopicFormat string `json:"topicFormat"` } -// OutputDetail defines the output detail +// OutputDetail define the output detail type OutputDetail struct { Endpoint string `json:"endpoint"` LogStoreName string `json:"logstoreName"` } -// LogConfig defines Log Config +// LogConfig define Log Config type LogConfig struct { Name string `json:"configName"` InputType string `json:"inputType"` diff --git a/core/logs/alils/log_project.go b/logs/alils/log_project.go similarity index 99% rename from core/logs/alils/log_project.go rename to logs/alils/log_project.go index 7ede3fef..59db8cbf 100755 --- a/core/logs/alils/log_project.go +++ b/logs/alils/log_project.go @@ -20,7 +20,7 @@ type errorMessage struct { Message string `json:"errorMessage"` } -// LogProject defines the Ali Project detail +// LogProject Define the Ali Project detail type LogProject struct { Name string // Project name Endpoint string // IP or hostname of SLS endpoint diff --git a/core/logs/alils/log_store.go b/logs/alils/log_store.go similarity index 98% rename from core/logs/alils/log_store.go rename to logs/alils/log_store.go index d5ff25e2..fa502736 100755 --- a/core/logs/alils/log_store.go +++ b/logs/alils/log_store.go @@ -12,7 +12,7 @@ import ( "github.com/gogo/protobuf/proto" ) -// LogStore stores the logs +// LogStore Store the logs type LogStore struct { Name string `json:"logstoreName"` TTL int @@ -24,7 +24,7 @@ type LogStore struct { project *LogProject } -// Shard defines the Log Shard +// Shard define the Log Shard type Shard struct { ShardID int `json:"shardID"` } @@ -71,7 +71,7 @@ func (s *LogStore) ListShards() (shardIDs []int, err error) { return } -// PutLogs puts logs into logstore. +// PutLogs put logs into logstore. // The callers should transform user logs into LogGroup. func (s *LogStore) PutLogs(lg *LogGroup) (err error) { body, err := proto.Marshal(lg) diff --git a/core/logs/alils/machine_group.go b/logs/alils/machine_group.go similarity index 88% rename from core/logs/alils/machine_group.go rename to logs/alils/machine_group.go index 101faeb4..b6c69a14 100755 --- a/core/logs/alils/machine_group.go +++ b/logs/alils/machine_group.go @@ -8,13 +8,13 @@ import ( "net/http/httputil" ) -// MachineGroupAttribute defines the Attribute +// MachineGroupAttribute define the Attribute type MachineGroupAttribute struct { ExternalName string `json:"externalName"` TopicName string `json:"groupTopic"` } -// MachineGroup defines the machine Group +// MachineGroup define the machine Group type MachineGroup struct { Name string `json:"groupName"` Type string `json:"groupType"` @@ -29,20 +29,20 @@ type MachineGroup struct { project *LogProject } -// Machine defines the Machine +// Machine define the Machine type Machine struct { IP string UniqueID string `json:"machine-uniqueid"` UserdefinedID string `json:"userdefined-id"` } -// MachineList defines the Machine List +// MachineList define the Machine List type MachineList struct { Total int Machines []*Machine } -// ListMachines returns the machine list of this machine group. +// ListMachines returns machine list of this machine group. func (m *MachineGroup) ListMachines() (ms []*Machine, total int, err error) { h := map[string]string{ "x-sls-bodyrawsize": "0", diff --git a/core/logs/alils/request.go b/logs/alils/request.go similarity index 100% rename from core/logs/alils/request.go rename to logs/alils/request.go diff --git a/core/logs/alils/signature.go b/logs/alils/signature.go similarity index 100% rename from core/logs/alils/signature.go rename to logs/alils/signature.go diff --git a/core/logs/conn.go b/logs/conn.go similarity index 65% rename from core/logs/conn.go rename to logs/conn.go index 1fd71be7..afe0cbb7 100644 --- a/core/logs/conn.go +++ b/logs/conn.go @@ -16,20 +16,16 @@ package logs import ( "encoding/json" - "fmt" "io" "net" - - "github.com/pkg/errors" + "time" ) // connWriter implements LoggerInterface. -// Writes messages in keep-live tcp connection. +// it writes messages in keep-live tcp connection. type connWriter struct { lg *logWriter innerWriter io.WriteCloser - formatter LogFormatter - Formatter string `json:"formatter"` ReconnectOnMsg bool `json:"reconnectOnMsg"` Reconnect bool `json:"reconnect"` Net string `json:"net"` @@ -37,40 +33,23 @@ type connWriter struct { Level int `json:"level"` } -// NewConn creates new ConnWrite returning as LoggerInterface. +// NewConn create new ConnWrite returning as LoggerInterface. func NewConn() Logger { conn := new(connWriter) conn.Level = LevelTrace - conn.formatter = conn return conn } -func (c *connWriter) Format(lm *LogMsg) string { - return lm.OldStyleFormat() +// Init init connection writer with json config. +// json config only need key "level". +func (c *connWriter) Init(jsonConfig string) error { + return json.Unmarshal([]byte(jsonConfig), c) } -// Init initializes a connection writer with json config. -// json config only needs they "level" key -func (c *connWriter) Init(config string) error { - res := json.Unmarshal([]byte(config), c) - if res == nil && len(c.Formatter) > 0 { - fmtr, ok := GetFormatter(c.Formatter) - if !ok { - return errors.New(fmt.Sprintf("the formatter with name: %s not found", c.Formatter)) - } - c.formatter = fmtr - } - return res -} - -func (c *connWriter) SetFormatter(f LogFormatter) { - c.formatter = f -} - -// WriteMsg writes message in connection. -// If connection is down, try to re-connect. -func (c *connWriter) WriteMsg(lm *LogMsg) error { - if lm.Level > c.Level { +// WriteMsg write message in connection. +// if connection is down, try to re-connect. +func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error { + if level > c.Level { return nil } if c.needToConnectOnMsg() { @@ -84,12 +63,7 @@ func (c *connWriter) WriteMsg(lm *LogMsg) error { defer c.innerWriter.Close() } - msg := c.formatter.Format(lm) - - _, err := c.lg.writeln(msg) - if err != nil { - return err - } + c.lg.writeln(when, msg) return nil } @@ -127,6 +101,7 @@ func (c *connWriter) connect() error { func (c *connWriter) needToConnectOnMsg() bool { if c.Reconnect { + c.Reconnect = false return true } diff --git a/adapter/utils/caller.go b/logs/conn_test.go similarity index 78% rename from adapter/utils/caller.go rename to logs/conn_test.go index 419f11d6..747fb890 100644 --- a/adapter/utils/caller.go +++ b/logs/conn_test.go @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package utils +package logs import ( - "github.com/astaxie/beego/core/utils" + "testing" ) -// GetFuncName get function name -func GetFuncName(i interface{}) string { - return utils.GetFuncName(i) +func TestConn(t *testing.T) { + log := NewLogger(1000) + log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`) + log.Informational("informational") } diff --git a/core/logs/console.go b/logs/console.go similarity index 57% rename from core/logs/console.go rename to logs/console.go index 66e2c7ea..3dcaee1d 100644 --- a/core/logs/console.go +++ b/logs/console.go @@ -16,18 +16,17 @@ package logs import ( "encoding/json" - "fmt" "os" "strings" + "time" - "github.com/pkg/errors" "github.com/shiena/ansicolor" ) // brush is a color join function type brush func(string) string -// newBrush returns a fix color Brush +// newBrush return a fix color Brush func newBrush(color string) brush { pre := "\033[" reset := "\033[0m" @@ -49,68 +48,39 @@ var colors = []brush{ // consoleWriter implements LoggerInterface and writes messages to terminal. type consoleWriter struct { - lg *logWriter - formatter LogFormatter - Formatter string `json:"formatter"` - Level int `json:"level"` - Colorful bool `json:"color"` // this filed is useful only when system's terminal supports color + lg *logWriter + Level int `json:"level"` + Colorful bool `json:"color"` //this filed is useful only when system's terminal supports color } -func (c *consoleWriter) Format(lm *LogMsg) string { - msg := lm.OldStyleFormat() - if c.Colorful { - msg = strings.Replace(msg, levelPrefix[lm.Level], colors[lm.Level](levelPrefix[lm.Level]), 1) - } - h, _, _ := formatTimeHeader(lm.When) - bytes := append(append(h, msg...), '\n') - return string(bytes) -} - -func (c *consoleWriter) SetFormatter(f LogFormatter) { - c.formatter = f -} - -// NewConsole creates ConsoleWriter returning as LoggerInterface. +// NewConsole create ConsoleWriter returning as LoggerInterface. func NewConsole() Logger { - return newConsole() -} - -func newConsole() *consoleWriter { cw := &consoleWriter{ lg: newLogWriter(ansicolor.NewAnsiColorWriter(os.Stdout)), Level: LevelDebug, Colorful: true, } - cw.formatter = cw return cw } -// Init initianlizes the console logger. -// jsonConfig must be in the format '{"level":LevelTrace}' -func (c *consoleWriter) Init(config string) error { - - if len(config) == 0 { +// Init init console logger. +// jsonConfig like '{"level":LevelTrace}'. +func (c *consoleWriter) Init(jsonConfig string) error { + if len(jsonConfig) == 0 { return nil } - - res := json.Unmarshal([]byte(config), c) - if res == nil && len(c.Formatter) > 0 { - fmtr, ok := GetFormatter(c.Formatter) - if !ok { - return errors.New(fmt.Sprintf("the formatter with name: %s not found", c.Formatter)) - } - c.formatter = fmtr - } - return res + return json.Unmarshal([]byte(jsonConfig), c) } -// WriteMsg writes message in console. -func (c *consoleWriter) WriteMsg(lm *LogMsg) error { - if lm.Level > c.Level { +// WriteMsg write message in console. +func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error { + if level > c.Level { return nil } - msg := c.formatter.Format(lm) - c.lg.writeln(msg) + if c.Colorful { + msg = strings.Replace(msg, levelPrefix[level], colors[level](levelPrefix[level]), 1) + } + c.lg.writeln(when, msg) return nil } diff --git a/core/logs/console_test.go b/logs/console_test.go similarity index 78% rename from core/logs/console_test.go rename to logs/console_test.go index e345ba40..4bc45f57 100644 --- a/core/logs/console_test.go +++ b/logs/console_test.go @@ -17,8 +17,6 @@ package logs import ( "testing" "time" - - "github.com/stretchr/testify/assert" ) // Try each log level in decreasing order of priority. @@ -64,19 +62,3 @@ func TestConsoleAsync(t *testing.T) { time.Sleep(1 * time.Millisecond) } } - -func TestFormat(t *testing.T) { - log := newConsole() - lm := &LogMsg{ - Level: LevelDebug, - Msg: "Hello, world", - When: time.Date(2020, 9, 19, 20, 12, 37, 9, time.UTC), - FilePath: "/user/home/main.go", - LineNumber: 13, - Prefix: "Cus", - } - res := log.Format(lm) - assert.Equal(t, "2020/09/19 20:12:37.000 \x1b[1;44m[D]\x1b[0m Cus Hello, world\n", res) - err := log.WriteMsg(lm) - assert.Nil(t, err) -} diff --git a/core/logs/es/es.go b/logs/es/es.go similarity index 56% rename from core/logs/es/es.go rename to logs/es/es.go index 6175f253..2b7b1710 100644 --- a/core/logs/es/es.go +++ b/logs/es/es.go @@ -12,14 +12,13 @@ import ( "github.com/elastic/go-elasticsearch/v6" "github.com/elastic/go-elasticsearch/v6/esapi" - "github.com/astaxie/beego/core/logs" + "github.com/astaxie/beego/logs" ) -// NewES returns a LoggerInterface +// NewES return a LoggerInterface func NewES() logs.Logger { cw := &esLogger{ - Level: logs.LevelDebug, - indexNaming: indexNaming, + Level: logs.LevelDebug, } return cw } @@ -32,36 +31,13 @@ func NewES() logs.Logger { // import _ "github.com/astaxie/beego/logs/es" type esLogger struct { *elasticsearch.Client - DSN string `json:"dsn"` - Level int `json:"level"` - formatter logs.LogFormatter - Formatter string `json:"formatter"` - - indexNaming IndexNaming -} - -func (el *esLogger) Format(lm *logs.LogMsg) string { - - msg := lm.OldStyleFormat() - idx := LogDocument{ - Timestamp: lm.When.Format(time.RFC3339), - Msg: msg, - } - body, err := json.Marshal(idx) - if err != nil { - return msg - } - return string(body) -} - -func (el *esLogger) SetFormatter(f logs.LogFormatter) { - el.formatter = f + DSN string `json:"dsn"` + Level int `json:"level"` } // {"dsn":"http://localhost:9200/","level":1} -func (el *esLogger) Init(config string) error { - - err := json.Unmarshal([]byte(config), el) +func (el *esLogger) Init(jsonconfig string) error { + err := json.Unmarshal([]byte(jsonconfig), el) if err != nil { return err } @@ -80,30 +56,30 @@ func (el *esLogger) Init(config string) error { } el.Client = conn } - if len(el.Formatter) > 0 { - fmtr, ok := logs.GetFormatter(el.Formatter) - if !ok { - return errors.New(fmt.Sprintf("the formatter with name: %s not found", el.Formatter)) - } - el.formatter = fmtr - } return nil } -// WriteMsg writes the msg and level into es -func (el *esLogger) WriteMsg(lm *logs.LogMsg) error { - if lm.Level > el.Level { +// WriteMsg will write the msg and level into es +func (el *esLogger) WriteMsg(when time.Time, msg string, level int) error { + if level > el.Level { return nil } - msg := el.formatter.Format(lm) - - req := esapi.IndexRequest{ - Index: indexNaming.IndexName(lm), - DocumentType: "logs", - Body: strings.NewReader(msg), + idx := LogDocument{ + Timestamp: when.Format(time.RFC3339), + Msg: msg, } - _, err := req.Do(context.Background(), el.Client) + + body, err := json.Marshal(idx) + if err != nil { + return err + } + req := esapi.IndexRequest{ + Index: fmt.Sprintf("%04d.%02d.%02d", when.Year(), when.Month(), when.Day()), + DocumentType: "logs", + Body: strings.NewReader(string(body)), + } + _, err = req.Do(context.Background(), el.Client) return err } diff --git a/core/logs/file.go b/logs/file.go similarity index 80% rename from core/logs/file.go rename to logs/file.go index b01be357..222db989 100644 --- a/core/logs/file.go +++ b/logs/file.go @@ -30,7 +30,7 @@ import ( ) // fileLogWriter implements LoggerInterface. -// Writes messages by lines limit, file size limit, or time frequency. +// It writes messages by lines limit, file size limit, or time frequency. type fileLogWriter struct { sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize // The opened file @@ -69,12 +69,9 @@ type fileLogWriter struct { RotatePerm string `json:"rotateperm"` fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix - - formatter LogFormatter - Formatter string `json:"formatter"` } -// newFileWriter creates a FileLogWriter returning as LoggerInterface. +// newFileWriter create a FileLogWriter returning as LoggerInterface. func newFileWriter() Logger { w := &fileLogWriter{ Daily: true, @@ -89,21 +86,9 @@ func newFileWriter() Logger { MaxFiles: 999, MaxSize: 1 << 28, } - w.formatter = w return w } -func (w *fileLogWriter) Format(lm *LogMsg) string { - msg := lm.OldStyleFormat() - hd, _, _ := formatTimeHeader(lm.When) - msg = fmt.Sprintf("%s %s\n", string(hd), msg) - return msg -} - -func (w *fileLogWriter) SetFormatter(f LogFormatter) { - w.formatter = f -} - // Init file logger with json config. // jsonConfig like: // { @@ -115,9 +100,8 @@ func (w *fileLogWriter) SetFormatter(f LogFormatter) { // "rotate":true, // "perm":"0600" // } -func (w *fileLogWriter) Init(config string) error { - - err := json.Unmarshal([]byte(config), w) +func (w *fileLogWriter) Init(jsonConfig string) error { + err := json.Unmarshal([]byte(jsonConfig), w) if err != nil { return err } @@ -129,14 +113,6 @@ func (w *fileLogWriter) Init(config string) error { if w.suffix == "" { w.suffix = ".log" } - - if len(w.Formatter) > 0 { - fmtr, ok := GetFormatter(w.Formatter) - if !ok { - return errors.New(fmt.Sprintf("the formatter with name: %s not found", w.Formatter)) - } - w.formatter = fmtr - } err = w.startLogger() return err } @@ -154,44 +130,42 @@ func (w *fileLogWriter) startLogger() error { return w.initFd() } -func (w *fileLogWriter) needRotateDaily(day int) bool { +func (w *fileLogWriter) needRotateDaily(size int, day int) bool { return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) || (w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) || (w.Daily && day != w.dailyOpenDate) } -func (w *fileLogWriter) needRotateHourly(hour int) bool { +func (w *fileLogWriter) needRotateHourly(size int, hour int) bool { return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) || (w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) || (w.Hourly && hour != w.hourlyOpenDate) } -// WriteMsg writes logger message into file. -func (w *fileLogWriter) WriteMsg(lm *LogMsg) error { - if lm.Level > w.Level { +// WriteMsg write logger message into file. +func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error { + if level > w.Level { return nil } - - _, d, h := formatTimeHeader(lm.When) - - msg := w.formatter.Format(lm) + hd, d, h := formatTimeHeader(when) + msg = string(hd) + msg + "\n" if w.Rotate { w.RLock() - if w.needRotateHourly(h) { + if w.needRotateHourly(len(msg), h) { w.RUnlock() w.Lock() - if w.needRotateHourly(h) { - if err := w.doRotate(lm.When); err != nil { + if w.needRotateHourly(len(msg), h) { + if err := w.doRotate(when); err != nil { fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) } } w.Unlock() - } else if w.needRotateDaily(d) { + } else if w.needRotateDaily(len(msg), d) { w.RUnlock() w.Lock() - if w.needRotateDaily(d) { - if err := w.doRotate(lm.When); err != nil { + if w.needRotateDaily(len(msg), d) { + if err := w.doRotate(when); err != nil { fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) } } @@ -262,7 +236,7 @@ func (w *fileLogWriter) dailyRotate(openTime time.Time) { tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100)) <-tm.C w.Lock() - if w.needRotateDaily(time.Now().Day()) { + if w.needRotateDaily(0, time.Now().Day()) { if err := w.doRotate(time.Now()); err != nil { fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) } @@ -277,7 +251,7 @@ func (w *fileLogWriter) hourlyRotate(openTime time.Time) { tm := time.NewTimer(time.Duration(nextHour.UnixNano() - openTime.UnixNano() + 100)) <-tm.C w.Lock() - if w.needRotateHourly(time.Now().Hour()) { + if w.needRotateHourly(0, time.Now().Hour()) { if err := w.doRotate(time.Now()); err != nil { fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) } @@ -312,7 +286,7 @@ func (w *fileLogWriter) lines() (int, error) { return count, nil } -// DoRotate means it needs to write logs into a new file. +// DoRotate means it need to write file in new file. // new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size) func (w *fileLogWriter) doRotate(logTime time.Time) error { // file exists @@ -328,7 +302,7 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error { _, err = os.Lstat(w.Filename) if err != nil { - // even if the file is not exist or other ,we should RESTART the logger + //even if the file is not exist or other ,we should RESTART the logger goto RESTART_LOGGER } @@ -399,21 +373,21 @@ func (w *fileLogWriter) deleteOldLog() { if info == nil { return } - if w.Hourly { - if !info.IsDir() && info.ModTime().Add(1*time.Hour*time.Duration(w.MaxHours)).Before(time.Now()) { - if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) && - strings.HasSuffix(filepath.Base(path), w.suffix) { - os.Remove(path) - } - } - } else if w.Daily { - if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) { - if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) && - strings.HasSuffix(filepath.Base(path), w.suffix) { - os.Remove(path) - } - } - } + if w.Hourly { + if !info.IsDir() && info.ModTime().Add(1 * time.Hour * time.Duration(w.MaxHours)).Before(time.Now()) { + if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) && + strings.HasSuffix(filepath.Base(path), w.suffix) { + os.Remove(path) + } + } + } else if w.Daily { + if !info.IsDir() && info.ModTime().Add(24 * time.Hour * time.Duration(w.MaxDays)).Before(time.Now()) { + if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) && + strings.HasSuffix(filepath.Base(path), w.suffix) { + os.Remove(path) + } + } + } return }) } @@ -423,7 +397,7 @@ func (w *fileLogWriter) Destroy() { w.fileWriter.Close() } -// Flush flushes file logger. +// Flush flush file logger. // there are no buffering messages in file logger in memory. // flush file means sync file from disk. func (w *fileLogWriter) Flush() { diff --git a/core/logs/file_test.go b/logs/file_test.go similarity index 89% rename from core/logs/file_test.go rename to logs/file_test.go index 6612ebe6..e7c2ca9a 100644 --- a/core/logs/file_test.go +++ b/logs/file_test.go @@ -22,8 +22,6 @@ import ( "strconv" "testing" "time" - - "github.com/stretchr/testify/assert" ) func TestFilePerm(t *testing.T) { @@ -188,7 +186,7 @@ func TestFileDailyRotate_06(t *testing.T) { //test file mode func TestFileHourlyRotate_01(t *testing.T) { log := NewLogger(10000) - log.SetLogger("file", `{"filename":"test3.log","hourly":true,"maxlines":4}`) + log.SetLogger("file", `{"filename":"test3.log","hourly":true,"maxlines":4}`) log.Debug("debug") log.Info("info") log.Notice("notice") @@ -239,7 +237,7 @@ func TestFileHourlyRotate_05(t *testing.T) { func TestFileHourlyRotate_06(t *testing.T) { //test file mode log := NewLogger(10000) - log.SetLogger("file", `{"filename":"test3.log", "hourly":true, "maxlines":4}`) + log.SetLogger("file", `{"filename":"test3.log", "hourly":true, "maxlines":4}`) log.Debug("debug") log.Info("info") log.Notice("notice") @@ -270,26 +268,20 @@ func testFileRotate(t *testing.T, fn1, fn2 string, daily, hourly bool) { Perm: "0660", RotatePerm: "0440", } - fw.formatter = fw - if daily { - fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1)) - fw.dailyOpenTime = time.Now().Add(-24 * time.Hour) - fw.dailyOpenDate = fw.dailyOpenTime.Day() - } + if daily { + fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1)) + fw.dailyOpenTime = time.Now().Add(-24 * time.Hour) + fw.dailyOpenDate = fw.dailyOpenTime.Day() + } - if hourly { - fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1)) - fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour) - fw.hourlyOpenDate = fw.hourlyOpenTime.Day() - } - lm := &LogMsg{ - Msg: "Test message", - Level: LevelDebug, - When: time.Now(), - } + if hourly { + fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1)) + fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour) + fw.hourlyOpenDate = fw.hourlyOpenTime.Day() + } - fw.WriteMsg(lm) + fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug) for _, file := range []string{fn1, fn2} { _, err := os.Stat(file) @@ -311,8 +303,6 @@ func testFileDailyRotate(t *testing.T, fn1, fn2 string) { Perm: "0660", RotatePerm: "0440", } - fw.formatter = fw - fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1)) fw.dailyOpenTime = time.Now().Add(-24 * time.Hour) fw.dailyOpenDate = fw.dailyOpenTime.Day() @@ -338,15 +328,13 @@ func testFileDailyRotate(t *testing.T, fn1, fn2 string) { func testFileHourlyRotate(t *testing.T, fn1, fn2 string) { fw := &fileLogWriter{ - Hourly: true, - MaxHours: 168, + Hourly: true, + MaxHours: 168, Rotate: true, Level: LevelTrace, Perm: "0660", RotatePerm: "0440", } - - fw.formatter = fw fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1)) fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour) fw.hourlyOpenDate = fw.hourlyOpenTime.Hour() @@ -430,18 +418,3 @@ func BenchmarkFileOnGoroutine(b *testing.B) { } os.Remove("test4.log") } - -func TestFileLogWriter_Format(t *testing.T) { - lg := &LogMsg{ - Level: LevelDebug, - Msg: "Hello, world", - When: time.Date(2020, 9, 19, 20, 12, 37, 9, time.UTC), - FilePath: "/user/home/main.go", - LineNumber: 13, - Prefix: "Cus", - } - - fw := newFileWriter().(*fileLogWriter) - res := fw.Format(lg) - assert.Equal(t, "2020/09/19 20:12:37.000 [D] Cus Hello, world\n", res) -} diff --git a/core/logs/jianliao.go b/logs/jianliao.go similarity index 56% rename from core/logs/jianliao.go rename to logs/jianliao.go index c82a0957..88ba0f9a 100644 --- a/core/logs/jianliao.go +++ b/logs/jianliao.go @@ -5,8 +5,7 @@ import ( "fmt" "net/http" "net/url" - - "github.com/pkg/errors" + "time" ) // JLWriter implements beego LoggerInterface and is used to send jiaoliao webhook @@ -17,50 +16,26 @@ type JLWriter struct { RedirectURL string `json:"redirecturl,omitempty"` ImageURL string `json:"imageurl,omitempty"` Level int `json:"level"` - - formatter LogFormatter - Formatter string `json:"formatter"` } -// newJLWriter creates jiaoliao writer. +// newJLWriter create jiaoliao writer. func newJLWriter() Logger { - res := &JLWriter{Level: LevelTrace} - res.formatter = res - return res + return &JLWriter{Level: LevelTrace} } // Init JLWriter with json config string -func (s *JLWriter) Init(config string) error { - - res := json.Unmarshal([]byte(config), s) - if res == nil && len(s.Formatter) > 0 { - fmtr, ok := GetFormatter(s.Formatter) - if !ok { - return errors.New(fmt.Sprintf("the formatter with name: %s not found", s.Formatter)) - } - s.formatter = fmtr - } - return res +func (s *JLWriter) Init(jsonconfig string) error { + return json.Unmarshal([]byte(jsonconfig), s) } -func (s *JLWriter) Format(lm *LogMsg) string { - msg := lm.OldStyleFormat() - msg = fmt.Sprintf("%s %s", lm.When.Format("2006-01-02 15:04:05"), msg) - return msg -} - -func (s *JLWriter) SetFormatter(f LogFormatter) { - s.formatter = f -} - -// WriteMsg writes message in smtp writer. -// Sends an email with subject and only this message. -func (s *JLWriter) WriteMsg(lm *LogMsg) error { - if lm.Level > s.Level { +// WriteMsg write message in smtp writer. +// it will send an email with subject and only this message. +func (s *JLWriter) WriteMsg(when time.Time, msg string, level int) error { + if level > s.Level { return nil } - text := s.formatter.Format(lm) + text := fmt.Sprintf("%s %s", when.Format("2006-01-02 15:04:05"), msg) form := url.Values{} form.Add("authorName", s.AuthorName) diff --git a/core/logs/log.go b/logs/log.go similarity index 78% rename from core/logs/log.go rename to logs/log.go index d5953dfb..39c006d2 100644 --- a/core/logs/log.go +++ b/logs/log.go @@ -37,12 +37,12 @@ import ( "fmt" "log" "os" + "path" "runtime" + "strconv" "strings" "sync" "time" - - "github.com/pkg/errors" ) // RFC5424 log message levels. @@ -86,10 +86,9 @@ type newLoggerFunc func() Logger // Logger defines the behavior of a log provider. type Logger interface { Init(config string) error - WriteMsg(lm *LogMsg) error + WriteMsg(when time.Time, msg string, level int) error Destroy() Flush() - SetFormatter(f LogFormatter) } var adapters = make(map[string]newLoggerFunc) @@ -109,22 +108,20 @@ func Register(name string, log newLoggerFunc) { } // BeeLogger is default logger in beego application. -// Can contain several providers and log message into all providers. +// it can contain several providers and log message into all providers. type BeeLogger struct { lock sync.Mutex level int init bool enableFuncCallDepth bool loggerFuncCallDepth int - enableFullFilePath bool asynchronous bool prefix string msgChanLen int64 - msgChan chan *LogMsg + msgChan chan *logMsg signalChan chan string wg sync.WaitGroup outputs []*nameLogger - globalFormatter string } const defaultAsyncMsgLen = 1e3 @@ -134,15 +131,21 @@ type nameLogger struct { name string } +type logMsg struct { + level int + msg string + when time.Time +} + var logMsgPool *sync.Pool // NewLogger returns a new BeeLogger. -// channelLen: the number of messages in chan(used where asynchronous is true). +// channelLen means the number of messages in chan(used where asynchronous is true). // if the buffering chan is full, logger adapters write to file or other way. func NewLogger(channelLens ...int64) *BeeLogger { bl := new(BeeLogger) bl.level = LevelDebug - bl.loggerFuncCallDepth = 3 + bl.loggerFuncCallDepth = 2 bl.msgChanLen = append(channelLens, 0)[0] if bl.msgChanLen <= 0 { bl.msgChanLen = defaultAsyncMsgLen @@ -152,7 +155,7 @@ func NewLogger(channelLens ...int64) *BeeLogger { return bl } -// Async sets the log to asynchronous and start the goroutine +// Async set the log to asynchronous and start the goroutine func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger { bl.lock.Lock() defer bl.lock.Unlock() @@ -163,10 +166,10 @@ func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger { if len(msgLen) > 0 && msgLen[0] > 0 { bl.msgChanLen = msgLen[0] } - bl.msgChan = make(chan *LogMsg, bl.msgChanLen) + bl.msgChan = make(chan *logMsg, bl.msgChanLen) logMsgPool = &sync.Pool{ New: func() interface{} { - return &LogMsg{} + return &logMsg{} }, } bl.wg.Add(1) @@ -175,7 +178,7 @@ func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger { } // SetLogger provides a given logger adapter into BeeLogger with config string. -// config must in in JSON format like {"interval":360}} +// config need to be correct JSON as string: {"interval":360}. func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error { config := append(configs, "{}")[0] for _, l := range bl.outputs { @@ -190,18 +193,7 @@ func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error { } lg := logAdapter() - - // Global formatter overrides the default set formatter - if len(bl.globalFormatter) > 0 { - fmtr, ok := GetFormatter(bl.globalFormatter) - if !ok { - return errors.New(fmt.Sprintf("the formatter with name: %s not found", bl.globalFormatter)) - } - lg.SetFormatter(fmtr) - } - err := lg.Init(config) - if err != nil { fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error()) return err @@ -211,7 +203,7 @@ func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error { } // SetLogger provides a given logger adapter into BeeLogger with config string. -// config must in in JSON format like {"interval":360}} +// config need to be correct JSON as string: {"interval":360}. func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error { bl.lock.Lock() defer bl.lock.Unlock() @@ -222,7 +214,7 @@ func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error { return bl.setLogger(adapterName, configs...) } -// DelLogger removes a logger adapter in BeeLogger. +// DelLogger remove a logger adapter in BeeLogger. func (bl *BeeLogger) DelLogger(adapterName string) error { bl.lock.Lock() defer bl.lock.Unlock() @@ -241,9 +233,9 @@ func (bl *BeeLogger) DelLogger(adapterName string) error { return nil } -func (bl *BeeLogger) writeToLoggers(lm *LogMsg) { +func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) { for _, l := range bl.outputs { - err := l.WriteMsg(lm) + err := l.WriteMsg(when, msg, level) if err != nil { fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err) } @@ -258,72 +250,65 @@ func (bl *BeeLogger) Write(p []byte) (n int, err error) { if p[len(p)-1] == '\n' { p = p[0 : len(p)-1] } - lm := &LogMsg{ - Msg: string(p), - Level: levelLoggerImpl, - } - // set levelLoggerImpl to ensure all log message will be write out - err = bl.writeMsg(lm) + err = bl.writeMsg(levelLoggerImpl, string(p)) if err == nil { return len(p), err } return 0, err } -func (bl *BeeLogger) writeMsg(lm *LogMsg) error { +func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error { if !bl.init { bl.lock.Lock() bl.setLogger(AdapterConsole) bl.lock.Unlock() } - var ( - file string - line int - ok bool - ) - - _, file, line, ok = runtime.Caller(bl.loggerFuncCallDepth) - if !ok { - file = "???" - line = 0 + if len(v) > 0 { + msg = fmt.Sprintf(msg, v...) } - lm.FilePath = file - lm.LineNumber = line - lm.enableFullFilePath = bl.enableFullFilePath - lm.enableFuncCallDepth = bl.enableFuncCallDepth + msg = bl.prefix + " " + msg - // set level info in front of filename info - if lm.Level == levelLoggerImpl { + when := time.Now() + if bl.enableFuncCallDepth { + _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) + if !ok { + file = "???" + line = 0 + } + _, filename := path.Split(file) + msg = "[" + filename + ":" + strconv.Itoa(line) + "] " + msg + } + + //set level info in front of filename info + if logLevel == levelLoggerImpl { // set to emergency to ensure all log will be print out correctly - lm.Level = LevelEmergency + logLevel = LevelEmergency + } else { + msg = levelPrefix[logLevel] + " " + msg } if bl.asynchronous { - logM := logMsgPool.Get().(*LogMsg) - logM.Level = lm.Level - logM.Msg = lm.Msg - logM.When = lm.When - logM.Args = lm.Args - logM.FilePath = lm.FilePath - logM.LineNumber = lm.LineNumber - logM.Prefix = lm.Prefix + lm := logMsgPool.Get().(*logMsg) + lm.level = logLevel + lm.msg = msg + lm.when = when if bl.outputs != nil { bl.msgChan <- lm } else { logMsgPool.Put(lm) } } else { - bl.writeToLoggers(lm) + bl.writeToLoggers(when, msg, logLevel) } return nil } -// SetLevel sets log message level. +// SetLevel Set log message level. // If message level (such as LevelDebug) is higher than logger level (such as LevelWarning), -// log providers will not be sent the message. +// log providers will not even be sent the message. func (bl *BeeLogger) SetLevel(l int) { bl.level = l } @@ -360,7 +345,7 @@ func (bl *BeeLogger) startLogger() { for { select { case bm := <-bl.msgChan: - bl.writeToLoggers(bm) + bl.writeToLoggers(bm.when, bm.msg, bm.level) logMsgPool.Put(bm) case sg := <-bl.signalChan: // Now should only send "flush" or "close" to bl.signalChan @@ -380,33 +365,12 @@ func (bl *BeeLogger) startLogger() { } } -func (bl *BeeLogger) setGlobalFormatter(fmtter string) error { - bl.globalFormatter = fmtter - return nil -} - -// SetGlobalFormatter sets the global formatter for all log adapters -// don't forget to register the formatter by invoking RegisterFormatter -func SetGlobalFormatter(fmtter string) error { - return beeLogger.setGlobalFormatter(fmtter) -} - // Emergency Log EMERGENCY level message. func (bl *BeeLogger) Emergency(format string, v ...interface{}) { if LevelEmergency > bl.level { return } - - lm := &LogMsg{ - Level: LevelEmergency, - Msg: format, - When: time.Now(), - } - if len(v) > 0 { - lm.Msg = fmt.Sprintf(lm.Msg, v...) - } - - bl.writeMsg(lm) + bl.writeMsg(LevelEmergency, format, v...) } // Alert Log ALERT level message. @@ -414,14 +378,7 @@ func (bl *BeeLogger) Alert(format string, v ...interface{}) { if LevelAlert > bl.level { return } - - lm := &LogMsg{ - Level: LevelAlert, - Msg: format, - When: time.Now(), - Args: v, - } - bl.writeMsg(lm) + bl.writeMsg(LevelAlert, format, v...) } // Critical Log CRITICAL level message. @@ -429,14 +386,7 @@ func (bl *BeeLogger) Critical(format string, v ...interface{}) { if LevelCritical > bl.level { return } - lm := &LogMsg{ - Level: LevelCritical, - Msg: format, - When: time.Now(), - Args: v, - } - - bl.writeMsg(lm) + bl.writeMsg(LevelCritical, format, v...) } // Error Log ERROR level message. @@ -444,14 +394,7 @@ func (bl *BeeLogger) Error(format string, v ...interface{}) { if LevelError > bl.level { return } - lm := &LogMsg{ - Level: LevelError, - Msg: format, - When: time.Now(), - Args: v, - } - - bl.writeMsg(lm) + bl.writeMsg(LevelError, format, v...) } // Warning Log WARNING level message. @@ -459,14 +402,7 @@ func (bl *BeeLogger) Warning(format string, v ...interface{}) { if LevelWarn > bl.level { return } - lm := &LogMsg{ - Level: LevelWarn, - Msg: format, - When: time.Now(), - Args: v, - } - - bl.writeMsg(lm) + bl.writeMsg(LevelWarn, format, v...) } // Notice Log NOTICE level message. @@ -474,14 +410,7 @@ func (bl *BeeLogger) Notice(format string, v ...interface{}) { if LevelNotice > bl.level { return } - lm := &LogMsg{ - Level: LevelNotice, - Msg: format, - When: time.Now(), - Args: v, - } - - bl.writeMsg(lm) + bl.writeMsg(LevelNotice, format, v...) } // Informational Log INFORMATIONAL level message. @@ -489,14 +418,7 @@ func (bl *BeeLogger) Informational(format string, v ...interface{}) { if LevelInfo > bl.level { return } - lm := &LogMsg{ - Level: LevelInfo, - Msg: format, - When: time.Now(), - Args: v, - } - - bl.writeMsg(lm) + bl.writeMsg(LevelInfo, format, v...) } // Debug Log DEBUG level message. @@ -504,14 +426,7 @@ func (bl *BeeLogger) Debug(format string, v ...interface{}) { if LevelDebug > bl.level { return } - lm := &LogMsg{ - Level: LevelDebug, - Msg: format, - When: time.Now(), - Args: v, - } - - bl.writeMsg(lm) + bl.writeMsg(LevelDebug, format, v...) } // Warn Log WARN level message. @@ -520,14 +435,7 @@ func (bl *BeeLogger) Warn(format string, v ...interface{}) { if LevelWarn > bl.level { return } - lm := &LogMsg{ - Level: LevelWarn, - Msg: format, - When: time.Now(), - Args: v, - } - - bl.writeMsg(lm) + bl.writeMsg(LevelWarn, format, v...) } // Info Log INFO level message. @@ -536,14 +444,7 @@ func (bl *BeeLogger) Info(format string, v ...interface{}) { if LevelInfo > bl.level { return } - lm := &LogMsg{ - Level: LevelInfo, - Msg: format, - When: time.Now(), - Args: v, - } - - bl.writeMsg(lm) + bl.writeMsg(LevelInfo, format, v...) } // Trace Log TRACE level message. @@ -552,14 +453,7 @@ func (bl *BeeLogger) Trace(format string, v ...interface{}) { if LevelDebug > bl.level { return } - lm := &LogMsg{ - Level: LevelDebug, - Msg: format, - When: time.Now(), - Args: v, - } - - bl.writeMsg(lm) + bl.writeMsg(LevelDebug, format, v...) } // Flush flush all chan data. @@ -603,7 +497,7 @@ func (bl *BeeLogger) flush() { for { if len(bl.msgChan) > 0 { bm := <-bl.msgChan - bl.writeToLoggers(bm) + bl.writeToLoggers(bm.when, bm.msg, bm.level) logMsgPool.Put(bm) continue } @@ -653,12 +547,6 @@ func GetLogger(prefixes ...string) *log.Logger { return l } -// EnableFullFilePath enables full file path logging. Disabled by default -// e.g "/home/Documents/GitHub/beego/mainapp/" instead of "mainapp" -func EnableFullFilePath(b bool) { - beeLogger.enableFullFilePath = b -} - // Reset will remove all the adapter func Reset() { beeLogger.Reset() @@ -687,7 +575,7 @@ func EnableFuncCallDepth(b bool) { // SetLogFuncCall set the CallDepth, default is 4 func SetLogFuncCall(b bool) { beeLogger.EnableFuncCallDepth(b) - beeLogger.SetLogFuncCallDepth(3) + beeLogger.SetLogFuncCallDepth(4) } // SetLogFuncCallDepth set log funcCallDepth @@ -765,9 +653,9 @@ func formatLog(f interface{}, v ...interface{}) string { return msg } if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") { - // format string + //format string } else { - // do not contain format char + //do not contain format char msg += strings.Repeat(" %v", len(v)) } default: diff --git a/core/logs/logger.go b/logs/logger.go similarity index 96% rename from core/logs/logger.go rename to logs/logger.go index d8b334d4..c7cf8a56 100644 --- a/core/logs/logger.go +++ b/logs/logger.go @@ -30,12 +30,11 @@ func newLogWriter(wr io.Writer) *logWriter { return &logWriter{writer: wr} } -func (lg *logWriter) writeln(msg string) (int, error) { +func (lg *logWriter) writeln(when time.Time, msg string) { lg.Lock() - msg += "\n" - n, err := lg.writer.Write([]byte(msg)) + h, _, _ := formatTimeHeader(when) + lg.writer.Write(append(append(h, msg...), '\n')) lg.Unlock() - return n, err } const ( diff --git a/core/logs/logger_test.go b/logs/logger_test.go similarity index 100% rename from core/logs/logger_test.go rename to logs/logger_test.go diff --git a/core/logs/multifile.go b/logs/multifile.go similarity index 83% rename from core/logs/multifile.go rename to logs/multifile.go index 79178211..90168274 100644 --- a/core/logs/multifile.go +++ b/logs/multifile.go @@ -16,6 +16,7 @@ package logs import ( "encoding/json" + "time" ) // A filesLogWriter manages several fileLogWriter @@ -45,7 +46,6 @@ var levelNames = [...]string{"emergency", "alert", "critical", "error", "warning // } func (f *multiFileLogWriter) Init(config string) error { - writer := newFileWriter().(*fileLogWriter) err := writer.Init(config) if err != nil { @@ -54,17 +54,11 @@ func (f *multiFileLogWriter) Init(config string) error { f.fullLogWriter = writer f.writers[LevelDebug+1] = writer - // unmarshal "separate" field to f.Separate - err = json.Unmarshal([]byte(config), f) - if err != nil { - return err - } + //unmarshal "separate" field to f.Separate + json.Unmarshal([]byte(config), f) jsonMap := map[string]interface{}{} - err = json.Unmarshal([]byte(config), &jsonMap) - if err != nil { - return err - } + json.Unmarshal([]byte(config), &jsonMap) for i := LevelEmergency; i < LevelDebug+1; i++ { for _, v := range f.Separate { @@ -81,17 +75,10 @@ func (f *multiFileLogWriter) Init(config string) error { } } } + return nil } -func (f *multiFileLogWriter) Format(lm *LogMsg) string { - return lm.OldStyleFormat() -} - -func (f *multiFileLogWriter) SetFormatter(fmt LogFormatter) { - f.fullLogWriter.SetFormatter(f) -} - func (f *multiFileLogWriter) Destroy() { for i := 0; i < len(f.writers); i++ { if f.writers[i] != nil { @@ -100,14 +87,14 @@ func (f *multiFileLogWriter) Destroy() { } } -func (f *multiFileLogWriter) WriteMsg(lm *LogMsg) error { +func (f *multiFileLogWriter) WriteMsg(when time.Time, msg string, level int) error { if f.fullLogWriter != nil { - f.fullLogWriter.WriteMsg(lm) + f.fullLogWriter.WriteMsg(when, msg, level) } for i := 0; i < len(f.writers)-1; i++ { if f.writers[i] != nil { - if lm.Level == f.writers[i].Level { - f.writers[i].WriteMsg(lm) + if level == f.writers[i].Level { + f.writers[i].WriteMsg(when, msg, level) } } } @@ -124,8 +111,7 @@ func (f *multiFileLogWriter) Flush() { // newFilesWriter create a FileLogWriter returning as LoggerInterface. func newFilesWriter() Logger { - res := &multiFileLogWriter{} - return res + return &multiFileLogWriter{} } func init() { diff --git a/core/logs/multifile_test.go b/logs/multifile_test.go similarity index 100% rename from core/logs/multifile_test.go rename to logs/multifile_test.go diff --git a/logs/slack.go b/logs/slack.go new file mode 100644 index 00000000..1cd2e5ae --- /dev/null +++ b/logs/slack.go @@ -0,0 +1,60 @@ +package logs + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" +) + +// SLACKWriter implements beego LoggerInterface and is used to send jiaoliao webhook +type SLACKWriter struct { + WebhookURL string `json:"webhookurl"` + Level int `json:"level"` +} + +// newSLACKWriter create jiaoliao writer. +func newSLACKWriter() Logger { + return &SLACKWriter{Level: LevelTrace} +} + +// Init SLACKWriter with json config string +func (s *SLACKWriter) Init(jsonconfig string) error { + return json.Unmarshal([]byte(jsonconfig), s) +} + +// WriteMsg write message in smtp writer. +// it will send an email with subject and only this message. +func (s *SLACKWriter) WriteMsg(when time.Time, msg string, level int) error { + if level > s.Level { + return nil + } + + text := fmt.Sprintf("{\"text\": \"%s %s\"}", when.Format("2006-01-02 15:04:05"), msg) + + form := url.Values{} + form.Add("payload", text) + + resp, err := http.PostForm(s.WebhookURL, form) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode) + } + return nil +} + +// Flush implementing method. empty. +func (s *SLACKWriter) Flush() { +} + +// Destroy implementing method. empty. +func (s *SLACKWriter) Destroy() { +} + +func init() { + Register(AdapterSlack, newSLACKWriter) +} diff --git a/core/logs/smtp.go b/logs/smtp.go similarity index 77% rename from core/logs/smtp.go rename to logs/smtp.go index 40891a7c..6208d7b8 100644 --- a/core/logs/smtp.go +++ b/logs/smtp.go @@ -21,8 +21,7 @@ import ( "net" "net/smtp" "strings" - - "github.com/pkg/errors" + "time" ) // SMTPWriter implements LoggerInterface and is used to send emails via given SMTP-server. @@ -34,15 +33,11 @@ type SMTPWriter struct { FromAddress string `json:"fromAddress"` RecipientAddresses []string `json:"sendTos"` Level int `json:"level"` - formatter LogFormatter - Formatter string `json:"formatter"` } -// NewSMTPWriter creates the smtp writer. +// NewSMTPWriter create smtp writer. func newSMTPWriter() Logger { - res := &SMTPWriter{Level: LevelTrace} - res.formatter = res - return res + return &SMTPWriter{Level: LevelTrace} } // Init smtp writer with json config. @@ -56,16 +51,8 @@ func newSMTPWriter() Logger { // "sendTos":["email1","email2"], // "level":LevelError // } -func (s *SMTPWriter) Init(config string) error { - res := json.Unmarshal([]byte(config), s) - if res == nil && len(s.Formatter) > 0 { - fmtr, ok := GetFormatter(s.Formatter) - if !ok { - return errors.New(fmt.Sprintf("the formatter with name: %s not found", s.Formatter)) - } - s.formatter = fmtr - } - return res +func (s *SMTPWriter) Init(jsonconfig string) error { + return json.Unmarshal([]byte(jsonconfig), s) } func (s *SMTPWriter) getSMTPAuth(host string) smtp.Auth { @@ -80,10 +67,6 @@ func (s *SMTPWriter) getSMTPAuth(host string) smtp.Auth { ) } -func (s *SMTPWriter) SetFormatter(f LogFormatter) { - s.formatter = f -} - func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error { client, err := smtp.Dial(hostAddressWithPort) if err != nil { @@ -132,14 +115,10 @@ func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAd return client.Quit() } -func (s *SMTPWriter) Format(lm *LogMsg) string { - return lm.OldStyleFormat() -} - -// WriteMsg writes message in smtp writer. -// Sends an email with subject and only this message. -func (s *SMTPWriter) WriteMsg(lm *LogMsg) error { - if lm.Level > s.Level { +// WriteMsg write message in smtp writer. +// it will send an email with subject and only this message. +func (s *SMTPWriter) WriteMsg(when time.Time, msg string, level int) error { + if level > s.Level { return nil } @@ -148,13 +127,11 @@ func (s *SMTPWriter) WriteMsg(lm *LogMsg) error { // Set up authentication information. auth := s.getSMTPAuth(hp[0]) - msg := s.Format(lm) - // Connect to the server, authenticate, set the sender and recipient, // and send the email all in one step. contentType := "Content-Type: text/plain" + "; charset=UTF-8" mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress + - ">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", lm.When.Format("2006-01-02 15:04:05")) + msg) + ">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", when.Format("2006-01-02 15:04:05")) + msg) return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg) } diff --git a/core/logs/smtp_test.go b/logs/smtp_test.go similarity index 100% rename from core/logs/smtp_test.go rename to logs/smtp_test.go diff --git a/adapter/metric/prometheus.go b/metric/prometheus.go similarity index 87% rename from adapter/metric/prometheus.go rename to metric/prometheus.go index 4660f626..7722240b 100644 --- a/adapter/metric/prometheus.go +++ b/metric/prometheus.go @@ -24,8 +24,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/astaxie/beego" - "github.com/astaxie/beego/core/logs" - "github.com/astaxie/beego/server/web" + "github.com/astaxie/beego/logs" ) func PrometheusMiddleWare(next http.Handler) http.Handler { @@ -33,9 +32,9 @@ func PrometheusMiddleWare(next http.Handler) http.Handler { Name: "beego", Subsystem: "http_request", ConstLabels: map[string]string{ - "server": web.BConfig.ServerName, - "env": web.BConfig.RunMode, - "appname": web.BConfig.AppName, + "server": beego.BConfig.ServerName, + "env": beego.BConfig.RunMode, + "appname": beego.BConfig.AppName, }, Help: "The statics info for http request", }, []string{"pattern", "method", "status", "duration"}) @@ -58,15 +57,15 @@ func registerBuildInfo() { Subsystem: "build_info", Help: "The building information", ConstLabels: map[string]string{ - "appname": web.BConfig.AppName, + "appname": beego.BConfig.AppName, "build_version": beego.BuildVersion, "build_revision": beego.BuildGitRevision, "build_status": beego.BuildStatus, "build_tag": beego.BuildTag, - "build_time": strings.Replace(beego.BuildTime, "--", " ", 1), + "build_time": strings.Replace(beego.BuildTime, "--", " ", 1), "go_version": beego.GoVersion, "git_branch": beego.GitBranch, - "start_time": time.Now().Format("2006-01-02 15:04:05"), + "start_time": time.Now().Format("2006-01-02 15:04:05"), }, }, []string{}) @@ -75,7 +74,7 @@ func registerBuildInfo() { } func report(dur time.Duration, writer http.ResponseWriter, q *http.Request, vec *prometheus.SummaryVec) { - ctrl := web.BeeApp.Handlers + ctrl := beego.BeeApp.Handlers ctx := ctrl.GetContext() ctx.Reset(writer, q) defer ctrl.GiveBackContext(ctx) diff --git a/adapter/metric/prometheus_test.go b/metric/prometheus_test.go similarity index 96% rename from adapter/metric/prometheus_test.go rename to metric/prometheus_test.go index 751348bf..d82a6dec 100644 --- a/adapter/metric/prometheus_test.go +++ b/metric/prometheus_test.go @@ -22,7 +22,7 @@ import ( "github.com/prometheus/client_golang/prometheus" - "github.com/astaxie/beego/adapter/context" + "github.com/astaxie/beego/context" ) func TestPrometheusMiddleWare(t *testing.T) { diff --git a/client/orm/migration/ddl.go b/migration/ddl.go similarity index 85% rename from client/orm/migration/ddl.go rename to migration/ddl.go index a396d39a..cd2c1c49 100644 --- a/client/orm/migration/ddl.go +++ b/migration/ddl.go @@ -17,7 +17,7 @@ package migration import ( "fmt" - "github.com/astaxie/beego/core/logs" + "github.com/astaxie/beego/logs" ) // Index struct defines the structure of Index Columns @@ -31,7 +31,7 @@ type Unique struct { Columns []*Column } -// Column struct defines a single column of a table +//Column struct defines a single column of a table type Column struct { Name string Inc string @@ -84,7 +84,7 @@ func (m *Migration) NewCol(name string) *Column { return col } -// PriCol creates a new primary column and attaches it to m struct +//PriCol creates a new primary column and attaches it to m struct func (m *Migration) PriCol(name string) *Column { col := &Column{Name: name} m.AddColumns(col) @@ -92,7 +92,7 @@ func (m *Migration) PriCol(name string) *Column { return col } -// UniCol creates / appends columns to specified unique key and attaches it to m struct +//UniCol creates / appends columns to specified unique key and attaches it to m struct func (m *Migration) UniCol(uni, name string) *Column { col := &Column{Name: name} m.AddColumns(col) @@ -114,7 +114,7 @@ func (m *Migration) UniCol(uni, name string) *Column { return col } -// ForeignCol creates a new foreign column and returns the instance of column +//ForeignCol creates a new foreign column and returns the instance of column func (m *Migration) ForeignCol(colname, foreigncol, foreigntable string) (foreign *Foreign) { foreign = &Foreign{ForeignColumn: foreigncol, ForeignTable: foreigntable} @@ -123,25 +123,25 @@ func (m *Migration) ForeignCol(colname, foreigncol, foreigntable string) (foreig return foreign } -// SetOnDelete sets the on delete of foreign +//SetOnDelete sets the on delete of foreign func (foreign *Foreign) SetOnDelete(del string) *Foreign { foreign.OnDelete = "ON DELETE" + del return foreign } -// SetOnUpdate sets the on update of foreign +//SetOnUpdate sets the on update of foreign func (foreign *Foreign) SetOnUpdate(update string) *Foreign { foreign.OnUpdate = "ON UPDATE" + update return foreign } -// Remove marks the columns to be removed. -// it allows reverse m to create the column. +//Remove marks the columns to be removed. +//it allows reverse m to create the column. func (c *Column) Remove() { c.remove = true } -// SetAuto enables auto_increment of column (can be used once) +//SetAuto enables auto_increment of column (can be used once) func (c *Column) SetAuto(inc bool) *Column { if inc { c.Inc = "auto_increment" @@ -149,7 +149,7 @@ func (c *Column) SetAuto(inc bool) *Column { return c } -// SetNullable sets the column to be null +//SetNullable sets the column to be null func (c *Column) SetNullable(null bool) *Column { if null { c.Null = "" @@ -160,13 +160,13 @@ func (c *Column) SetNullable(null bool) *Column { return c } -// SetDefault sets the default value, prepend with "DEFAULT " +//SetDefault sets the default value, prepend with "DEFAULT " func (c *Column) SetDefault(def string) *Column { c.Default = "DEFAULT " + def return c } -// SetUnsigned sets the column to be unsigned int +//SetUnsigned sets the column to be unsigned int func (c *Column) SetUnsigned(unsign bool) *Column { if unsign { c.Unsign = "UNSIGNED" @@ -174,13 +174,13 @@ func (c *Column) SetUnsigned(unsign bool) *Column { return c } -// SetDataType sets the dataType of the column +//SetDataType sets the dataType of the column func (c *Column) SetDataType(dataType string) *Column { c.DataType = dataType return c } -// SetOldNullable allows reverting to previous nullable on reverse ms +//SetOldNullable allows reverting to previous nullable on reverse ms func (c *RenameColumn) SetOldNullable(null bool) *RenameColumn { if null { c.OldNull = "" @@ -191,13 +191,13 @@ func (c *RenameColumn) SetOldNullable(null bool) *RenameColumn { return c } -// SetOldDefault allows reverting to previous default on reverse ms +//SetOldDefault allows reverting to previous default on reverse ms func (c *RenameColumn) SetOldDefault(def string) *RenameColumn { c.OldDefault = def return c } -// SetOldUnsigned allows reverting to previous unsgined on reverse ms +//SetOldUnsigned allows reverting to previous unsgined on reverse ms func (c *RenameColumn) SetOldUnsigned(unsign bool) *RenameColumn { if unsign { c.OldUnsign = "UNSIGNED" @@ -205,19 +205,19 @@ func (c *RenameColumn) SetOldUnsigned(unsign bool) *RenameColumn { return c } -// SetOldDataType allows reverting to previous datatype on reverse ms +//SetOldDataType allows reverting to previous datatype on reverse ms func (c *RenameColumn) SetOldDataType(dataType string) *RenameColumn { c.OldDataType = dataType return c } -// SetPrimary adds the columns to the primary key (can only be used any number of times in only one m) +//SetPrimary adds the columns to the primary key (can only be used any number of times in only one m) func (c *Column) SetPrimary(m *Migration) *Column { m.Primary = append(m.Primary, c) return c } -// AddColumnsToUnique adds the columns to Unique Struct +//AddColumnsToUnique adds the columns to Unique Struct func (unique *Unique) AddColumnsToUnique(columns ...*Column) *Unique { unique.Columns = append(unique.Columns, columns...) @@ -225,7 +225,7 @@ func (unique *Unique) AddColumnsToUnique(columns ...*Column) *Unique { return unique } -// AddColumns adds columns to m struct +//AddColumns adds columns to m struct func (m *Migration) AddColumns(columns ...*Column) *Migration { m.Columns = append(m.Columns, columns...) @@ -233,38 +233,38 @@ func (m *Migration) AddColumns(columns ...*Column) *Migration { return m } -// AddPrimary adds the column to primary in m struct +//AddPrimary adds the column to primary in m struct func (m *Migration) AddPrimary(primary *Column) *Migration { m.Primary = append(m.Primary, primary) return m } -// AddUnique adds the column to unique in m struct +//AddUnique adds the column to unique in m struct func (m *Migration) AddUnique(unique *Unique) *Migration { m.Uniques = append(m.Uniques, unique) return m } -// AddForeign adds the column to foreign in m struct +//AddForeign adds the column to foreign in m struct func (m *Migration) AddForeign(foreign *Foreign) *Migration { m.Foreigns = append(m.Foreigns, foreign) return m } -// AddIndex adds the column to index in m struct +//AddIndex adds the column to index in m struct func (m *Migration) AddIndex(index *Index) *Migration { m.Indexes = append(m.Indexes, index) return m } -// RenameColumn allows renaming of columns +//RenameColumn allows renaming of columns func (m *Migration) RenameColumn(from, to string) *RenameColumn { rename := &RenameColumn{OldName: from, NewName: to} m.Renames = append(m.Renames, rename) return rename } -// GetSQL returns the generated sql depending on ModifyType +//GetSQL returns the generated sql depending on ModifyType func (m *Migration) GetSQL() (sql string) { sql = "" switch m.ModifyType { diff --git a/adapter/migration/doc.go b/migration/doc.go similarity index 100% rename from adapter/migration/doc.go rename to migration/doc.go diff --git a/client/orm/migration/migration.go b/migration/migration.go similarity index 99% rename from client/orm/migration/migration.go rename to migration/migration.go index aeea12c6..5ddfd972 100644 --- a/client/orm/migration/migration.go +++ b/migration/migration.go @@ -33,8 +33,8 @@ import ( "strings" "time" - "github.com/astaxie/beego/client/orm" - "github.com/astaxie/beego/core/logs" + "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/orm" ) // const the data format for the bee generate migration datatype diff --git a/server/web/mime.go b/mime.go similarity index 99% rename from server/web/mime.go rename to mime.go index 9393e9c7..ca2878ab 100644 --- a/server/web/mime.go +++ b/mime.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego var mimemaps = map[string]string{ ".3dm": "x-world/x-3dmf", diff --git a/server/web/namespace.go b/namespace.go similarity index 98% rename from server/web/namespace.go rename to namespace.go index 58afb6c7..4952c9d5 100644 --- a/server/web/namespace.go +++ b/namespace.go @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "net/http" "strings" - beecontext "github.com/astaxie/beego/server/web/context" + beecontext "github.com/astaxie/beego/context" ) type namespaceCond func(*beecontext.Context) bool @@ -91,7 +91,7 @@ func (n *Namespace) Filter(action string, filter ...FilterFunc) *Namespace { a = FinishRouter } for _, f := range filter { - n.handlers.InsertFilter("*", a, f, WithReturnOnOutput(true)) + n.handlers.InsertFilter("*", a, f) } return n } diff --git a/server/web/namespace_test.go b/namespace_test.go similarity index 98% rename from server/web/namespace_test.go rename to namespace_test.go index a6f87bba..b3f20dff 100644 --- a/server/web/namespace_test.go +++ b/namespace_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "net/http" @@ -20,7 +20,7 @@ import ( "strconv" "testing" - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/context" ) func TestNamespaceGet(t *testing.T) { diff --git a/client/orm/README.md b/orm/README.md similarity index 100% rename from client/orm/README.md rename to orm/README.md diff --git a/client/orm/cmd.go b/orm/cmd.go similarity index 85% rename from client/orm/cmd.go rename to orm/cmd.go index b0661971..0ff4dc40 100644 --- a/client/orm/cmd.go +++ b/orm/cmd.go @@ -46,7 +46,7 @@ func printHelp(errs ...string) { os.Exit(2) } -// RunCommand listens for orm command and runs if command arguments have been passed. +// RunCommand listen for orm command and then run it if command arguments passed. func RunCommand() { if len(os.Args) < 2 || os.Args[1] != "orm" { return @@ -83,7 +83,7 @@ type commandSyncDb struct { rtOnError bool } -// Parse the orm command line arguments. +// parse orm command line arguments. func (d *commandSyncDb) Parse(args []string) { var name string @@ -96,20 +96,16 @@ func (d *commandSyncDb) Parse(args []string) { d.al = getDbAlias(name) } -// Run orm line command. +// run orm line command. func (d *commandSyncDb) Run() error { var drops []string - var err error if d.force { - drops, err = modelCache.getDbDropSQL(d.al) - if err != nil { - return err - } + drops = getDbDropSQL(d.al) } db := d.al.DB - if d.force && len(drops) > 0 { + if d.force { for i, mi := range modelCache.allOrdered() { query := drops[i] if !d.noInfo { @@ -128,10 +124,7 @@ func (d *commandSyncDb) Run() error { } } - createQueries, indexes, err := modelCache.getDbCreateSQL(d.al) - if err != nil { - return err - } + sqls, indexes := getDbCreateSQL(d.al) tables, err := d.al.DbBaser.GetTables(db) if err != nil { @@ -142,12 +135,6 @@ func (d *commandSyncDb) Run() error { } for i, mi := range modelCache.allOrdered() { - - if !isApplicableTableForDB(mi.addrField, d.al.Name) { - fmt.Printf("table `%s` is not applicable to database '%s'\n", mi.table, d.al.Name) - continue - } - if tables[mi.table] { if !d.noInfo { fmt.Printf("table `%s` already exists, skip\n", mi.table) @@ -214,7 +201,7 @@ func (d *commandSyncDb) Run() error { fmt.Printf("create table `%s` \n", mi.table) } - queries := []string{createQueries[i]} + queries := []string{sqls[i]} for _, idx := range indexes[mi.table] { queries = append(queries, idx.SQL) } @@ -245,7 +232,7 @@ type commandSQLAll struct { al *alias } -// Parse orm command line arguments. +// parse orm command line arguments. func (d *commandSQLAll) Parse(args []string) { var name string @@ -256,15 +243,12 @@ func (d *commandSQLAll) Parse(args []string) { d.al = getDbAlias(name) } -// Run orm line command. +// run orm line command. func (d *commandSQLAll) Run() error { - createQueries, indexes, err := modelCache.getDbCreateSQL(d.al) - if err != nil { - return err - } + sqls, indexes := getDbCreateSQL(d.al) var all []string for i, mi := range modelCache.allOrdered() { - queries := []string{createQueries[i]} + queries := []string{sqls[i]} for _, idx := range indexes[mi.table] { queries = append(queries, idx.SQL) } @@ -282,9 +266,9 @@ func init() { } // RunSyncdb run syncdb command line. -// name: Table's alias name (default is "default") -// force: Run the next sql command even if the current gave an error -// verbose: Print all information, useful for debugging +// name means table's alias name. default is "default". +// force means run next sql if the current is error. +// verbose means show all info when running command or not. func RunSyncdb(name string, force bool, verbose bool) error { BootStrap() diff --git a/orm/cmd_utils.go b/orm/cmd_utils.go new file mode 100644 index 00000000..61f17346 --- /dev/null +++ b/orm/cmd_utils.go @@ -0,0 +1,320 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "fmt" + "os" + "strings" +) + +type dbIndex struct { + Table string + Name string + SQL string +} + +// create database drop sql. +func getDbDropSQL(al *alias) (sqls []string) { + if len(modelCache.cache) == 0 { + fmt.Println("no Model found, need register your model") + os.Exit(2) + } + + Q := al.DbBaser.TableQuote() + + for _, mi := range modelCache.allOrdered() { + sqls = append(sqls, fmt.Sprintf(`DROP TABLE IF EXISTS %s%s%s`, Q, mi.table, Q)) + } + return sqls +} + +// get database column type string. +func getColumnTyp(al *alias, fi *fieldInfo) (col string) { + T := al.DbBaser.DbTypes() + fieldType := fi.fieldType + fieldSize := fi.size + +checkColumn: + switch fieldType { + case TypeBooleanField: + col = T["bool"] + case TypeVarCharField: + if al.Driver == DRPostgres && fi.toText { + col = T["string-text"] + } else { + col = fmt.Sprintf(T["string"], fieldSize) + } + case TypeCharField: + col = fmt.Sprintf(T["string-char"], fieldSize) + case TypeTextField: + col = T["string-text"] + case TypeTimeField: + col = T["time.Time-clock"] + case TypeDateField: + col = T["time.Time-date"] + case TypeDateTimeField: + col = T["time.Time"] + case TypeBitField: + col = T["int8"] + case TypeSmallIntegerField: + col = T["int16"] + case TypeIntegerField: + col = T["int32"] + case TypeBigIntegerField: + if al.Driver == DRSqlite { + fieldType = TypeIntegerField + goto checkColumn + } + col = T["int64"] + case TypePositiveBitField: + col = T["uint8"] + case TypePositiveSmallIntegerField: + col = T["uint16"] + case TypePositiveIntegerField: + col = T["uint32"] + case TypePositiveBigIntegerField: + col = T["uint64"] + case TypeFloatField: + col = T["float64"] + case TypeDecimalField: + s := T["float64-decimal"] + if !strings.Contains(s, "%d") { + col = s + } else { + col = fmt.Sprintf(s, fi.digits, fi.decimals) + } + case TypeJSONField: + if al.Driver != DRPostgres { + fieldType = TypeVarCharField + goto checkColumn + } + col = T["json"] + case TypeJsonbField: + if al.Driver != DRPostgres { + fieldType = TypeVarCharField + goto checkColumn + } + col = T["jsonb"] + case RelForeignKey, RelOneToOne: + fieldType = fi.relModelInfo.fields.pk.fieldType + fieldSize = fi.relModelInfo.fields.pk.size + goto checkColumn + } + + return +} + +// create alter sql string. +func getColumnAddQuery(al *alias, fi *fieldInfo) string { + Q := al.DbBaser.TableQuote() + typ := getColumnTyp(al, fi) + + if !fi.null { + typ += " " + "NOT NULL" + } + + return fmt.Sprintf("ALTER TABLE %s%s%s ADD COLUMN %s%s%s %s %s", + Q, fi.mi.table, Q, + Q, fi.column, Q, + typ, getColumnDefault(fi), + ) +} + +// create database creation string. +func getDbCreateSQL(al *alias) (sqls []string, tableIndexes map[string][]dbIndex) { + if len(modelCache.cache) == 0 { + fmt.Println("no Model found, need register your model") + os.Exit(2) + } + + Q := al.DbBaser.TableQuote() + T := al.DbBaser.DbTypes() + sep := fmt.Sprintf("%s, %s", Q, Q) + + tableIndexes = make(map[string][]dbIndex) + + for _, mi := range modelCache.allOrdered() { + sql := fmt.Sprintf("-- %s\n", strings.Repeat("-", 50)) + sql += fmt.Sprintf("-- Table Structure for `%s`\n", mi.fullName) + sql += fmt.Sprintf("-- %s\n", strings.Repeat("-", 50)) + + sql += fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s%s%s (\n", Q, mi.table, Q) + + columns := make([]string, 0, len(mi.fields.fieldsDB)) + + sqlIndexes := [][]string{} + + for _, fi := range mi.fields.fieldsDB { + + column := fmt.Sprintf(" %s%s%s ", Q, fi.column, Q) + col := getColumnTyp(al, fi) + + if fi.auto { + switch al.Driver { + case DRSqlite, DRPostgres: + column += T["auto"] + default: + column += col + " " + T["auto"] + } + } else if fi.pk { + column += col + " " + T["pk"] + } else { + column += col + + if !fi.null { + column += " " + "NOT NULL" + } + + //if fi.initial.String() != "" { + // column += " DEFAULT " + fi.initial.String() + //} + + // Append attribute DEFAULT + column += getColumnDefault(fi) + + if fi.unique { + column += " " + "UNIQUE" + } + + if fi.index { + sqlIndexes = append(sqlIndexes, []string{fi.column}) + } + } + + if strings.Contains(column, "%COL%") { + column = strings.Replace(column, "%COL%", fi.column, -1) + } + + if fi.description != "" && al.Driver!=DRSqlite { + column += " " + fmt.Sprintf("COMMENT '%s'",fi.description) + } + + columns = append(columns, column) + } + + if mi.model != nil { + allnames := getTableUnique(mi.addrField) + if !mi.manual && len(mi.uniques) > 0 { + allnames = append(allnames, mi.uniques) + } + for _, names := range allnames { + cols := make([]string, 0, len(names)) + for _, name := range names { + if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol { + cols = append(cols, fi.column) + } else { + panic(fmt.Errorf("cannot found column `%s` when parse UNIQUE in `%s.TableUnique`", name, mi.fullName)) + } + } + column := fmt.Sprintf(" UNIQUE (%s%s%s)", Q, strings.Join(cols, sep), Q) + columns = append(columns, column) + } + } + + sql += strings.Join(columns, ",\n") + sql += "\n)" + + if al.Driver == DRMySQL { + var engine string + if mi.model != nil { + engine = getTableEngine(mi.addrField) + } + if engine == "" { + engine = al.Engine + } + sql += " ENGINE=" + engine + } + + sql += ";" + sqls = append(sqls, sql) + + if mi.model != nil { + for _, names := range getTableIndex(mi.addrField) { + cols := make([]string, 0, len(names)) + for _, name := range names { + if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol { + cols = append(cols, fi.column) + } else { + panic(fmt.Errorf("cannot found column `%s` when parse INDEX in `%s.TableIndex`", name, mi.fullName)) + } + } + sqlIndexes = append(sqlIndexes, cols) + } + } + + for _, names := range sqlIndexes { + name := mi.table + "_" + strings.Join(names, "_") + cols := strings.Join(names, sep) + sql := fmt.Sprintf("CREATE INDEX %s%s%s ON %s%s%s (%s%s%s);", Q, name, Q, Q, mi.table, Q, Q, cols, Q) + + index := dbIndex{} + index.Table = mi.table + index.Name = name + index.SQL = sql + + tableIndexes[mi.table] = append(tableIndexes[mi.table], index) + } + + } + + return +} + +// Get string value for the attribute "DEFAULT" for the CREATE, ALTER commands +func getColumnDefault(fi *fieldInfo) string { + var ( + v, t, d string + ) + + // Skip default attribute if field is in relations + if fi.rel || fi.reverse { + return v + } + + t = " DEFAULT '%s' " + + // These defaults will be useful if there no config value orm:"default" and NOT NULL is on + switch fi.fieldType { + case TypeTimeField, TypeDateField, TypeDateTimeField, TypeTextField: + return v + + case TypeBitField, TypeSmallIntegerField, TypeIntegerField, + TypeBigIntegerField, TypePositiveBitField, TypePositiveSmallIntegerField, + TypePositiveIntegerField, TypePositiveBigIntegerField, TypeFloatField, + TypeDecimalField: + t = " DEFAULT %s " + d = "0" + case TypeBooleanField: + t = " DEFAULT %s " + d = "FALSE" + case TypeJSONField, TypeJsonbField: + d = "{}" + } + + if fi.colDefault { + if !fi.initial.Exist() { + v = fmt.Sprintf(t, "") + } else { + v = fmt.Sprintf(t, fi.initial.String()) + } + } else { + if !fi.null { + v = fmt.Sprintf(t, d) + } + } + + return v +} diff --git a/client/orm/db.go b/orm/db.go similarity index 94% rename from client/orm/db.go rename to orm/db.go index b103d218..9a1827e8 100644 --- a/client/orm/db.go +++ b/orm/db.go @@ -21,8 +21,6 @@ import ( "reflect" "strings" "time" - - "github.com/astaxie/beego/client/orm/hints" ) const ( @@ -38,11 +36,10 @@ var ( var ( operators = map[string]bool{ - "exact": true, - "iexact": true, - "strictexact": true, - "contains": true, - "icontains": true, + "exact": true, + "iexact": true, + "contains": true, + "icontains": true, // "regex": true, // "iregex": true, "gt": true, @@ -487,14 +484,7 @@ func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []s if isMulti { return res.RowsAffected() } - - lastInsertId, err := res.LastInsertId() - if err != nil { - DebugLog.Println(ErrLastInsertIdUnavailable, ':', err) - return lastInsertId, ErrLastInsertIdUnavailable - } else { - return lastInsertId, nil - } + return res.LastInsertId() } return 0, err } @@ -595,14 +585,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a if isMulti { return res.RowsAffected() } - - lastInsertId, err := res.LastInsertId() - if err != nil { - DebugLog.Println(ErrLastInsertIdUnavailable, ':', err) - return lastInsertId, ErrLastInsertIdUnavailable - } else { - return lastInsertId, nil - } + return res.LastInsertId() } return 0, err } @@ -755,10 +738,8 @@ func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con } tables := newDbTables(mi, d.ins) - var specifyIndexes string if qs != nil { tables.parseRelated(qs.related, qs.relDepth) - specifyIndexes = tables.getIndexSql(mi.table, qs.useIndex, qs.indexes) } where, args := tables.getCondSQL(cond, false, tz) @@ -809,12 +790,9 @@ func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con sets := strings.Join(cols, ", ") + " " if d.ins.SupportUpdateJoin() { - query = fmt.Sprintf("UPDATE %s%s%s T0 %s%sSET %s%s", Q, mi.table, Q, specifyIndexes, join, sets, where) + query = fmt.Sprintf("UPDATE %s%s%s T0 %sSET %s%s", Q, mi.table, Q, join, sets, where) } else { - supQuery := fmt.Sprintf("SELECT T0.%s%s%s FROM %s%s%s T0 %s%s%s", - Q, mi.fields.pk.column, Q, - Q, mi.table, Q, - specifyIndexes, join, where) + supQuery := fmt.Sprintf("SELECT T0.%s%s%s FROM %s%s%s T0 %s%s", Q, mi.fields.pk.column, Q, Q, mi.table, Q, join, where) query = fmt.Sprintf("UPDATE %s%s%s SET %sWHERE %s%s%s IN ( %s )", Q, mi.table, Q, sets, Q, mi.fields.pk.column, Q, supQuery) } @@ -865,10 +843,8 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con tables := newDbTables(mi, d.ins) tables.skipEnd = true - var specifyIndexes string if qs != nil { tables.parseRelated(qs.related, qs.relDepth) - specifyIndexes = tables.getIndexSql(mi.table, qs.useIndex, qs.indexes) } if cond == nil || cond.IsEmpty() { @@ -881,7 +857,7 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con join := tables.getJoinSQL() cols := fmt.Sprintf("T0.%s%s%s", Q, mi.fields.pk.column, Q) - query := fmt.Sprintf("SELECT %s FROM %s%s%s T0 %s%s%s", cols, Q, mi.table, Q, specifyIndexes, join, where) + query := fmt.Sprintf("SELECT %s FROM %s%s%s T0 %s%s", cols, Q, mi.table, Q, join, where) d.ins.ReplaceMarks(&query) @@ -1026,7 +1002,6 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi orderBy := tables.getOrderSQL(qs.orders) limit := tables.getLimitSQL(mi, offset, rlimit) join := tables.getJoinSQL() - specifyIndexes := tables.getIndexSql(mi.table, qs.useIndex, qs.indexes) for _, tbl := range tables.tables { if tbl.sel { @@ -1040,11 +1015,9 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi if qs.distinct { sqlSelect += " DISTINCT" } - query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s%s", - sqlSelect, sels, Q, mi.table, Q, - specifyIndexes, join, where, groupBy, orderBy, limit) + query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s", sqlSelect, sels, Q, mi.table, Q, join, where, groupBy, orderBy, limit) - if qs.forUpdate { + if qs.forupdate { query += " FOR UPDATE" } @@ -1180,13 +1153,10 @@ func (d *dbBase) Count(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition groupBy := tables.getGroupSQL(qs.groups) tables.getOrderSQL(qs.orders) join := tables.getJoinSQL() - specifyIndexes := tables.getIndexSql(mi.table, qs.useIndex, qs.indexes) Q := d.ins.TableQuote() - query := fmt.Sprintf("SELECT COUNT(*) FROM %s%s%s T0 %s%s%s%s", - Q, mi.table, Q, - specifyIndexes, join, where, groupBy) + query := fmt.Sprintf("SELECT COUNT(*) FROM %s%s%s T0 %s%s%s", Q, mi.table, Q, join, where, groupBy) if groupBy != "" { query = fmt.Sprintf("SELECT COUNT(*) FROM (%s) AS T", query) @@ -1356,14 +1326,7 @@ setValue: t time.Time err error ) - - if fi.timePrecision != nil && len(s) >= (20+*fi.timePrecision) { - layout := formatDateTime + "." - for i := 0; i < *fi.timePrecision; i++ { - layout += "0" - } - t, err = time.ParseInLocation(layout, s[:20+*fi.timePrecision], tz) - } else if len(s) >= 19 { + if len(s) >= 19 { s = s[:19] t, err = time.ParseInLocation(formatDateTime, s, tz) } else if len(s) >= 10 { @@ -1717,7 +1680,6 @@ func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Cond orderBy := tables.getOrderSQL(qs.orders) limit := tables.getLimitSQL(mi, qs.offset, qs.limit) join := tables.getJoinSQL() - specifyIndexes := tables.getIndexSql(mi.table, qs.useIndex, qs.indexes) sels := strings.Join(cols, ", ") @@ -1725,10 +1687,7 @@ func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Cond if qs.distinct { sqlSelect += " DISTINCT" } - query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s%s", - sqlSelect, sels, - Q, mi.table, Q, - specifyIndexes, join, where, groupBy, orderBy, limit) + query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s", sqlSelect, sels, Q, mi.table, Q, join, where, groupBy, orderBy, limit) d.ins.ReplaceMarks(&query) @@ -1822,6 +1781,10 @@ func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Cond return cnt, nil } +func (d *dbBase) RowsTo(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, string, string, *time.Location) (int64, error) { + return 0, nil +} + // flag of update joined record. func (d *dbBase) SupportUpdateJoin() bool { return true @@ -1937,29 +1900,3 @@ func (d *dbBase) ShowColumnsQuery(table string) string { func (d *dbBase) IndexExists(dbQuerier, string, string) bool { panic(ErrNotImplement) } - -// GenerateSpecifyIndex return a specifying index clause -func (d *dbBase) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string { - var s []string - Q := d.TableQuote() - for _, index := range indexes { - tmp := fmt.Sprintf(`%s%s%s`, Q, index, Q) - s = append(s, tmp) - } - - var useWay string - - switch useIndex { - case hints.KeyUseIndex: - useWay = `USE` - case hints.KeyForceIndex: - useWay = `FORCE` - case hints.KeyIgnoreIndex: - useWay = `IGNORE` - default: - DebugLog.Println("[WARN] Not a valid specifying action, so that action is ignored") - return `` - } - - return fmt.Sprintf(` %s INDEX(%s) `, useWay, strings.Join(s, `,`)) -} diff --git a/client/orm/db_alias.go b/orm/db_alias.go similarity index 62% rename from client/orm/db_alias.go rename to orm/db_alias.go index 29e0904c..cf6a5935 100644 --- a/client/orm/db_alias.go +++ b/orm/db_alias.go @@ -18,10 +18,10 @@ import ( "context" "database/sql" "fmt" + lru "github.com/hashicorp/golang-lru" + "reflect" "sync" "time" - - lru "github.com/hashicorp/golang-lru" ) // DriverType database driver constant int. @@ -63,7 +63,7 @@ var ( "tidb": DRTiDB, "oracle": DROracle, "oci8": DROracle, // github.com/mattn/go-oci8 - "ora": DROracle, // https://github.com/rana/ora + "ora": DROracle, //https://github.com/rana/ora } dbBasers = map[DriverType]dbBaser{ DRMySQL: newdbBaseMysql(), @@ -107,14 +107,10 @@ func (ac *_dbCache) getDefault() (al *alias) { type DB struct { *sync.RWMutex - DB *sql.DB - stmtDecorators *lru.Cache - stmtDecoratorsLimit int + DB *sql.DB + stmtDecorators *lru.Cache } -var _ dbQuerier = new(DB) -var _ txer = new(DB) - func (d *DB) Begin() (*sql.Tx, error) { return d.DB.Begin() } @@ -123,7 +119,7 @@ func (d *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) return d.DB.BeginTx(ctx, opts) } -// su must call release to release *sql.Stmt after using +//su must call release to release *sql.Stmt after using func (d *DB) getStmtDecorator(query string) (*stmtDecorator, error) { d.RLock() c, ok := d.stmtDecorators.Get(query) @@ -164,14 +160,16 @@ func (d *DB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error } func (d *DB) Exec(query string, args ...interface{}) (sql.Result, error) { - return d.ExecContext(context.Background(), query, args...) + sd, err := d.getStmtDecorator(query) + if err != nil { + return nil, err + } + stmt := sd.getStmt() + defer sd.release() + return stmt.Exec(args...) } func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { - if d.stmtDecorators == nil { - return d.DB.ExecContext(ctx, query, args...) - } - sd, err := d.getStmtDecorator(query) if err != nil { return nil, err @@ -182,14 +180,16 @@ func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) } func (d *DB) Query(query string, args ...interface{}) (*sql.Rows, error) { - return d.QueryContext(context.Background(), query, args...) + sd, err := d.getStmtDecorator(query) + if err != nil { + return nil, err + } + stmt := sd.getStmt() + defer sd.release() + return stmt.Query(args...) } func (d *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { - if d.stmtDecorators == nil { - return d.DB.QueryContext(ctx, query, args...) - } - sd, err := d.getStmtDecorator(query) if err != nil { return nil, err @@ -200,86 +200,37 @@ func (d *DB) QueryContext(ctx context.Context, query string, args ...interface{} } func (d *DB) QueryRow(query string, args ...interface{}) *sql.Row { - return d.QueryRowContext(context.Background(), query, args...) -} - -func (d *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { - if d.stmtDecorators == nil { - return d.DB.QueryRowContext(ctx, query, args...) - } - sd, err := d.getStmtDecorator(query) if err != nil { panic(err) } stmt := sd.getStmt() defer sd.release() - return stmt.QueryRowContext(ctx, args...) + return stmt.QueryRow(args...) + } -type TxDB struct { - tx *sql.Tx -} - -var _ dbQuerier = new(TxDB) -var _ txEnder = new(TxDB) - -func (t *TxDB) Commit() error { - return t.tx.Commit() -} - -func (t *TxDB) Rollback() error { - return t.tx.Rollback() -} - -var _ dbQuerier = new(TxDB) -var _ txEnder = new(TxDB) - -func (t *TxDB) Prepare(query string) (*sql.Stmt, error) { - return t.PrepareContext(context.Background(), query) -} - -func (t *TxDB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) { - return t.tx.PrepareContext(ctx, query) -} - -func (t *TxDB) Exec(query string, args ...interface{}) (sql.Result, error) { - return t.ExecContext(context.Background(), query, args...) -} - -func (t *TxDB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { - return t.tx.ExecContext(ctx, query, args...) -} - -func (t *TxDB) Query(query string, args ...interface{}) (*sql.Rows, error) { - return t.QueryContext(context.Background(), query, args...) -} - -func (t *TxDB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { - return t.tx.QueryContext(ctx, query, args...) -} - -func (t *TxDB) QueryRow(query string, args ...interface{}) *sql.Row { - return t.QueryRowContext(context.Background(), query, args...) -} - -func (t *TxDB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { - return t.tx.QueryRowContext(ctx, query, args...) +func (d *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { + sd, err := d.getStmtDecorator(query) + if err != nil { + panic(err) + } + stmt := sd.getStmt() + defer sd.release() + return stmt.QueryRowContext(ctx, args) } type alias struct { - Name string - Driver DriverType - DriverName string - DataSource string - MaxIdleConns int - MaxOpenConns int - ConnMaxLifetime time.Duration - StmtCacheSize int - DB *DB - DbBaser dbBaser - TZ *time.Location - Engine string + Name string + Driver DriverType + DriverName string + DataSource string + MaxIdleConns int + MaxOpenConns int + DB *DB + DbBaser dbBaser + TZ *time.Location + Engine string } func detectTZ(al *alias) { @@ -338,53 +289,15 @@ func detectTZ(al *alias) { } } -func addAliasWthDB(aliasName, driverName string, db *sql.DB, params ...DBOption) (*alias, error) { - existErr := fmt.Errorf("DataBase alias name `%s` already registered, cannot reuse", aliasName) - if _, ok := dataBaseCache.get(aliasName); ok { - return nil, existErr - } - - al, err := newAliasWithDb(aliasName, driverName, db, params...) - if err != nil { - return nil, err - } - - if !dataBaseCache.add(aliasName, al) { - return nil, existErr - } - - return al, nil -} - -func newAliasWithDb(aliasName, driverName string, db *sql.DB, params ...DBOption) (*alias, error) { - - al := &alias{} - al.DB = &DB{ - RWMutex: new(sync.RWMutex), - DB: db, - } - - for _, p := range params { - p(al) - } - - var stmtCache *lru.Cache - var stmtCacheSize int - - if al.StmtCacheSize > 0 { - _stmtCache, errC := newStmtDecoratorLruWithEvict(al.StmtCacheSize) - if errC != nil { - return nil, errC - } else { - stmtCache = _stmtCache - stmtCacheSize = al.StmtCacheSize - } - } - +func addAliasWthDB(aliasName, driverName string, db *sql.DB) (*alias, error) { + al := new(alias) al.Name = aliasName al.DriverName = driverName - al.DB.stmtDecorators = stmtCache - al.DB.stmtDecoratorsLimit = stmtCacheSize + al.DB = &DB{ + RWMutex: new(sync.RWMutex), + DB: db, + stmtDecorators: newStmtDecoratorLruWithEvict(), + } if dr, ok := drivers[driverName]; ok { al.DbBaser = dbBasers[dr] @@ -398,50 +311,21 @@ func newAliasWithDb(aliasName, driverName string, db *sql.DB, params ...DBOption return nil, fmt.Errorf("register db Ping `%s`, %s", aliasName, err.Error()) } - detectTZ(al) + if !dataBaseCache.add(aliasName, al) { + return nil, fmt.Errorf("DataBase alias name `%s` already registered, cannot reuse", aliasName) + } return al, nil } -// SetMaxIdleConns Change the max idle conns for *sql.DB, use specify database alias name -// Deprecated you should not use this, we will remove it in the future -func SetMaxIdleConns(aliasName string, maxIdleConns int) { - al := getDbAlias(aliasName) - al.SetMaxIdleConns(maxIdleConns) -} - -// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name -// Deprecated you should not use this, we will remove it in the future -func SetMaxOpenConns(aliasName string, maxOpenConns int) { - al := getDbAlias(aliasName) - al.SetMaxIdleConns(maxOpenConns) -} - -// SetMaxIdleConns Change the max idle conns for *sql.DB, use specify database alias name -func (al *alias) SetMaxIdleConns(maxIdleConns int) { - al.MaxIdleConns = maxIdleConns - al.DB.DB.SetMaxIdleConns(maxIdleConns) -} - -// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name -func (al *alias) SetMaxOpenConns(maxOpenConns int) { - al.MaxOpenConns = maxOpenConns - al.DB.DB.SetMaxOpenConns(maxOpenConns) -} - -func (al *alias) SetConnMaxLifetime(lifeTime time.Duration) { - al.ConnMaxLifetime = lifeTime - al.DB.DB.SetConnMaxLifetime(lifeTime) -} - // AddAliasWthDB add a aliasName for the drivename -func AddAliasWthDB(aliasName, driverName string, db *sql.DB, params ...DBOption) error { - _, err := addAliasWthDB(aliasName, driverName, db, params...) +func AddAliasWthDB(aliasName, driverName string, db *sql.DB) error { + _, err := addAliasWthDB(aliasName, driverName, db) return err } // RegisterDataBase Setting the database connect params. Use the database driver self dataSource args. -func RegisterDataBase(aliasName, driverName, dataSource string, params ...DBOption) error { +func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) error { var ( err error db *sql.DB @@ -454,13 +338,24 @@ func RegisterDataBase(aliasName, driverName, dataSource string, params ...DBOpti goto end } - al, err = addAliasWthDB(aliasName, driverName, db, params...) + al, err = addAliasWthDB(aliasName, driverName, db) if err != nil { goto end } al.DataSource = dataSource + detectTZ(al) + + for i, v := range params { + switch i { + case 0: + SetMaxIdleConns(al.Name, v) + case 1: + SetMaxOpenConns(al.Name, v) + } + } + end: if err != nil { if db != nil { @@ -494,6 +389,24 @@ func SetDataBaseTZ(aliasName string, tz *time.Location) error { return nil } +// SetMaxIdleConns Change the max idle conns for *sql.DB, use specify database alias name +func SetMaxIdleConns(aliasName string, maxIdleConns int) { + al := getDbAlias(aliasName) + al.MaxIdleConns = maxIdleConns + al.DB.DB.SetMaxIdleConns(maxIdleConns) +} + +// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name +func SetMaxOpenConns(aliasName string, maxOpenConns int) { + al := getDbAlias(aliasName) + al.MaxOpenConns = maxOpenConns + al.DB.DB.SetMaxOpenConns(maxOpenConns) + // for tip go 1.2 + if fun := reflect.ValueOf(al.DB).MethodByName("SetMaxOpenConns"); fun.IsValid() { + fun.Call([]reflect.Value{reflect.ValueOf(maxOpenConns)}) + } +} + // GetDB Get *sql.DB from registered database by db alias name. // Use "default" as alias name if you not set. func GetDB(aliasNames ...string) (*sql.DB, error) { @@ -511,7 +424,8 @@ func GetDB(aliasNames ...string) (*sql.DB, error) { } type stmtDecorator struct { - wg sync.WaitGroup + wg sync.WaitGroup + lastUse int64 stmt *sql.Stmt } @@ -519,19 +433,16 @@ func (s *stmtDecorator) getStmt() *sql.Stmt { return s.stmt } -// acquire will add one -// since this method will be used inside read lock scope, -// so we can not do more things here -// we should think about refactor this func (s *stmtDecorator) acquire() { s.wg.Add(1) + s.lastUse = time.Now().Unix() } func (s *stmtDecorator) release() { s.wg.Done() } -// garbage recycle for stmt +//garbage recycle for stmt func (s *stmtDecorator) destroy() { go func() { s.wg.Wait() @@ -542,45 +453,13 @@ func (s *stmtDecorator) destroy() { func newStmtDecorator(sqlStmt *sql.Stmt) *stmtDecorator { return &stmtDecorator{ stmt: sqlStmt, + lastUse: time.Now().Unix(), } } -func newStmtDecoratorLruWithEvict(cacheSize int) (*lru.Cache, error) { - cache, err := lru.NewWithEvict(cacheSize, func(key interface{}, value interface{}) { +func newStmtDecoratorLruWithEvict() *lru.Cache { + cache, _ := lru.NewWithEvict(1000, func(key interface{}, value interface{}) { value.(*stmtDecorator).destroy() }) - if err != nil { - return nil, err - } - return cache, nil -} - -type DBOption func(al *alias) - -// MaxIdleConnections return a hint about MaxIdleConnections -func MaxIdleConnections(maxIdleConn int) DBOption { - return func(al *alias) { - al.SetMaxIdleConns(maxIdleConn) - } -} - -// MaxOpenConnections return a hint about MaxOpenConnections -func MaxOpenConnections(maxOpenConn int) DBOption { - return func(al *alias) { - al.SetMaxOpenConns(maxOpenConn) - } -} - -// ConnMaxLifetime return a hint about ConnMaxLifetime -func ConnMaxLifetime(v time.Duration) DBOption { - return func(al *alias) { - al.SetConnMaxLifetime(v) - } -} - -// MaxStmtCacheSize return a hint about MaxStmtCacheSize -func MaxStmtCacheSize(v int) DBOption { - return func(al *alias) { - al.StmtCacheSize = v - } + return cache } diff --git a/client/orm/db_mysql.go b/orm/db_mysql.go similarity index 79% rename from client/orm/db_mysql.go rename to orm/db_mysql.go index f674ab2b..6e99058e 100644 --- a/client/orm/db_mysql.go +++ b/orm/db_mysql.go @@ -22,11 +22,10 @@ import ( // mysql operators. var mysqlOperators = map[string]string{ - "exact": "= ?", - "iexact": "LIKE ?", - "strictexact": "= BINARY ?", - "contains": "LIKE BINARY ?", - "icontains": "LIKE ?", + "exact": "= ?", + "iexact": "LIKE ?", + "contains": "LIKE BINARY ?", + "icontains": "LIKE ?", // "regex": "REGEXP BINARY ?", // "iregex": "REGEXP ?", "gt": "> ?", @@ -43,25 +42,24 @@ var mysqlOperators = map[string]string{ // mysql column field types. var mysqlTypes = map[string]string{ - "auto": "AUTO_INCREMENT NOT NULL PRIMARY KEY", - "pk": "NOT NULL PRIMARY KEY", - "bool": "bool", - "string": "varchar(%d)", - "string-char": "char(%d)", - "string-text": "longtext", - "time.Time-date": "date", - "time.Time": "datetime", - "int8": "tinyint", - "int16": "smallint", - "int32": "integer", - "int64": "bigint", - "uint8": "tinyint unsigned", - "uint16": "smallint unsigned", - "uint32": "integer unsigned", - "uint64": "bigint unsigned", - "float64": "double precision", - "float64-decimal": "numeric(%d, %d)", - "time.Time-precision": "datetime(%d)", + "auto": "AUTO_INCREMENT NOT NULL PRIMARY KEY", + "pk": "NOT NULL PRIMARY KEY", + "bool": "bool", + "string": "varchar(%d)", + "string-char": "char(%d)", + "string-text": "longtext", + "time.Time-date": "date", + "time.Time": "datetime", + "int8": "tinyint", + "int16": "smallint", + "int32": "integer", + "int64": "bigint", + "uint8": "tinyint unsigned", + "uint16": "smallint unsigned", + "uint32": "integer unsigned", + "uint64": "bigint unsigned", + "float64": "double precision", + "float64-decimal": "numeric(%d, %d)", } // mysql dbBaser implementation. @@ -166,14 +164,7 @@ func (d *dbBaseMysql) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Val if isMulti { return res.RowsAffected() } - - lastInsertId, err := res.LastInsertId() - if err != nil { - DebugLog.Println(ErrLastInsertIdUnavailable, ':', err) - return lastInsertId, ErrLastInsertIdUnavailable - } else { - return lastInsertId, nil - } + return res.LastInsertId() } return 0, err } diff --git a/client/orm/db_oracle.go b/orm/db_oracle.go similarity index 67% rename from client/orm/db_oracle.go rename to orm/db_oracle.go index cb0d5052..5d121f83 100644 --- a/client/orm/db_oracle.go +++ b/orm/db_oracle.go @@ -17,8 +17,6 @@ package orm import ( "fmt" "strings" - - "github.com/astaxie/beego/client/orm/hints" ) // oracle operators. @@ -33,24 +31,23 @@ var oracleOperators = map[string]string{ // oracle column field types. var oracleTypes = map[string]string{ - "pk": "NOT NULL PRIMARY KEY", - "bool": "bool", - "string": "VARCHAR2(%d)", - "string-char": "CHAR(%d)", - "string-text": "VARCHAR2(%d)", - "time.Time-date": "DATE", - "time.Time": "TIMESTAMP", - "int8": "INTEGER", - "int16": "INTEGER", - "int32": "INTEGER", - "int64": "INTEGER", - "uint8": "INTEGER", - "uint16": "INTEGER", - "uint32": "INTEGER", - "uint64": "INTEGER", - "float64": "NUMBER", - "float64-decimal": "NUMBER(%d, %d)", - "time.Time-precision": "TIMESTAMP(%d)", + "pk": "NOT NULL PRIMARY KEY", + "bool": "bool", + "string": "VARCHAR2(%d)", + "string-char": "CHAR(%d)", + "string-text": "VARCHAR2(%d)", + "time.Time-date": "DATE", + "time.Time": "TIMESTAMP", + "int8": "INTEGER", + "int16": "INTEGER", + "int32": "INTEGER", + "int64": "INTEGER", + "uint8": "INTEGER", + "uint16": "INTEGER", + "uint32": "INTEGER", + "uint64": "INTEGER", + "float64": "NUMBER", + "float64-decimal": "NUMBER(%d, %d)", } // oracle dbBaser @@ -99,29 +96,6 @@ func (d *dbBaseOracle) IndexExists(db dbQuerier, table string, name string) bool return cnt > 0 } -func (d *dbBaseOracle) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string { - var s []string - Q := d.TableQuote() - for _, index := range indexes { - tmp := fmt.Sprintf(`%s%s%s`, Q, index, Q) - s = append(s, tmp) - } - - var hint string - - switch useIndex { - case hints.KeyUseIndex, hints.KeyForceIndex: - hint = `INDEX` - case hints.KeyIgnoreIndex: - hint = `NO_INDEX` - default: - DebugLog.Println("[WARN] Not a valid specifying action, so that action is ignored") - return `` - } - - return fmt.Sprintf(` /*+ %s(%s %s)*/ `, hint, tableName, strings.Join(s, `,`)) -} - // execute insert sql with given struct and given values. // insert the given values, not the field values in struct. func (d *dbBaseOracle) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []string, values []interface{}) (int64, error) { @@ -152,14 +126,7 @@ func (d *dbBaseOracle) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, nam if isMulti { return res.RowsAffected() } - - lastInsertId, err := res.LastInsertId() - if err != nil { - DebugLog.Println(ErrLastInsertIdUnavailable, ':', err) - return lastInsertId, ErrLastInsertIdUnavailable - } else { - return lastInsertId, nil - } + return res.LastInsertId() } return 0, err } diff --git a/client/orm/db_postgres.go b/orm/db_postgres.go similarity index 78% rename from client/orm/db_postgres.go rename to orm/db_postgres.go index 12431d6e..c488fb38 100644 --- a/client/orm/db_postgres.go +++ b/orm/db_postgres.go @@ -39,27 +39,26 @@ var postgresOperators = map[string]string{ // postgresql column field types. var postgresTypes = map[string]string{ - "auto": "serial NOT NULL PRIMARY KEY", - "pk": "NOT NULL PRIMARY KEY", - "bool": "bool", - "string": "varchar(%d)", - "string-char": "char(%d)", - "string-text": "text", - "time.Time-date": "date", - "time.Time": "timestamp with time zone", - "int8": `smallint CHECK("%COL%" >= -127 AND "%COL%" <= 128)`, - "int16": "smallint", - "int32": "integer", - "int64": "bigint", - "uint8": `smallint CHECK("%COL%" >= 0 AND "%COL%" <= 255)`, - "uint16": `integer CHECK("%COL%" >= 0)`, - "uint32": `bigint CHECK("%COL%" >= 0)`, - "uint64": `bigint CHECK("%COL%" >= 0)`, - "float64": "double precision", - "float64-decimal": "numeric(%d, %d)", - "json": "json", - "jsonb": "jsonb", - "time.Time-precision": "timestamp(%d) with time zone", + "auto": "serial NOT NULL PRIMARY KEY", + "pk": "NOT NULL PRIMARY KEY", + "bool": "bool", + "string": "varchar(%d)", + "string-char": "char(%d)", + "string-text": "text", + "time.Time-date": "date", + "time.Time": "timestamp with time zone", + "int8": `smallint CHECK("%COL%" >= -127 AND "%COL%" <= 128)`, + "int16": "smallint", + "int32": "integer", + "int64": "bigint", + "uint8": `smallint CHECK("%COL%" >= 0 AND "%COL%" <= 255)`, + "uint16": `integer CHECK("%COL%" >= 0)`, + "uint32": `bigint CHECK("%COL%" >= 0)`, + "uint64": `bigint CHECK("%COL%" >= 0)`, + "float64": "double precision", + "float64-decimal": "numeric(%d, %d)", + "json": "json", + "jsonb": "jsonb", } // postgresql dbBaser. @@ -182,12 +181,6 @@ func (d *dbBasePostgres) IndexExists(db dbQuerier, table string, name string) bo return cnt > 0 } -// GenerateSpecifyIndex return a specifying index clause -func (d *dbBasePostgres) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string { - DebugLog.Println("[WARN] Not support any specifying index action, so that action is ignored") - return `` -} - // create new postgresql dbBaser. func newdbBasePostgres() dbBaser { b := new(dbBasePostgres) diff --git a/client/orm/db_sqlite.go b/orm/db_sqlite.go similarity index 73% rename from client/orm/db_sqlite.go rename to orm/db_sqlite.go index 961f2535..1d62ee34 100644 --- a/client/orm/db_sqlite.go +++ b/orm/db_sqlite.go @@ -18,10 +18,7 @@ import ( "database/sql" "fmt" "reflect" - "strings" "time" - - "github.com/astaxie/beego/client/orm/hints" ) // sqlite operators. @@ -44,25 +41,24 @@ var sqliteOperators = map[string]string{ // sqlite column types. var sqliteTypes = map[string]string{ - "auto": "integer NOT NULL PRIMARY KEY AUTOINCREMENT", - "pk": "NOT NULL PRIMARY KEY", - "bool": "bool", - "string": "varchar(%d)", - "string-char": "character(%d)", - "string-text": "text", - "time.Time-date": "date", - "time.Time": "datetime", - "time.Time-precision": "datetime(%d)", - "int8": "tinyint", - "int16": "smallint", - "int32": "integer", - "int64": "bigint", - "uint8": "tinyint unsigned", - "uint16": "smallint unsigned", - "uint32": "integer unsigned", - "uint64": "bigint unsigned", - "float64": "real", - "float64-decimal": "decimal", + "auto": "integer NOT NULL PRIMARY KEY AUTOINCREMENT", + "pk": "NOT NULL PRIMARY KEY", + "bool": "bool", + "string": "varchar(%d)", + "string-char": "character(%d)", + "string-text": "text", + "time.Time-date": "date", + "time.Time": "datetime", + "int8": "tinyint", + "int16": "smallint", + "int32": "integer", + "int64": "bigint", + "uint8": "tinyint unsigned", + "uint16": "smallint unsigned", + "uint32": "integer unsigned", + "uint64": "bigint unsigned", + "float64": "real", + "float64-decimal": "decimal", } // sqlite dbBaser. @@ -157,24 +153,6 @@ func (d *dbBaseSqlite) IndexExists(db dbQuerier, table string, name string) bool return false } -// GenerateSpecifyIndex return a specifying index clause -func (d *dbBaseSqlite) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string { - var s []string - Q := d.TableQuote() - for _, index := range indexes { - tmp := fmt.Sprintf(`%s%s%s`, Q, index, Q) - s = append(s, tmp) - } - - switch useIndex { - case hints.KeyUseIndex, hints.KeyForceIndex: - return fmt.Sprintf(` INDEXED BY %s `, strings.Join(s, `,`)) - default: - DebugLog.Println("[WARN] Not a valid specifying action, so that action is ignored") - return `` - } -} - // create new sqlite dbBaser. func newdbBaseSqlite() dbBaser { b := new(dbBaseSqlite) diff --git a/client/orm/db_tables.go b/orm/db_tables.go similarity index 97% rename from client/orm/db_tables.go rename to orm/db_tables.go index 5fd472d1..4b21a6fc 100644 --- a/client/orm/db_tables.go +++ b/orm/db_tables.go @@ -472,15 +472,6 @@ func (t *dbTables) getLimitSQL(mi *modelInfo, offset int64, limit int64) (limits return } -// getIndexSql generate index sql. -func (t *dbTables) getIndexSql(tableName string, useIndex int, indexes []string) (clause string) { - if len(indexes) == 0 { - return - } - - return t.base.GenerateSpecifyIndex(tableName, useIndex, indexes) -} - // crete new tables collection. func newDbTables(mi *modelInfo, base dbBaser) *dbTables { tables := &dbTables{} diff --git a/client/orm/db_tidb.go b/orm/db_tidb.go similarity index 100% rename from client/orm/db_tidb.go rename to orm/db_tidb.go diff --git a/client/orm/db_utils.go b/orm/db_utils.go similarity index 100% rename from client/orm/db_utils.go rename to orm/db_utils.go diff --git a/orm/models.go b/orm/models.go new file mode 100644 index 00000000..4776bcba --- /dev/null +++ b/orm/models.go @@ -0,0 +1,99 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "sync" +) + +const ( + odCascade = "cascade" + odSetNULL = "set_null" + odSetDefault = "set_default" + odDoNothing = "do_nothing" + defaultStructTagName = "orm" + defaultStructTagDelim = ";" +) + +var ( + modelCache = &_modelCache{ + cache: make(map[string]*modelInfo), + cacheByFullName: make(map[string]*modelInfo), + } +) + +// model info collection +type _modelCache struct { + sync.RWMutex // only used outsite for bootStrap + orders []string + cache map[string]*modelInfo + cacheByFullName map[string]*modelInfo + done bool +} + +// get all model info +func (mc *_modelCache) all() map[string]*modelInfo { + m := make(map[string]*modelInfo, len(mc.cache)) + for k, v := range mc.cache { + m[k] = v + } + return m +} + +// get ordered model info +func (mc *_modelCache) allOrdered() []*modelInfo { + m := make([]*modelInfo, 0, len(mc.orders)) + for _, table := range mc.orders { + m = append(m, mc.cache[table]) + } + return m +} + +// get model info by table name +func (mc *_modelCache) get(table string) (mi *modelInfo, ok bool) { + mi, ok = mc.cache[table] + return +} + +// get model info by full name +func (mc *_modelCache) getByFullName(name string) (mi *modelInfo, ok bool) { + mi, ok = mc.cacheByFullName[name] + return +} + +// set model info to collection +func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo { + mii := mc.cache[table] + mc.cache[table] = mi + mc.cacheByFullName[mi.fullName] = mi + if mii == nil { + mc.orders = append(mc.orders, table) + } + return mii +} + +// clean all model info. +func (mc *_modelCache) clean() { + mc.orders = make([]string, 0) + mc.cache = make(map[string]*modelInfo) + mc.cacheByFullName = make(map[string]*modelInfo) + mc.done = false +} + +// ResetModelCache Clean model cache. Then you can re-RegisterModel. +// Common use this api for test case. +func ResetModelCache() { + modelCache.clean() +} diff --git a/orm/models_boot.go b/orm/models_boot.go new file mode 100644 index 00000000..8c56b3c4 --- /dev/null +++ b/orm/models_boot.go @@ -0,0 +1,347 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "fmt" + "os" + "reflect" + "runtime/debug" + "strings" +) + +// register models. +// PrefixOrSuffix means table name prefix or suffix. +// isPrefix whether the prefix is prefix or suffix +func registerModel(PrefixOrSuffix string, model interface{}, isPrefix bool) { + val := reflect.ValueOf(model) + typ := reflect.Indirect(val).Type() + + if val.Kind() != reflect.Ptr { + panic(fmt.Errorf(" cannot use non-ptr model struct `%s`", getFullName(typ))) + } + // For this case: + // u := &User{} + // registerModel(&u) + if typ.Kind() == reflect.Ptr { + panic(fmt.Errorf(" only allow ptr model struct, it looks you use two reference to the struct `%s`", typ)) + } + + table := getTableName(val) + + if PrefixOrSuffix != "" { + if isPrefix { + table = PrefixOrSuffix + table + } else { + table = table + PrefixOrSuffix + } + } + // models's fullname is pkgpath + struct name + name := getFullName(typ) + if _, ok := modelCache.getByFullName(name); ok { + fmt.Printf(" model `%s` repeat register, must be unique\n", name) + os.Exit(2) + } + + if _, ok := modelCache.get(table); ok { + fmt.Printf(" table name `%s` repeat register, must be unique\n", table) + os.Exit(2) + } + + mi := newModelInfo(val) + if mi.fields.pk == nil { + outFor: + for _, fi := range mi.fields.fieldsDB { + if strings.ToLower(fi.name) == "id" { + switch fi.addrValue.Elem().Kind() { + case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64: + fi.auto = true + fi.pk = true + mi.fields.pk = fi + break outFor + } + } + } + + if mi.fields.pk == nil { + fmt.Printf(" `%s` needs a primary key field, default is to use 'id' if not set\n", name) + os.Exit(2) + } + + } + + mi.table = table + mi.pkg = typ.PkgPath() + mi.model = model + mi.manual = true + + modelCache.set(table, mi) +} + +// bootstrap models +func bootStrap() { + if modelCache.done { + return + } + var ( + err error + models map[string]*modelInfo + ) + if dataBaseCache.getDefault() == nil { + err = fmt.Errorf("must have one register DataBase alias named `default`") + goto end + } + + // set rel and reverse model + // RelManyToMany set the relTable + models = modelCache.all() + for _, mi := range models { + for _, fi := range mi.fields.columns { + if fi.rel || fi.reverse { + elm := fi.addrValue.Type().Elem() + if fi.fieldType == RelReverseMany || fi.fieldType == RelManyToMany { + elm = elm.Elem() + } + // check the rel or reverse model already register + name := getFullName(elm) + mii, ok := modelCache.getByFullName(name) + if !ok || mii.pkg != elm.PkgPath() { + err = fmt.Errorf("can not find rel in field `%s`, `%s` may be miss register", fi.fullName, elm.String()) + goto end + } + fi.relModelInfo = mii + + switch fi.fieldType { + case RelManyToMany: + if fi.relThrough != "" { + if i := strings.LastIndex(fi.relThrough, "."); i != -1 && len(fi.relThrough) > (i+1) { + pn := fi.relThrough[:i] + rmi, ok := modelCache.getByFullName(fi.relThrough) + if !ok || pn != rmi.pkg { + err = fmt.Errorf("field `%s` wrong rel_through value `%s` cannot find table", fi.fullName, fi.relThrough) + goto end + } + fi.relThroughModelInfo = rmi + fi.relTable = rmi.table + } else { + err = fmt.Errorf("field `%s` wrong rel_through value `%s`", fi.fullName, fi.relThrough) + goto end + } + } else { + i := newM2MModelInfo(mi, mii) + if fi.relTable != "" { + i.table = fi.relTable + } + if v := modelCache.set(i.table, i); v != nil { + err = fmt.Errorf("the rel table name `%s` already registered, cannot be use, please change one", fi.relTable) + goto end + } + fi.relTable = i.table + fi.relThroughModelInfo = i + } + + fi.relThroughModelInfo.isThrough = true + } + } + } + } + + // check the rel filed while the relModelInfo also has filed point to current model + // if not exist, add a new field to the relModelInfo + models = modelCache.all() + for _, mi := range models { + for _, fi := range mi.fields.fieldsRel { + switch fi.fieldType { + case RelForeignKey, RelOneToOne, RelManyToMany: + inModel := false + for _, ffi := range fi.relModelInfo.fields.fieldsReverse { + if ffi.relModelInfo == mi { + inModel = true + break + } + } + if !inModel { + rmi := fi.relModelInfo + ffi := new(fieldInfo) + ffi.name = mi.name + ffi.column = ffi.name + ffi.fullName = rmi.fullName + "." + ffi.name + ffi.reverse = true + ffi.relModelInfo = mi + ffi.mi = rmi + if fi.fieldType == RelOneToOne { + ffi.fieldType = RelReverseOne + } else { + ffi.fieldType = RelReverseMany + } + if !rmi.fields.Add(ffi) { + added := false + for cnt := 0; cnt < 5; cnt++ { + ffi.name = fmt.Sprintf("%s%d", mi.name, cnt) + ffi.column = ffi.name + ffi.fullName = rmi.fullName + "." + ffi.name + if added = rmi.fields.Add(ffi); added { + break + } + } + if !added { + panic(fmt.Errorf("cannot generate auto reverse field info `%s` to `%s`", fi.fullName, ffi.fullName)) + } + } + } + } + } + } + + models = modelCache.all() + for _, mi := range models { + for _, fi := range mi.fields.fieldsRel { + switch fi.fieldType { + case RelManyToMany: + for _, ffi := range fi.relThroughModelInfo.fields.fieldsRel { + switch ffi.fieldType { + case RelOneToOne, RelForeignKey: + if ffi.relModelInfo == fi.relModelInfo { + fi.reverseFieldInfoTwo = ffi + } + if ffi.relModelInfo == mi { + fi.reverseField = ffi.name + fi.reverseFieldInfo = ffi + } + } + } + if fi.reverseFieldInfoTwo == nil { + err = fmt.Errorf("can not find m2m field for m2m model `%s`, ensure your m2m model defined correct", + fi.relThroughModelInfo.fullName) + goto end + } + } + } + } + + models = modelCache.all() + for _, mi := range models { + for _, fi := range mi.fields.fieldsReverse { + switch fi.fieldType { + case RelReverseOne: + found := false + mForA: + for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelOneToOne] { + if ffi.relModelInfo == mi { + found = true + fi.reverseField = ffi.name + fi.reverseFieldInfo = ffi + + ffi.reverseField = fi.name + ffi.reverseFieldInfo = fi + break mForA + } + } + if !found { + err = fmt.Errorf("reverse field `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName) + goto end + } + case RelReverseMany: + found := false + mForB: + for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelForeignKey] { + if ffi.relModelInfo == mi { + found = true + fi.reverseField = ffi.name + fi.reverseFieldInfo = ffi + + ffi.reverseField = fi.name + ffi.reverseFieldInfo = fi + + break mForB + } + } + if !found { + mForC: + for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelManyToMany] { + conditions := fi.relThrough != "" && fi.relThrough == ffi.relThrough || + fi.relTable != "" && fi.relTable == ffi.relTable || + fi.relThrough == "" && fi.relTable == "" + if ffi.relModelInfo == mi && conditions { + found = true + + fi.reverseField = ffi.reverseFieldInfoTwo.name + fi.reverseFieldInfo = ffi.reverseFieldInfoTwo + fi.relThroughModelInfo = ffi.relThroughModelInfo + fi.reverseFieldInfoTwo = ffi.reverseFieldInfo + fi.reverseFieldInfoM2M = ffi + ffi.reverseFieldInfoM2M = fi + + break mForC + } + } + } + if !found { + err = fmt.Errorf("reverse field for `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName) + goto end + } + } + } + } + +end: + if err != nil { + fmt.Println(err) + debug.PrintStack() + os.Exit(2) + } +} + +// RegisterModel register models +func RegisterModel(models ...interface{}) { + if modelCache.done { + panic(fmt.Errorf("RegisterModel must be run before BootStrap")) + } + RegisterModelWithPrefix("", models...) +} + +// RegisterModelWithPrefix register models with a prefix +func RegisterModelWithPrefix(prefix string, models ...interface{}) { + if modelCache.done { + panic(fmt.Errorf("RegisterModelWithPrefix must be run before BootStrap")) + } + + for _, model := range models { + registerModel(prefix, model, true) + } +} + +// RegisterModelWithSuffix register models with a suffix +func RegisterModelWithSuffix(suffix string, models ...interface{}) { + if modelCache.done { + panic(fmt.Errorf("RegisterModelWithSuffix must be run before BootStrap")) + } + + for _, model := range models { + registerModel(suffix, model, false) + } +} + +// BootStrap bootstrap models. +// make all model parsed and can not add more models +func BootStrap() { + modelCache.Lock() + defer modelCache.Unlock() + if modelCache.done { + return + } + bootStrap() + modelCache.done = true +} diff --git a/client/orm/models_fields.go b/orm/models_fields.go similarity index 100% rename from client/orm/models_fields.go rename to orm/models_fields.go diff --git a/client/orm/models_info_f.go b/orm/models_info_f.go similarity index 97% rename from client/orm/models_info_f.go rename to orm/models_info_f.go index 7152fada..7044b0bd 100644 --- a/client/orm/models_info_f.go +++ b/orm/models_info_f.go @@ -137,7 +137,6 @@ type fieldInfo struct { isFielder bool // implement Fielder interface onDelete string description string - timePrecision *int } // new field info @@ -178,7 +177,7 @@ func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mN decimals := tags["decimals"] size := tags["size"] onDelete := tags["on_delete"] - precision := tags["precision"] + initial.Clear() if v, ok := tags["default"]; ok { initial.Set(v) @@ -378,18 +377,6 @@ checkType: fi.index = false fi.unique = false case TypeTimeField, TypeDateField, TypeDateTimeField: - if fieldType == TypeDateTimeField { - if precision != "" { - v, e := StrTo(precision).Int() - if e != nil { - err = fmt.Errorf("convert %s to int error:%v", precision, e) - } else { - fi.timePrecision = &v - } - } - - } - if attrs["auto_now"] { fi.autoNow = true } else if attrs["auto_now_add"] { diff --git a/client/orm/models_info_m.go b/orm/models_info_m.go similarity index 98% rename from client/orm/models_info_m.go rename to orm/models_info_m.go index c450239c..a4d733b6 100644 --- a/client/orm/models_info_m.go +++ b/orm/models_info_m.go @@ -29,7 +29,7 @@ type modelInfo struct { model interface{} fields *fields manual bool - addrField reflect.Value // store the original struct value + addrField reflect.Value //store the original struct value uniques []string isThrough bool } diff --git a/client/orm/models_test.go b/orm/models_test.go similarity index 66% rename from client/orm/models_test.go rename to orm/models_test.go index d5aa2fa0..e3a635f2 100644 --- a/client/orm/models_test.go +++ b/orm/models_test.go @@ -53,24 +53,18 @@ func (e *SliceStringField) FieldType() int { } func (e *SliceStringField) SetRaw(value interface{}) error { - f := func(str string) { - if len(str) > 0 { - parts := strings.Split(str, ",") + switch d := value.(type) { + case []string: + e.Set(d) + case string: + if len(d) > 0 { + parts := strings.Split(d, ",") v := make([]string, 0, len(parts)) for _, p := range parts { v = append(v, strings.TrimSpace(p)) } e.Set(v) } - } - - switch d := value.(type) { - case []string: - e.Set(d) - case string: - f(d) - case []byte: - f(string(d)) default: return fmt.Errorf(" unknown value `%v`", value) } @@ -102,8 +96,6 @@ func (e *JSONFieldTest) SetRaw(value interface{}) error { switch d := value.(type) { case string: return json.Unmarshal([]byte(d), e) - case []byte: - return json.Unmarshal(d, e) default: return fmt.Errorf(" unknown value `%v`", value) } @@ -143,56 +135,55 @@ type Data struct { } type DataNull struct { - ID int `orm:"column(id)"` - Boolean bool `orm:"null"` - Char string `orm:"null;size(50)"` - Text string `orm:"null;type(text)"` - JSON string `orm:"type(json);null"` - Jsonb string `orm:"type(jsonb);null"` - Time time.Time `orm:"null;type(time)"` - Date time.Time `orm:"null;type(date)"` - DateTime time.Time `orm:"null;column(datetime)"` - DateTimePrecision time.Time `orm:"null;type(datetime);precision(4)"` - Byte byte `orm:"null"` - Rune rune `orm:"null"` - Int int `orm:"null"` - Int8 int8 `orm:"null"` - Int16 int16 `orm:"null"` - Int32 int32 `orm:"null"` - Int64 int64 `orm:"null"` - Uint uint `orm:"null"` - Uint8 uint8 `orm:"null"` - Uint16 uint16 `orm:"null"` - Uint32 uint32 `orm:"null"` - Uint64 uint64 `orm:"null"` - Float32 float32 `orm:"null"` - Float64 float64 `orm:"null"` - Decimal float64 `orm:"digits(8);decimals(4);null"` - NullString sql.NullString `orm:"null"` - NullBool sql.NullBool `orm:"null"` - NullFloat64 sql.NullFloat64 `orm:"null"` - NullInt64 sql.NullInt64 `orm:"null"` - BooleanPtr *bool `orm:"null"` - CharPtr *string `orm:"null;size(50)"` - TextPtr *string `orm:"null;type(text)"` - BytePtr *byte `orm:"null"` - RunePtr *rune `orm:"null"` - IntPtr *int `orm:"null"` - Int8Ptr *int8 `orm:"null"` - Int16Ptr *int16 `orm:"null"` - Int32Ptr *int32 `orm:"null"` - Int64Ptr *int64 `orm:"null"` - UintPtr *uint `orm:"null"` - Uint8Ptr *uint8 `orm:"null"` - Uint16Ptr *uint16 `orm:"null"` - Uint32Ptr *uint32 `orm:"null"` - Uint64Ptr *uint64 `orm:"null"` - Float32Ptr *float32 `orm:"null"` - Float64Ptr *float64 `orm:"null"` - DecimalPtr *float64 `orm:"digits(8);decimals(4);null"` - TimePtr *time.Time `orm:"null;type(time)"` - DatePtr *time.Time `orm:"null;type(date)"` - DateTimePtr *time.Time `orm:"null"` + ID int `orm:"column(id)"` + Boolean bool `orm:"null"` + Char string `orm:"null;size(50)"` + Text string `orm:"null;type(text)"` + JSON string `orm:"type(json);null"` + Jsonb string `orm:"type(jsonb);null"` + Time time.Time `orm:"null;type(time)"` + Date time.Time `orm:"null;type(date)"` + DateTime time.Time `orm:"null;column(datetime)"` + Byte byte `orm:"null"` + Rune rune `orm:"null"` + Int int `orm:"null"` + Int8 int8 `orm:"null"` + Int16 int16 `orm:"null"` + Int32 int32 `orm:"null"` + Int64 int64 `orm:"null"` + Uint uint `orm:"null"` + Uint8 uint8 `orm:"null"` + Uint16 uint16 `orm:"null"` + Uint32 uint32 `orm:"null"` + Uint64 uint64 `orm:"null"` + Float32 float32 `orm:"null"` + Float64 float64 `orm:"null"` + Decimal float64 `orm:"digits(8);decimals(4);null"` + NullString sql.NullString `orm:"null"` + NullBool sql.NullBool `orm:"null"` + NullFloat64 sql.NullFloat64 `orm:"null"` + NullInt64 sql.NullInt64 `orm:"null"` + BooleanPtr *bool `orm:"null"` + CharPtr *string `orm:"null;size(50)"` + TextPtr *string `orm:"null;type(text)"` + BytePtr *byte `orm:"null"` + RunePtr *rune `orm:"null"` + IntPtr *int `orm:"null"` + Int8Ptr *int8 `orm:"null"` + Int16Ptr *int16 `orm:"null"` + Int32Ptr *int32 `orm:"null"` + Int64Ptr *int64 `orm:"null"` + UintPtr *uint `orm:"null"` + Uint8Ptr *uint8 `orm:"null"` + Uint16Ptr *uint16 `orm:"null"` + Uint32Ptr *uint32 `orm:"null"` + Uint64Ptr *uint64 `orm:"null"` + Float32Ptr *float32 `orm:"null"` + Float64Ptr *float64 `orm:"null"` + DecimalPtr *float64 `orm:"digits(8);decimals(4);null"` + TimePtr *time.Time `orm:"null;type(time)"` + DatePtr *time.Time `orm:"null;type(date)"` + DateTimePtr *time.Time `orm:"null"` } type String string @@ -240,21 +231,6 @@ type UserBig struct { Name string } -type TM struct { - ID int `orm:"column(id)"` - TMPrecision1 time.Time `orm:"type(datetime);precision(3)"` - TMPrecision2 time.Time `orm:"auto_now_add;type(datetime);precision(4)"` -} - -func (t *TM) TableName() string { - return "tm" -} - -func NewTM() *TM { - obj := new(TM) - return obj -} - type User struct { ID int `orm:"column(id)"` UserName string `orm:"size(30);unique"` @@ -311,14 +287,13 @@ func NewProfile() *Profile { } type Post struct { - ID int `orm:"column(id)"` - User *User `orm:"rel(fk)"` - Title string `orm:"size(60)"` - Content string `orm:"type(text)"` - Created time.Time `orm:"auto_now_add"` - Updated time.Time `orm:"auto_now"` - UpdatedPrecision time.Time `orm:"auto_now;type(datetime);precision(4)"` - Tags []*Tag `orm:"rel(m2m);rel_through(github.com/astaxie/beego/client/orm.PostTags)"` + ID int `orm:"column(id)"` + User *User `orm:"rel(fk)"` + Title string `orm:"size(60)"` + Content string `orm:"type(text)"` + Created time.Time `orm:"auto_now_add"` + Updated time.Time `orm:"auto_now"` + Tags []*Tag `orm:"rel(m2m);rel_through(github.com/astaxie/beego/orm.PostTags)"` } func (u *Post) TableIndex() [][]string { @@ -376,7 +351,7 @@ type Group struct { type Permission struct { ID int `orm:"column(id)"` Name string - Groups []*Group `orm:"rel(m2m);rel_through(github.com/astaxie/beego/client/orm.GroupPermissions)"` + Groups []*Group `orm:"rel(m2m);rel_through(github.com/astaxie/beego/orm.GroupPermissions)"` } type GroupPermissions struct { @@ -405,15 +380,6 @@ type InLine struct { Email string } -type Index struct { - // Common Fields - Id int `orm:"column(id)"` - - // Other Fields - F1 int `orm:"column(f1);index"` - F2 int `orm:"column(f2);index"` -} - func NewInLine() *InLine { return new(InLine) } @@ -445,11 +411,6 @@ type PtrPk struct { Positive bool } -type StrPk struct { - Id string `orm:"column(id);size(64);pk"` - Value string -} - var DBARGS = struct { Driver string Source string @@ -485,7 +446,7 @@ var ( usage: - go get -u github.com/astaxie/beego/client/orm + go get -u github.com/astaxie/beego/orm go get -u github.com/go-sql-driver/mysql go get -u github.com/mattn/go-sqlite3 go get -u github.com/lib/pq @@ -495,43 +456,38 @@ var ( mysql -u root -e 'create database orm_test;' export ORM_DRIVER=mysql export ORM_SOURCE="root:@/orm_test?charset=utf8" - go test -v github.com/astaxie/beego/client/orm + go test -v github.com/astaxie/beego/orm #### Sqlite3 export ORM_DRIVER=sqlite3 export ORM_SOURCE='file:memory_test?mode=memory' - go test -v github.com/astaxie/beego/client/orm + go test -v github.com/astaxie/beego/orm #### PostgreSQL psql -c 'create database orm_test;' -U postgres export ORM_DRIVER=postgres export ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable" - go test -v github.com/astaxie/beego/client/orm + go test -v github.com/astaxie/beego/orm #### TiDB export ORM_DRIVER=tidb export ORM_SOURCE='memory://test/test' - go test -v github.com/astaxie/beego/pgk/orm + go test -v github.com/astaxie/beego/orm ` ) func init() { - // Debug, _ = StrTo(DBARGS.Debug).Bool() - Debug = true + Debug, _ = StrTo(DBARGS.Debug).Bool() if DBARGS.Driver == "" || DBARGS.Source == "" { fmt.Println(helpinfo) os.Exit(2) } - err := RegisterDataBase("default", DBARGS.Driver, DBARGS.Source, MaxIdleConnections(20)) - - if err != nil { - panic(fmt.Sprintf("can not register database: %v", err)) - } + RegisterDataBase("default", DBARGS.Driver, DBARGS.Source, 20) alias := getDbAlias("default") if alias.Driver == DRMySQL { diff --git a/client/orm/models_utils.go b/orm/models_utils.go similarity index 93% rename from client/orm/models_utils.go rename to orm/models_utils.go index 950ca243..71127a6b 100644 --- a/client/orm/models_utils.go +++ b/orm/models_utils.go @@ -45,7 +45,6 @@ var supportTag = map[string]int{ "on_delete": 2, "type": 2, "description": 2, - "precision": 2, } // get reflect.Type name with package path. @@ -107,18 +106,6 @@ func getTableUnique(val reflect.Value) [][]string { return nil } -// get whether the table needs to be created for the database alias -func isApplicableTableForDB(val reflect.Value, db string) bool { - fun := val.MethodByName("IsApplicableTableForDB") - if fun.IsValid() { - vals := fun.Call([]reflect.Value{reflect.ValueOf(db)}) - if len(vals) > 0 && vals[0].Kind() == reflect.Bool { - return vals[0].Bool() - } - } - return true -} - // get snaked column name func getColumnName(ft int, addrField reflect.Value, sf reflect.StructField, col string) string { column := col diff --git a/client/orm/orm.go b/orm/orm.go similarity index 57% rename from client/orm/orm.go rename to orm/orm.go index a83faeb2..0551b1cd 100644 --- a/client/orm/orm.go +++ b/orm/orm.go @@ -21,7 +21,7 @@ // // import ( // "fmt" -// "github.com/astaxie/beego/client/orm" +// "github.com/astaxie/beego/orm" // _ "github.com/go-sql-driver/mysql" // import your used driver // ) // @@ -60,12 +60,8 @@ import ( "fmt" "os" "reflect" + "sync" "time" - - "github.com/astaxie/beego/client/orm/hints" - "github.com/astaxie/beego/core/utils" - - "github.com/astaxie/beego/core/logs" ) // DebugQueries define the debug @@ -80,14 +76,13 @@ var ( DefaultRowsLimit = -1 DefaultRelsDepth = 2 DefaultTimeLoc = time.Local - ErrTxDone = errors.New(" transaction already done") + ErrTxHasBegan = errors.New(" transaction already begin") + ErrTxDone = errors.New(" transaction not begin") ErrMultiRows = errors.New(" return multi rows") ErrNoRows = errors.New(" no row found") ErrStmtClosed = errors.New(" stmt already closed") ErrArgs = errors.New(" args error may be empty") ErrNotImplement = errors.New("have not implement") - - ErrLastInsertIdUnavailable = errors.New(" last insert id is unavailable") ) // Params stores the Params @@ -96,17 +91,16 @@ type Params map[string]interface{} // ParamsList stores paramslist type ParamsList []interface{} -type ormBase struct { +type orm struct { alias *alias db dbQuerier + isTx bool } -var _ DQL = new(ormBase) -var _ DML = new(ormBase) -var _ DriverGetter = new(ormBase) +var _ Ormer = new(orm) // get model info and model reflect value -func (o *ormBase) getMiInd(md interface{}, needPtr bool) (mi *modelInfo, ind reflect.Value) { +func (o *orm) getMiInd(md interface{}, needPtr bool) (mi *modelInfo, ind reflect.Value) { val := reflect.ValueOf(md) ind = reflect.Indirect(val) typ := ind.Type() @@ -121,7 +115,7 @@ func (o *ormBase) getMiInd(md interface{}, needPtr bool) (mi *modelInfo, ind ref } // get field info from model info by given field name -func (o *ormBase) getFieldInfo(mi *modelInfo, name string) *fieldInfo { +func (o *orm) getFieldInfo(mi *modelInfo, name string) *fieldInfo { fi, ok := mi.fields.GetByAny(name) if !ok { panic(fmt.Errorf(" cannot find field `%s` for model `%s`", name, mi.fullName)) @@ -130,42 +124,33 @@ func (o *ormBase) getFieldInfo(mi *modelInfo, name string) *fieldInfo { } // read data to model -func (o *ormBase) Read(md interface{}, cols ...string) error { - return o.ReadWithCtx(context.Background(), md, cols...) -} -func (o *ormBase) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error { +func (o *orm) Read(md interface{}, cols ...string) error { mi, ind := o.getMiInd(md, true) return o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, false) } // read data to model, like Read(), but use "SELECT FOR UPDATE" form -func (o *ormBase) ReadForUpdate(md interface{}, cols ...string) error { - return o.ReadForUpdateWithCtx(context.Background(), md, cols...) -} -func (o *ormBase) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error { +func (o *orm) ReadForUpdate(md interface{}, cols ...string) error { mi, ind := o.getMiInd(md, true) return o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, true) } // Try to read a row from the database, or insert one if it doesn't exist -func (o *ormBase) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) { - return o.ReadOrCreateWithCtx(context.Background(), md, col1, cols...) -} -func (o *ormBase) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) { +func (o *orm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) { cols = append([]string{col1}, cols...) mi, ind := o.getMiInd(md, true) err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, false) if err == ErrNoRows { // Create - id, err := o.InsertWithCtx(ctx, md) - return err == nil, id, err + id, err := o.Insert(md) + return (err == nil), id, err } id, vid := int64(0), ind.FieldByIndex(mi.fields.pk.fieldIndex) if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 { id = int64(vid.Uint()) } else if mi.fields.pk.rel { - return o.ReadOrCreateWithCtx(ctx, vid.Interface(), mi.fields.pk.relModelInfo.fields.pk.name) + return o.ReadOrCreate(vid.Interface(), mi.fields.pk.relModelInfo.fields.pk.name) } else { id = vid.Int() } @@ -174,10 +159,7 @@ func (o *ormBase) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 } // insert model data to database -func (o *ormBase) Insert(md interface{}) (int64, error) { - return o.InsertWithCtx(context.Background(), md) -} -func (o *ormBase) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) { +func (o *orm) Insert(md interface{}) (int64, error) { mi, ind := o.getMiInd(md, true) id, err := o.alias.DbBaser.Insert(o.db, mi, ind, o.alias.TZ) if err != nil { @@ -190,7 +172,7 @@ func (o *ormBase) InsertWithCtx(ctx context.Context, md interface{}) (int64, err } // set auto pk field -func (o *ormBase) setPk(mi *modelInfo, ind reflect.Value, id int64) { +func (o *orm) setPk(mi *modelInfo, ind reflect.Value, id int64) { if mi.fields.pk.auto { if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 { ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(uint64(id)) @@ -201,10 +183,7 @@ func (o *ormBase) setPk(mi *modelInfo, ind reflect.Value, id int64) { } // insert some models to database -func (o *ormBase) InsertMulti(bulk int, mds interface{}) (int64, error) { - return o.InsertMultiWithCtx(context.Background(), bulk, mds) -} -func (o *ormBase) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) { +func (o *orm) InsertMulti(bulk int, mds interface{}) (int64, error) { var cnt int64 sind := reflect.Indirect(reflect.ValueOf(mds)) @@ -239,10 +218,7 @@ func (o *ormBase) InsertMultiWithCtx(ctx context.Context, bulk int, mds interfac } // InsertOrUpdate data to database -func (o *ormBase) InsertOrUpdate(md interface{}, colConflictAndArgs ...string) (int64, error) { - return o.InsertOrUpdateWithCtx(context.Background(), md, colConflictAndArgs...) -} -func (o *ormBase) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) { +func (o *orm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) { mi, ind := o.getMiInd(md, true) id, err := o.alias.DbBaser.InsertOrUpdate(o.db, mi, ind, o.alias, colConflitAndArgs...) if err != nil { @@ -256,20 +232,14 @@ func (o *ormBase) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, col // update model to database. // cols set the columns those want to update. -func (o *ormBase) Update(md interface{}, cols ...string) (int64, error) { - return o.UpdateWithCtx(context.Background(), md, cols...) -} -func (o *ormBase) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) { +func (o *orm) Update(md interface{}, cols ...string) (int64, error) { mi, ind := o.getMiInd(md, true) return o.alias.DbBaser.Update(o.db, mi, ind, o.alias.TZ, cols) } // delete model in database // cols shows the delete conditions values read from. default is pk -func (o *ormBase) Delete(md interface{}, cols ...string) (int64, error) { - return o.DeleteWithCtx(context.Background(), md, cols...) -} -func (o *ormBase) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) { +func (o *orm) Delete(md interface{}, cols ...string) (int64, error) { mi, ind := o.getMiInd(md, true) num, err := o.alias.DbBaser.Delete(o.db, mi, ind, o.alias.TZ, cols) if err != nil { @@ -282,10 +252,7 @@ func (o *ormBase) DeleteWithCtx(ctx context.Context, md interface{}, cols ...str } // create a models to models queryer -func (o *ormBase) QueryM2M(md interface{}, name string) QueryM2Mer { - return o.QueryM2MWithCtx(context.Background(), md, name) -} -func (o *ormBase) QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer { +func (o *orm) QueryM2M(md interface{}, name string) QueryM2Mer { mi, ind := o.getMiInd(md, true) fi := o.getFieldInfo(mi, name) @@ -307,38 +274,32 @@ func (o *ormBase) QueryM2MWithCtx(ctx context.Context, md interface{}, name stri // for _,tag := range post.Tags{...} // // make sure the relation is defined in model struct tags. -func (o *ormBase) LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) { - return o.LoadRelatedWithCtx(context.Background(), md, name, args...) -} -func (o *ormBase) LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) { - _, fi, ind, qs := o.queryRelated(md, name) +func (o *orm) LoadRelated(md interface{}, name string, args ...interface{}) (int64, error) { + _, fi, ind, qseter := o.queryRelated(md, name) + + qs := qseter.(*querySet) var relDepth int var limit, offset int64 var order string - - kvs := utils.NewKVs(args...) - kvs.IfContains(hints.KeyRelDepth, func(value interface{}) { - if v, ok := value.(bool); ok { - if v { - relDepth = DefaultRelsDepth + for i, arg := range args { + switch i { + case 0: + if v, ok := arg.(bool); ok { + if v { + relDepth = DefaultRelsDepth + } + } else if v, ok := arg.(int); ok { + relDepth = v } - } else if v, ok := value.(int); ok { - relDepth = v + case 1: + limit = ToInt64(arg) + case 2: + offset = ToInt64(arg) + case 3: + order, _ = arg.(string) } - }).IfContains(hints.KeyLimit, func(value interface{}) { - if v, ok := value.(int64); ok { - limit = v - } - }).IfContains(hints.KeyOffset, func(value interface{}) { - if v, ok := value.(int64); ok { - offset = v - } - }).IfContains(hints.KeyOrderBy, func(value interface{}) { - if v, ok := value.(string); ok { - order = v - } - }) + } switch fi.fieldType { case RelOneToOne, RelForeignKey, RelReverseOne: @@ -374,8 +335,20 @@ func (o *ormBase) LoadRelatedWithCtx(ctx context.Context, md interface{}, name s return nums, err } +// return a QuerySeter for related models to md model. +// it can do all, update, delete in QuerySeter. +// example: +// qs := orm.QueryRelated(post,"Tag") +// qs.All(&[]*Tag{}) +// +func (o *orm) QueryRelated(md interface{}, name string) QuerySeter { + // is this api needed ? + _, _, _, qs := o.queryRelated(md, name) + return qs +} + // get QuerySeter for related models to md model -func (o *ormBase) queryRelated(md interface{}, name string) (*modelInfo, *fieldInfo, reflect.Value, *querySet) { +func (o *orm) queryRelated(md interface{}, name string) (*modelInfo, *fieldInfo, reflect.Value, QuerySeter) { mi, ind := o.getMiInd(md, true) fi := o.getFieldInfo(mi, name) @@ -407,7 +380,7 @@ func (o *ormBase) queryRelated(md interface{}, name string) (*modelInfo, *fieldI } // get reverse relation QuerySeter -func (o *ormBase) getReverseQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet { +func (o *orm) getReverseQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet { switch fi.fieldType { case RelReverseOne, RelReverseMany: default: @@ -428,7 +401,7 @@ func (o *ormBase) getReverseQs(md interface{}, mi *modelInfo, fi *fieldInfo) *qu } // get relation QuerySeter -func (o *ormBase) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet { +func (o *orm) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet { switch fi.fieldType { case RelOneToOne, RelForeignKey, RelManyToMany: default: @@ -450,10 +423,7 @@ func (o *ormBase) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *queryS // return a QuerySeter for table operations. // table name can be string or struct. // e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)), -func (o *ormBase) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) { - return o.QueryTableWithCtx(context.Background(), ptrStructOrTableName) -} -func (o *ormBase) QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) (qs QuerySeter) { +func (o *orm) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) { var name string if table, ok := ptrStructOrTableName.(string); ok { name = nameStrategyMap[defaultNameStrategy](table) @@ -472,21 +442,89 @@ func (o *ormBase) QueryTableWithCtx(ctx context.Context, ptrStructOrTableName in return } -// return a raw query seter for raw sql string. -func (o *ormBase) Raw(query string, args ...interface{}) RawSeter { - return o.RawWithCtx(context.Background(), query, args...) +// switch to another registered database driver by given name. +func (o *orm) Using(name string) error { + if o.isTx { + panic(fmt.Errorf(" transaction has been start, cannot change db")) + } + if al, ok := dataBaseCache.get(name); ok { + o.alias = al + if Debug { + o.db = newDbQueryLog(al, al.DB) + } else { + o.db = al.DB + } + } else { + return fmt.Errorf(" unknown db alias name `%s`", name) + } + return nil } -func (o *ormBase) RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter { + +// begin transaction +func (o *orm) Begin() error { + return o.BeginTx(context.Background(), nil) +} + +func (o *orm) BeginTx(ctx context.Context, opts *sql.TxOptions) error { + if o.isTx { + return ErrTxHasBegan + } + var tx *sql.Tx + tx, err := o.db.(txer).BeginTx(ctx, opts) + if err != nil { + return err + } + o.isTx = true + if Debug { + o.db.(*dbQueryLog).SetDB(tx) + } else { + o.db = tx + } + return nil +} + +// commit transaction +func (o *orm) Commit() error { + if !o.isTx { + return ErrTxDone + } + err := o.db.(txEnder).Commit() + if err == nil { + o.isTx = false + o.Using(o.alias.Name) + } else if err == sql.ErrTxDone { + return ErrTxDone + } + return err +} + +// rollback transaction +func (o *orm) Rollback() error { + if !o.isTx { + return ErrTxDone + } + err := o.db.(txEnder).Rollback() + if err == nil { + o.isTx = false + o.Using(o.alias.Name) + } else if err == sql.ErrTxDone { + return ErrTxDone + } + return err +} + +// return a raw query seter for raw sql string. +func (o *orm) Raw(query string, args ...interface{}) RawSeter { return newRawSet(o, query, args) } // return current using database Driver -func (o *ormBase) Driver() Driver { +func (o *orm) Driver() Driver { return driver(o.alias.Name) } // return sql.DBStats for current database -func (o *ormBase) DBStats() *sql.DBStats { +func (o *orm) DBStats() *sql.DBStats { if o.alias != nil && o.alias.DB != nil { stats := o.alias.DB.DB.Stats() return &stats @@ -494,134 +532,48 @@ func (o *ormBase) DBStats() *sql.DBStats { return nil } -type orm struct { - ormBase -} - -var _ Ormer = new(orm) - -func (o *orm) Begin() (TxOrmer, error) { - return o.BeginWithCtx(context.Background()) -} - -func (o *orm) BeginWithCtx(ctx context.Context) (TxOrmer, error) { - return o.BeginWithCtxAndOpts(ctx, nil) -} - -func (o *orm) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) { - return o.BeginWithCtxAndOpts(context.Background(), opts) -} - -func (o *orm) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) { - tx, err := o.db.(txer).BeginTx(ctx, opts) - if err != nil { - return nil, err - } - - _txOrm := &txOrm{ - ormBase: ormBase{ - alias: o.alias, - db: &TxDB{tx: tx}, - }, - } - - var taskTxOrm TxOrmer = _txOrm - return taskTxOrm, nil -} - -func (o *orm) DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error { - return o.DoTxWithCtx(context.Background(), task) -} - -func (o *orm) DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error { - return o.DoTxWithCtxAndOpts(ctx, nil, task) -} - -func (o *orm) DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error { - return o.DoTxWithCtxAndOpts(context.Background(), opts, task) -} - -func (o *orm) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error { - return doTxTemplate(o, ctx, opts, task) -} - -func doTxTemplate(o TxBeginner, ctx context.Context, opts *sql.TxOptions, - task func(ctx context.Context, txOrm TxOrmer) error) error { - _txOrm, err := o.BeginWithCtxAndOpts(ctx, opts) - if err != nil { - return err - } - panicked := true - defer func() { - if panicked || err != nil { - e := _txOrm.Rollback() - if e != nil { - logs.Error("rollback transaction failed: %v,%v", e, panicked) - } - } else { - e := _txOrm.Commit() - if e != nil { - logs.Error("commit transaction failed: %v,%v", e, panicked) - } - } - }() - var taskTxOrm = _txOrm - err = task(ctx, taskTxOrm) - panicked = false - return err -} - -type txOrm struct { - ormBase -} - -var _ TxOrmer = new(txOrm) - -func (t *txOrm) Commit() error { - return t.db.(txEnder).Commit() -} - -func (t *txOrm) Rollback() error { - return t.db.(txEnder).Rollback() -} - // NewOrm create new orm func NewOrm() Ormer { BootStrap() // execute only once - return NewOrmUsingDB(`default`) -} -// NewOrmUsingDB create new orm with the name -func NewOrmUsingDB(aliasName string) Ormer { - if al, ok := dataBaseCache.get(aliasName); ok { - return newDBWithAlias(al) - } else { - panic(fmt.Errorf(" unknown db alias name `%s`", aliasName)) + o := new(orm) + err := o.Using("default") + if err != nil { + panic(err) } + return o } // NewOrmWithDB create a new ormer object with specify *sql.DB for query -func NewOrmWithDB(driverName, aliasName string, db *sql.DB, params ...DBOption) (Ormer, error) { - al, err := newAliasWithDb(aliasName, driverName, db, params...) - if err != nil { - return nil, err +func NewOrmWithDB(driverName, aliasName string, db *sql.DB) (Ormer, error) { + var al *alias + + if dr, ok := drivers[driverName]; ok { + al = new(alias) + al.DbBaser = dbBasers[dr] + al.Driver = dr + } else { + return nil, fmt.Errorf("driver name `%s` have not registered", driverName) } - return newDBWithAlias(al), nil -} + al.Name = aliasName + al.DriverName = driverName + al.DB = &DB{ + RWMutex: new(sync.RWMutex), + DB: db, + stmtDecorators: newStmtDecoratorLruWithEvict(), + } + + detectTZ(al) -func newDBWithAlias(al *alias) Ormer { o := new(orm) o.alias = al if Debug { - o.db = newDbQueryLog(al, al.DB) + o.db = newDbQueryLog(o.alias, db) } else { - o.db = al.DB + o.db = db } - if len(globalFilterChains) > 0 { - return NewFilterOrmDecorator(o, globalFilterChains...) - } - return o + return o, nil } diff --git a/client/orm/orm_conds.go b/orm/orm_conds.go similarity index 100% rename from client/orm/orm_conds.go rename to orm/orm_conds.go diff --git a/client/orm/orm_log.go b/orm/orm_log.go similarity index 88% rename from client/orm/orm_log.go rename to orm/orm_log.go index d8df7e36..f107bb59 100644 --- a/client/orm/orm_log.go +++ b/orm/orm_log.go @@ -61,7 +61,7 @@ func debugLogQueies(alias *alias, operaton, query string, t time.Time, err error con += " - " + err.Error() } logMap["sql"] = fmt.Sprintf("%s-`%s`", query, strings.Join(cons, "`, `")) - if LogFunc != nil { + if LogFunc != nil{ LogFunc(logMap) } DebugLog.Println(con) @@ -127,7 +127,10 @@ var _ txer = new(dbQueryLog) var _ txEnder = new(dbQueryLog) func (d *dbQueryLog) Prepare(query string) (*sql.Stmt, error) { - return d.PrepareContext(context.Background(), query) + a := time.Now() + stmt, err := d.db.Prepare(query) + debugLogQueies(d.alias, "db.Prepare", query, a, err) + return stmt, err } func (d *dbQueryLog) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) { @@ -138,7 +141,10 @@ func (d *dbQueryLog) PrepareContext(ctx context.Context, query string) (*sql.Stm } func (d *dbQueryLog) Exec(query string, args ...interface{}) (sql.Result, error) { - return d.ExecContext(context.Background(), query, args...) + a := time.Now() + res, err := d.db.Exec(query, args...) + debugLogQueies(d.alias, "db.Exec", query, a, err, args...) + return res, err } func (d *dbQueryLog) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { @@ -149,7 +155,10 @@ func (d *dbQueryLog) ExecContext(ctx context.Context, query string, args ...inte } func (d *dbQueryLog) Query(query string, args ...interface{}) (*sql.Rows, error) { - return d.QueryContext(context.Background(), query, args...) + a := time.Now() + res, err := d.db.Query(query, args...) + debugLogQueies(d.alias, "db.Query", query, a, err, args...) + return res, err } func (d *dbQueryLog) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { @@ -160,7 +169,10 @@ func (d *dbQueryLog) QueryContext(ctx context.Context, query string, args ...int } func (d *dbQueryLog) QueryRow(query string, args ...interface{}) *sql.Row { - return d.QueryRowContext(context.Background(), query, args...) + a := time.Now() + res := d.db.QueryRow(query, args...) + debugLogQueies(d.alias, "db.QueryRow", query, a, nil, args...) + return res } func (d *dbQueryLog) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { @@ -171,7 +183,10 @@ func (d *dbQueryLog) QueryRowContext(ctx context.Context, query string, args ... } func (d *dbQueryLog) Begin() (*sql.Tx, error) { - return d.BeginTx(context.Background(), nil) + a := time.Now() + tx, err := d.db.(txer).Begin() + debugLogQueies(d.alias, "db.Begin", "START TRANSACTION", a, err) + return tx, err } func (d *dbQueryLog) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) { diff --git a/client/orm/orm_object.go b/orm/orm_object.go similarity index 96% rename from client/orm/orm_object.go rename to orm/orm_object.go index 6f9798d3..de3181ce 100644 --- a/client/orm/orm_object.go +++ b/orm/orm_object.go @@ -22,7 +22,7 @@ import ( // an insert queryer struct type insertSet struct { mi *modelInfo - orm *ormBase + orm *orm stmt stmtQuerier closed bool } @@ -70,7 +70,7 @@ func (o *insertSet) Close() error { } // create new insert queryer. -func newInsertSet(orm *ormBase, mi *modelInfo) (Inserter, error) { +func newInsertSet(orm *orm, mi *modelInfo) (Inserter, error) { bi := new(insertSet) bi.orm = orm bi.mi = mi diff --git a/client/orm/orm_querym2m.go b/orm/orm_querym2m.go similarity index 97% rename from client/orm/orm_querym2m.go rename to orm/orm_querym2m.go index 17e1b5d1..6a270a0d 100644 --- a/client/orm/orm_querym2m.go +++ b/orm/orm_querym2m.go @@ -129,7 +129,7 @@ func (o *queryM2M) Count() (int64, error) { var _ QueryM2Mer = new(queryM2M) // create new M2M queryer. -func newQueryM2M(md interface{}, o *ormBase, mi *modelInfo, fi *fieldInfo, ind reflect.Value) QueryM2Mer { +func newQueryM2M(md interface{}, o *orm, mi *modelInfo, fi *fieldInfo, ind reflect.Value) QueryM2Mer { qm2m := new(queryM2M) qm2m.md = md qm2m.mi = mi diff --git a/client/orm/orm_queryset.go b/orm/orm_queryset.go similarity index 91% rename from client/orm/orm_queryset.go rename to orm/orm_queryset.go index ed223e24..878b836b 100644 --- a/client/orm/orm_queryset.go +++ b/orm/orm_queryset.go @@ -17,8 +17,6 @@ package orm import ( "context" "fmt" - - "github.com/astaxie/beego/client/orm/hints" ) type colValue struct { @@ -73,10 +71,8 @@ type querySet struct { groups []string orders []string distinct bool - forUpdate bool - useIndex int - indexes []string - orm *ormBase + forupdate bool + orm *orm ctx context.Context forContext bool } @@ -152,28 +148,7 @@ func (o querySet) Distinct() QuerySeter { // add FOR UPDATE to SELECT func (o querySet) ForUpdate() QuerySeter { - o.forUpdate = true - return &o -} - -// ForceIndex force index for query -func (o querySet) ForceIndex(indexes ...string) QuerySeter { - o.useIndex = hints.KeyForceIndex - o.indexes = indexes - return &o -} - -// UseIndex use index for query -func (o querySet) UseIndex(indexes ...string) QuerySeter { - o.useIndex = hints.KeyUseIndex - o.indexes = indexes - return &o -} - -// IgnoreIndex ignore index for query -func (o querySet) IgnoreIndex(indexes ...string) QuerySeter { - o.useIndex = hints.KeyIgnoreIndex - o.indexes = indexes + o.forupdate = true return &o } @@ -317,7 +292,7 @@ func (o querySet) WithContext(ctx context.Context) QuerySeter { } // create new QuerySeter. -func newQuerySet(orm *ormBase, mi *modelInfo) QuerySeter { +func newQuerySet(orm *orm, mi *modelInfo) QuerySeter { o := new(querySet) o.mi = mi o.orm = orm diff --git a/client/orm/orm_raw.go b/orm/orm_raw.go similarity index 91% rename from client/orm/orm_raw.go rename to orm/orm_raw.go index e11e97fa..3325a7ea 100644 --- a/client/orm/orm_raw.go +++ b/orm/orm_raw.go @@ -19,8 +19,6 @@ import ( "fmt" "reflect" "time" - - "github.com/pkg/errors" ) // raw sql string prepared statement @@ -34,8 +32,7 @@ func (o *rawPrepare) Exec(args ...interface{}) (sql.Result, error) { if o.closed { return nil, ErrStmtClosed } - flatParams := getFlatParams(nil, args, o.rs.orm.alias.TZ) - return o.stmt.Exec(flatParams...) + return o.stmt.Exec(args...) } func (o *rawPrepare) Close() error { @@ -66,7 +63,7 @@ func newRawPreparer(rs *rawSet) (RawPreparer, error) { type rawSet struct { query string args []interface{} - orm *ormBase + orm *orm } var _ RawSeter = new(rawSet) @@ -330,8 +327,6 @@ func (o *rawSet) QueryRow(containers ...interface{}) error { return err } - structTagMap := make(map[reflect.StructTag]map[string]string) - defer rows.Close() if rows.Next() { @@ -373,50 +368,23 @@ func (o *rawSet) QueryRow(containers ...interface{}) error { field.Set(mf) field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex) } - if fi.isFielder { - fd := field.Addr().Interface().(Fielder) - err := fd.SetRaw(value) - if err != nil { - return errors.Errorf("set raw error:%s", err) - } - } else { - o.setFieldValue(field, value) - } + o.setFieldValue(field, value) } } } else { - // define recursive function - var recursiveSetField func(rv reflect.Value) - recursiveSetField = func(rv reflect.Value) { - for i := 0; i < rv.NumField(); i++ { - f := rv.Field(i) - fe := rv.Type().Field(i) - - // check if the field is a Struct - // recursive the Struct type - if fe.Type.Kind() == reflect.Struct { - recursiveSetField(f) - } - - // thanks @Gazeboxu. - tags := structTagMap[fe.Tag] - if tags == nil { - _, tags = parseStructTag(fe.Tag.Get(defaultStructTagName)) - structTagMap[fe.Tag] = tags - } - var col string - if col = tags["column"]; col == "" { - col = nameStrategyMap[nameStrategy](fe.Name) - } - if v, ok := columnsMp[col]; ok { - value := reflect.ValueOf(v).Elem().Interface() - o.setFieldValue(f, value) - } + for i := 0; i < ind.NumField(); i++ { + f := ind.Field(i) + fe := ind.Type().Field(i) + _, tags := parseStructTag(fe.Tag.Get(defaultStructTagName)) + var col string + if col = tags["column"]; col == "" { + col = nameStrategyMap[nameStrategy](fe.Name) + } + if v, ok := columnsMp[col]; ok { + value := reflect.ValueOf(v).Elem().Interface() + o.setFieldValue(f, value) } } - - // init call the recursive function - recursiveSetField(ind) } } else { @@ -541,15 +509,7 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) { field.Set(mf) field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex) } - if fi.isFielder { - fd := field.Addr().Interface().(Fielder) - err := fd.SetRaw(value) - if err != nil { - return 0, errors.Errorf("set raw error:%s", err) - } - } else { - o.setFieldValue(field, value) - } + o.setFieldValue(field, value) } } } else { @@ -898,7 +858,7 @@ func (o *rawSet) Prepare() (RawPreparer, error) { return newRawPreparer(o) } -func newRawSet(orm *ormBase, query string, args []interface{}) RawSeter { +func newRawSet(orm *orm, query string, args []interface{}) RawSeter { o := new(rawSet) o.query = query o.args = args diff --git a/client/orm/orm_test.go b/orm/orm_test.go similarity index 89% rename from client/orm/orm_test.go rename to orm/orm_test.go index 565f6c60..bdb430b6 100644 --- a/client/orm/orm_test.go +++ b/orm/orm_test.go @@ -30,10 +30,6 @@ import ( "strings" "testing" "time" - - "github.com/astaxie/beego/client/orm/hints" - - "github.com/stretchr/testify/assert" ) var _ = os.PathSeparator @@ -145,7 +141,6 @@ func getCaller(skip int) string { return fmt.Sprintf("%s:%s:%d: \n%s", fn, funName, line, strings.Join(codes, "\n")) } -// Deprecated: Using stretchr/testify/assert func throwFail(t *testing.T, err error, args ...interface{}) { if err != nil { con := fmt.Sprintf("\t\nError: %s\n%s\n", err.Error(), getCaller(2)) @@ -202,9 +197,6 @@ func TestSyncDb(t *testing.T) { RegisterModel(new(IntegerPk)) RegisterModel(new(UintPk)) RegisterModel(new(PtrPk)) - RegisterModel(new(Index)) - RegisterModel(new(StrPk)) - RegisterModel(new(TM)) err := RunSyncdb("default", true, Debug) throwFail(t, err) @@ -229,9 +221,6 @@ func TestRegisterModels(t *testing.T) { RegisterModel(new(IntegerPk)) RegisterModel(new(UintPk)) RegisterModel(new(PtrPk)) - RegisterModel(new(Index)) - RegisterModel(new(StrPk)) - RegisterModel(new(TM)) BootStrap() @@ -305,34 +294,19 @@ func TestDataTypes(t *testing.T) { vu := e.Interface() switch name { case "Date": + vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDate) + value = value.(time.Time).In(DefaultTimeLoc).Format(testDate) case "DateTime": + vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDateTime) + value = value.(time.Time).In(DefaultTimeLoc).Format(testDateTime) case "Time": - assert.True(t, vu.(time.Time).In(DefaultTimeLoc).Sub(value.(time.Time).In(DefaultTimeLoc)) <= time.Second) - break - default: - assert.Equal(t, value, vu) + vu = vu.(time.Time).In(DefaultTimeLoc).Format(testTime) + value = value.(time.Time).In(DefaultTimeLoc).Format(testTime) } + throwFail(t, AssertIs(vu == value, true), value, vu) } } -func TestTM(t *testing.T) { - // The precision of sqlite is not implemented - if dORM.Driver().Type() == 2 { - return - } - var recTM TM - tm := NewTM() - tm.TMPrecision1 = time.Unix(1596766024, 123456789) - tm.TMPrecision2 = time.Unix(1596766024, 123456789) - _, err := dORM.Insert(tm) - throwFail(t, err) - - err = dORM.QueryTable("tm").One(&recTM) - throwFail(t, err) - throwFail(t, AssertIs(recTM.TMPrecision1.String(), "2020-08-07 02:07:04.123 +0000 UTC")) - throwFail(t, AssertIs(recTM.TMPrecision2.String(), "2020-08-07 02:07:04.1235 +0000 UTC")) -} - func TestNullDataTypes(t *testing.T) { d := DataNull{} @@ -481,11 +455,9 @@ func TestNullDataTypes(t *testing.T) { throwFail(t, AssertIs(*d.Float32Ptr, float32Ptr)) throwFail(t, AssertIs(*d.Float64Ptr, float64Ptr)) throwFail(t, AssertIs(*d.DecimalPtr, decimalPtr)) - - // in mysql, there are some precision problem, (*d.TimePtr).UTC() != timePtr.UTC() - assert.True(t, (*d.TimePtr).UTC().Sub(timePtr.UTC()) <= time.Second) - assert.True(t, (*d.DatePtr).UTC().Sub(datePtr.UTC()) <= time.Second) - assert.True(t, (*d.DateTimePtr).UTC().Sub(dateTimePtr.UTC()) <= time.Second) + throwFail(t, AssertIs((*d.TimePtr).UTC().Format(testTime), timePtr.UTC().Format(testTime))) + throwFail(t, AssertIs((*d.DatePtr).UTC().Format(testDate), datePtr.UTC().Format(testDate))) + throwFail(t, AssertIs((*d.DateTimePtr).UTC().Format(testDateTime), dateTimePtr.UTC().Format(testDateTime))) // test support for pointer fields using RawSeter.QueryRows() var dnList []*DataNull @@ -560,9 +532,8 @@ func TestCRUD(t *testing.T) { throwFail(t, AssertIs(u.Status, 3)) throwFail(t, AssertIs(u.IsStaff, true)) throwFail(t, AssertIs(u.IsActive, true)) - - assert.True(t, u.Created.In(DefaultTimeLoc).Sub(user.Created.In(DefaultTimeLoc)) <= time.Second) - assert.True(t, u.Updated.In(DefaultTimeLoc).Sub(user.Updated.In(DefaultTimeLoc)) <= time.Second) + throwFail(t, AssertIs(u.Created.In(DefaultTimeLoc), user.Created.In(DefaultTimeLoc), testDate)) + throwFail(t, AssertIs(u.Updated.In(DefaultTimeLoc), user.Updated.In(DefaultTimeLoc), testDateTime)) user.UserName = "astaxie" user.Profile = profile @@ -798,20 +769,6 @@ func TestCustomField(t *testing.T) { throwFailNow(t, AssertIs(user.Extra.Name, "beego")) throwFailNow(t, AssertIs(user.Extra.Data, "orm")) - - var users []User - Q := dDbBaser.TableQuote() - n, err := dORM.Raw(fmt.Sprintf("SELECT * FROM %suser%s where id=?", Q, Q), 2).QueryRows(&users) - throwFailNow(t, err) - throwFailNow(t, AssertIs(n, 1)) - throwFailNow(t, AssertIs(users[0].Extra.Name, "beego")) - throwFailNow(t, AssertIs(users[0].Extra.Data, "orm")) - - user = User{} - err = dORM.Raw(fmt.Sprintf("SELECT * FROM %suser%s where id=?", Q, Q), 2).QueryRow(&user) - throwFailNow(t, err) - throwFailNow(t, AssertIs(user.Extra.Name, "beego")) - throwFailNow(t, AssertIs(user.Extra.Data, "orm")) } func TestExpr(t *testing.T) { @@ -833,32 +790,6 @@ func TestExpr(t *testing.T) { // throwFail(t, AssertIs(num, 3)) } -func TestSpecifyIndex(t *testing.T) { - var index *Index - index = &Index{ - F1: 1, - F2: 2, - } - _, _ = dORM.Insert(index) - throwFailNow(t, AssertIs(index.Id, 1)) - - index = &Index{ - F1: 3, - F2: 4, - } - _, _ = dORM.Insert(index) - throwFailNow(t, AssertIs(index.Id, 2)) - - _ = dORM.QueryTable(&Index{}).Filter(`f1`, `1`).ForceIndex(`index_f1`).One(index) - throwFailNow(t, AssertIs(index.F2, 2)) - - _ = dORM.QueryTable(&Index{}).Filter(`f2`, `4`).UseIndex(`index_f2`).One(index) - throwFailNow(t, AssertIs(index.F1, 3)) - - _ = dORM.QueryTable(&Index{}).Filter(`f1`, `1`).IgnoreIndex(`index_f1`, `index_f2`).One(index) - throwFailNow(t, AssertIs(index.F2, 2)) -} - func TestOperators(t *testing.T) { qs := dORM.QueryTable("user") num, err := qs.Filter("user_name", "slene").Count() @@ -877,17 +808,6 @@ func TestOperators(t *testing.T) { throwFail(t, err) throwFail(t, AssertIs(num, 1)) - if IsMysql { - // Now only mysql support `strictexact` - num, err = qs.Filter("user_name__strictexact", "Slene").Count() - throwFail(t, err) - throwFail(t, AssertIs(num, 0)) - - num, err = qs.Filter("user_name__strictexact", "slene").Count() - throwFail(t, err) - throwFail(t, AssertIs(num, 1)) - } - num, err = qs.Filter("user_name__contains", "e").Count() throwFail(t, err) throwFail(t, AssertIs(num, 2)) @@ -1356,32 +1276,24 @@ func TestLoadRelated(t *testing.T) { throwFailNow(t, AssertIs(len(user.Posts), 2)) throwFailNow(t, AssertIs(user.Posts[0].User.ID, 3)) - num, err = dORM.LoadRelated(&user, "Posts", hints.DefaultRelDepth()) + num, err = dORM.LoadRelated(&user, "Posts", true) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 2)) throwFailNow(t, AssertIs(len(user.Posts), 2)) throwFailNow(t, AssertIs(user.Posts[0].User.UserName, "astaxie")) - num, err = dORM.LoadRelated(&user, "Posts", - hints.DefaultRelDepth(), - hints.Limit(1)) + num, err = dORM.LoadRelated(&user, "Posts", true, 1) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 1)) throwFailNow(t, AssertIs(len(user.Posts), 1)) - num, err = dORM.LoadRelated(&user, "Posts", - hints.DefaultRelDepth(), - hints.OrderBy("-Id")) + num, err = dORM.LoadRelated(&user, "Posts", true, 0, 0, "-Id") throwFailNow(t, err) throwFailNow(t, AssertIs(num, 2)) throwFailNow(t, AssertIs(len(user.Posts), 2)) throwFailNow(t, AssertIs(user.Posts[0].Title, "Formatting")) - num, err = dORM.LoadRelated(&user, "Posts", - hints.DefaultRelDepth(), - hints.Limit(1), - hints.Offset(1), - hints.OrderBy("Id")) + num, err = dORM.LoadRelated(&user, "Posts", true, 1, 1, "Id") throwFailNow(t, err) throwFailNow(t, AssertIs(num, 1)) throwFailNow(t, AssertIs(len(user.Posts), 1)) @@ -1403,7 +1315,7 @@ func TestLoadRelated(t *testing.T) { throwFailNow(t, AssertIs(profile.User == nil, false)) throwFailNow(t, AssertIs(profile.User.UserName, "astaxie")) - num, err = dORM.LoadRelated(&profile, "User", hints.DefaultRelDepth()) + num, err = dORM.LoadRelated(&profile, "User", true) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 1)) throwFailNow(t, AssertIs(profile.User == nil, false)) @@ -1420,7 +1332,7 @@ func TestLoadRelated(t *testing.T) { throwFailNow(t, AssertIs(user.Profile == nil, false)) throwFailNow(t, AssertIs(user.Profile.Age, 30)) - num, err = dORM.LoadRelated(&user, "Profile", hints.DefaultRelDepth()) + num, err = dORM.LoadRelated(&user, "Profile", true) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 1)) throwFailNow(t, AssertIs(user.Profile == nil, false)) @@ -1440,7 +1352,7 @@ func TestLoadRelated(t *testing.T) { throwFailNow(t, AssertIs(post.User == nil, false)) throwFailNow(t, AssertIs(post.User.UserName, "astaxie")) - num, err = dORM.LoadRelated(&post, "User", hints.DefaultRelDepth()) + num, err = dORM.LoadRelated(&post, "User", true) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 1)) throwFailNow(t, AssertIs(post.User == nil, false)) @@ -1460,7 +1372,7 @@ func TestLoadRelated(t *testing.T) { throwFailNow(t, AssertIs(len(post.Tags), 2)) throwFailNow(t, AssertIs(post.Tags[0].Name, "golang")) - num, err = dORM.LoadRelated(&post, "Tags", hints.DefaultRelDepth()) + num, err = dORM.LoadRelated(&post, "Tags", true) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 2)) throwFailNow(t, AssertIs(len(post.Tags), 2)) @@ -1481,7 +1393,7 @@ func TestLoadRelated(t *testing.T) { throwFailNow(t, AssertIs(tag.Posts[0].User.ID, 2)) throwFailNow(t, AssertIs(tag.Posts[0].User.Profile == nil, true)) - num, err = dORM.LoadRelated(&tag, "Posts", hints.DefaultRelDepth()) + num, err = dORM.LoadRelated(&tag, "Posts", true) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 3)) throwFailNow(t, AssertIs(tag.Posts[0].Title, "Introduction")) @@ -1744,14 +1656,18 @@ func TestRawQueryRow(t *testing.T) { switch col { case "id": throwFail(t, AssertIs(id, 1)) - break case "time": + v = v.(time.Time).In(DefaultTimeLoc) + value := dataValues[col].(time.Time).In(DefaultTimeLoc) + throwFail(t, AssertIs(v, value, testTime)) case "date": + v = v.(time.Time).In(DefaultTimeLoc) + value := dataValues[col].(time.Time).In(DefaultTimeLoc) + throwFail(t, AssertIs(v, value, testDate)) case "datetime": v = v.(time.Time).In(DefaultTimeLoc) value := dataValues[col].(time.Time).In(DefaultTimeLoc) - assert.True(t, v.(time.Time).Sub(value) <= time.Second) - break + throwFail(t, AssertIs(v, value, testDateTime)) default: throwFail(t, AssertIs(v, dataValues[col])) } @@ -1773,24 +1689,6 @@ func TestRawQueryRow(t *testing.T) { throwFail(t, AssertIs(*status, 3)) throwFail(t, AssertIs(pid, nil)) - type Embeded struct { - Email string - } - type queryRowNoModelTest struct { - Id int - EmbedField Embeded - } - - cols = []string{ - "id", "email", - } - var row queryRowNoModelTest - query = fmt.Sprintf("SELECT %s%s%s FROM %suser%s WHERE id = ?", Q, strings.Join(cols, sep), Q, Q, Q) - err = dORM.Raw(query, 4).QueryRow(&row) - throwFail(t, err) - throwFail(t, AssertIs(row.Id, 4)) - throwFail(t, AssertIs(row.EmbedField.Email, "nobody@gmail.com")) - // test for sql.Null* fields nData := &DataNull{ NullString: sql.NullString{String: "test sql.null", Valid: true}, @@ -1842,13 +1740,16 @@ func TestQueryRows(t *testing.T) { vu := e.Interface() switch name { case "Time": + vu = vu.(time.Time).In(DefaultTimeLoc).Format(testTime) + value = value.(time.Time).In(DefaultTimeLoc).Format(testTime) case "Date": + vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDate) + value = value.(time.Time).In(DefaultTimeLoc).Format(testDate) case "DateTime": - assert.True(t, vu.(time.Time).In(DefaultTimeLoc).Sub(value.(time.Time).In(DefaultTimeLoc)) <= time.Second) - break - default: - assert.Equal(t, value, vu) + vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDateTime) + value = value.(time.Time).In(DefaultTimeLoc).Format(testDateTime) } + throwFail(t, AssertIs(vu == value, true), value, vu) } var datas2 []Data @@ -1866,14 +1767,16 @@ func TestQueryRows(t *testing.T) { vu := e.Interface() switch name { case "Time": + vu = vu.(time.Time).In(DefaultTimeLoc).Format(testTime) + value = value.(time.Time).In(DefaultTimeLoc).Format(testTime) case "Date": + vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDate) + value = value.(time.Time).In(DefaultTimeLoc).Format(testDate) case "DateTime": - assert.True(t, vu.(time.Time).In(DefaultTimeLoc).Sub(value.(time.Time).In(DefaultTimeLoc)) <= time.Second) - break - default: - assert.Equal(t, value, vu) + vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDateTime) + value = value.(time.Time).In(DefaultTimeLoc).Format(testDateTime) } - + throwFail(t, AssertIs(vu == value, true), value, vu) } var ids []int @@ -1890,7 +1793,7 @@ func TestQueryRows(t *testing.T) { throwFailNow(t, AssertIs(ids[2], 4)) throwFailNow(t, AssertIs(usernames[2], "nobody")) - // test query rows by nested struct + //test query rows by nested struct var l []userProfile query = fmt.Sprintf("SELECT * FROM %suser_profile%s LEFT JOIN %suser%s ON %suser_profile%s.%sid%s = %suser%s.%sid%s", Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q) num, err = dORM.Raw(query).QueryRows(&l) @@ -2117,24 +2020,24 @@ func TestTransaction(t *testing.T) { // this test worked when database support transaction o := NewOrm() - to, err := o.Begin() + err := o.Begin() throwFail(t, err) var names = []string{"1", "2", "3"} var tag Tag tag.Name = names[0] - id, err := to.Insert(&tag) + id, err := o.Insert(&tag) throwFail(t, err) throwFail(t, AssertIs(id > 0, true)) - num, err := to.QueryTable("tag").Filter("name", "golang").Update(Params{"name": names[1]}) + num, err := o.QueryTable("tag").Filter("name", "golang").Update(Params{"name": names[1]}) throwFail(t, err) throwFail(t, AssertIs(num, 1)) switch { case IsMysql || IsSqlite: - res, err := to.Raw("INSERT INTO tag (name) VALUES (?)", names[2]).Exec() + res, err := o.Raw("INSERT INTO tag (name) VALUES (?)", names[2]).Exec() throwFail(t, err) if err == nil { id, err = res.LastInsertId() @@ -2143,22 +2046,22 @@ func TestTransaction(t *testing.T) { } } - err = to.Rollback() + err = o.Rollback() throwFail(t, err) num, err = o.QueryTable("tag").Filter("name__in", names).Count() throwFail(t, err) throwFail(t, AssertIs(num, 0)) - to, err = o.Begin() + err = o.Begin() throwFail(t, err) tag.Name = "commit" - id, err = to.Insert(&tag) + id, err = o.Insert(&tag) throwFail(t, err) throwFail(t, AssertIs(id > 0, true)) - to.Commit() + o.Commit() throwFail(t, err) num, err = o.QueryTable("tag").Filter("name", "commit").Delete() @@ -2177,33 +2080,33 @@ func TestTransactionIsolationLevel(t *testing.T) { o2 := NewOrm() // start two transaction with isolation level repeatable read - to1, err := o1.BeginWithCtxAndOpts(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead}) + err := o1.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead}) throwFail(t, err) - to2, err := o2.BeginWithCtxAndOpts(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead}) + err = o2.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead}) throwFail(t, err) // o1 insert tag var tag Tag tag.Name = "test-transaction" - id, err := to1.Insert(&tag) + id, err := o1.Insert(&tag) throwFail(t, err) throwFail(t, AssertIs(id > 0, true)) // o2 query tag table, no result - num, err := to2.QueryTable("tag").Filter("name", "test-transaction").Count() + num, err := o2.QueryTable("tag").Filter("name", "test-transaction").Count() throwFail(t, err) throwFail(t, AssertIs(num, 0)) // o1 commit - to1.Commit() + o1.Commit() // o2 query tag table, still no result - num, err = to2.QueryTable("tag").Filter("name", "test-transaction").Count() + num, err = o2.QueryTable("tag").Filter("name", "test-transaction").Count() throwFail(t, err) throwFail(t, AssertIs(num, 0)) // o2 commit and query tag table, get the result - to2.Commit() + o2.Commit() num, err = o2.QueryTable("tag").Filter("name", "test-transaction").Count() throwFail(t, err) throwFail(t, AssertIs(num, 1)) @@ -2216,14 +2119,14 @@ func TestTransactionIsolationLevel(t *testing.T) { func TestBeginTxWithContextCanceled(t *testing.T) { o := NewOrm() ctx, cancel := context.WithCancel(context.Background()) - to, _ := o.BeginWithCtx(ctx) - id, err := to.Insert(&Tag{Name: "test-context"}) + o.BeginTx(ctx, nil) + id, err := o.Insert(&Tag{Name: "test-context"}) throwFail(t, err) throwFail(t, AssertIs(id > 0, true)) // cancel the context before commit to make it error cancel() - err = to.Commit() + err = o.Commit() throwFail(t, AssertIs(err, context.Canceled)) } @@ -2284,8 +2187,8 @@ func TestInLine(t *testing.T) { throwFail(t, AssertIs(il.Name, name)) throwFail(t, AssertIs(il.Email, email)) - assert.True(t, il.Created.In(DefaultTimeLoc).Sub(inline.Created.In(DefaultTimeLoc)) <= time.Second) - assert.True(t, il.Updated.In(DefaultTimeLoc).Sub(inline.Updated.In(DefaultTimeLoc)) <= time.Second) + throwFail(t, AssertIs(il.Created.In(DefaultTimeLoc), inline.Created.In(DefaultTimeLoc), testDate)) + throwFail(t, AssertIs(il.Updated.In(DefaultTimeLoc), inline.Updated.In(DefaultTimeLoc), testDateTime)) } func TestInLineOneToOne(t *testing.T) { @@ -2510,7 +2413,7 @@ func TestInsertOrUpdate(t *testing.T) { fmt.Println("sqlite3 is nonsupport") return } - // test1 + //test1 _, err := dORM.InsertOrUpdate(&user1, "user_name") if err != nil { fmt.Println(err) @@ -2522,7 +2425,7 @@ func TestInsertOrUpdate(t *testing.T) { dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(user1.Status, test.Status)) } - // test2 + //test2 _, err = dORM.InsertOrUpdate(&user2, "user_name") if err != nil { fmt.Println(err) @@ -2536,11 +2439,11 @@ func TestInsertOrUpdate(t *testing.T) { throwFailNow(t, AssertIs(user2.Password, strings.TrimSpace(test.Password))) } - // postgres ON CONFLICT DO UPDATE SET can`t use colu=colu+values + //postgres ON CONFLICT DO UPDATE SET can`t use colu=colu+values if IsPostgres { return } - // test3 + + //test3 + _, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status+1") if err != nil { fmt.Println(err) @@ -2552,7 +2455,7 @@ func TestInsertOrUpdate(t *testing.T) { dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(user2.Status+1, test.Status)) } - // test4 - + //test4 - _, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status-1") if err != nil { fmt.Println(err) @@ -2564,7 +2467,7 @@ func TestInsertOrUpdate(t *testing.T) { dORM.Read(&test, "user_name") throwFailNow(t, AssertIs((user2.Status+1)-1, test.Status)) } - // test5 * + //test5 * _, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status*3") if err != nil { fmt.Println(err) @@ -2576,7 +2479,7 @@ func TestInsertOrUpdate(t *testing.T) { dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(((user2.Status+1)-1)*3, test.Status)) } - // test6 / + //test6 / _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status/3") if err != nil { fmt.Println(err) @@ -2589,82 +2492,3 @@ func TestInsertOrUpdate(t *testing.T) { throwFailNow(t, AssertIs((((user2.Status+1)-1)*3)/3, test.Status)) } } - -func TestStrPkInsert(t *testing.T) { - RegisterModel(new(StrPk)) - pk := `1` - value := `StrPkValues(*56` - strPk := &StrPk{ - Id: pk, - Value: value, - } - - var err error - _, err = dORM.Insert(strPk) - if err != ErrLastInsertIdUnavailable { - throwFailNow(t, AssertIs(err, nil)) - } - - var vForTesting StrPk - err = dORM.QueryTable(new(StrPk)).Filter(`id`, pk).One(&vForTesting) - throwFailNow(t, AssertIs(err, nil)) - throwFailNow(t, AssertIs(vForTesting.Value, value)) - - value2 := `s8s5da7as` - strPkForUpsert := &StrPk{ - Id: pk, - Value: value2, - } - - _, err = dORM.InsertOrUpdate(strPkForUpsert, `id`) - if err != nil { - fmt.Println(err) - if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" { - } else if err == ErrLastInsertIdUnavailable { - } else { - throwFailNow(t, err) - } - } else { - var vForTesting2 StrPk - err = dORM.QueryTable(new(StrPk)).Filter(`id`, pk).One(&vForTesting2) - throwFailNow(t, AssertIs(err, nil)) - throwFailNow(t, AssertIs(vForTesting2.Value, value2)) - } -} - -func TestPSQueryBuilder(t *testing.T) { - // only test postgres - if dORM.Driver().Type() != 4 { - return - } - - var user User - var l []userProfile - o := NewOrm() - - qb, err := NewQueryBuilder("postgres") - if err != nil { - throwFailNow(t, err) - } - qb.Select("user.id", "user.user_name"). - From("user").Where("id = ?").OrderBy("user_name"). - Desc().Limit(1).Offset(0) - sql := qb.String() - err = o.Raw(sql, 2).QueryRow(&user) - if err != nil { - throwFailNow(t, err) - } - throwFail(t, AssertIs(user.UserName, "slene")) - - qb.Select("*"). - From("user_profile").InnerJoin("user"). - On("user_profile.id = user.id") - sql = qb.String() - num, err := o.Raw(sql).QueryRows(&l) - if err != nil { - throwFailNow(t, err) - } - throwFailNow(t, AssertIs(num, 1)) - throwFailNow(t, AssertIs(l[0].UserName, "astaxie")) - throwFailNow(t, AssertIs(l[0].Age, 30)) -} diff --git a/client/orm/qb.go b/orm/qb.go similarity index 96% rename from client/orm/qb.go rename to orm/qb.go index c82d2255..e0655a17 100644 --- a/client/orm/qb.go +++ b/orm/qb.go @@ -52,7 +52,7 @@ func NewQueryBuilder(driver string) (qb QueryBuilder, err error) { } else if driver == "tidb" { qb = new(TiDBQueryBuilder) } else if driver == "postgres" { - qb = new(PostgresQueryBuilder) + err = errors.New("postgres query builder is not supported yet") } else if driver == "sqlite" { err = errors.New("sqlite query builder is not supported yet") } else { diff --git a/client/orm/qb_mysql.go b/orm/qb_mysql.go similarity index 71% rename from client/orm/qb_mysql.go rename to orm/qb_mysql.go index 19130496..23bdc9ee 100644 --- a/client/orm/qb_mysql.go +++ b/orm/qb_mysql.go @@ -25,144 +25,144 @@ const CommaSpace = ", " // MySQLQueryBuilder is the SQL build type MySQLQueryBuilder struct { - tokens []string + Tokens []string } // Select will join the fields func (qb *MySQLQueryBuilder) Select(fields ...string) QueryBuilder { - qb.tokens = append(qb.tokens, "SELECT", strings.Join(fields, CommaSpace)) + qb.Tokens = append(qb.Tokens, "SELECT", strings.Join(fields, CommaSpace)) return qb } // ForUpdate add the FOR UPDATE clause func (qb *MySQLQueryBuilder) ForUpdate() QueryBuilder { - qb.tokens = append(qb.tokens, "FOR UPDATE") + qb.Tokens = append(qb.Tokens, "FOR UPDATE") return qb } // From join the tables func (qb *MySQLQueryBuilder) From(tables ...string) QueryBuilder { - qb.tokens = append(qb.tokens, "FROM", strings.Join(tables, CommaSpace)) + qb.Tokens = append(qb.Tokens, "FROM", strings.Join(tables, CommaSpace)) return qb } // InnerJoin INNER JOIN the table func (qb *MySQLQueryBuilder) InnerJoin(table string) QueryBuilder { - qb.tokens = append(qb.tokens, "INNER JOIN", table) + qb.Tokens = append(qb.Tokens, "INNER JOIN", table) return qb } // LeftJoin LEFT JOIN the table func (qb *MySQLQueryBuilder) LeftJoin(table string) QueryBuilder { - qb.tokens = append(qb.tokens, "LEFT JOIN", table) + qb.Tokens = append(qb.Tokens, "LEFT JOIN", table) return qb } // RightJoin RIGHT JOIN the table func (qb *MySQLQueryBuilder) RightJoin(table string) QueryBuilder { - qb.tokens = append(qb.tokens, "RIGHT JOIN", table) + qb.Tokens = append(qb.Tokens, "RIGHT JOIN", table) return qb } // On join with on cond func (qb *MySQLQueryBuilder) On(cond string) QueryBuilder { - qb.tokens = append(qb.tokens, "ON", cond) + qb.Tokens = append(qb.Tokens, "ON", cond) return qb } // Where join the Where cond func (qb *MySQLQueryBuilder) Where(cond string) QueryBuilder { - qb.tokens = append(qb.tokens, "WHERE", cond) + qb.Tokens = append(qb.Tokens, "WHERE", cond) return qb } // And join the and cond func (qb *MySQLQueryBuilder) And(cond string) QueryBuilder { - qb.tokens = append(qb.tokens, "AND", cond) + qb.Tokens = append(qb.Tokens, "AND", cond) return qb } // Or join the or cond func (qb *MySQLQueryBuilder) Or(cond string) QueryBuilder { - qb.tokens = append(qb.tokens, "OR", cond) + qb.Tokens = append(qb.Tokens, "OR", cond) return qb } // In join the IN (vals) func (qb *MySQLQueryBuilder) In(vals ...string) QueryBuilder { - qb.tokens = append(qb.tokens, "IN", "(", strings.Join(vals, CommaSpace), ")") + qb.Tokens = append(qb.Tokens, "IN", "(", strings.Join(vals, CommaSpace), ")") return qb } // OrderBy join the Order by fields func (qb *MySQLQueryBuilder) OrderBy(fields ...string) QueryBuilder { - qb.tokens = append(qb.tokens, "ORDER BY", strings.Join(fields, CommaSpace)) + qb.Tokens = append(qb.Tokens, "ORDER BY", strings.Join(fields, CommaSpace)) return qb } // Asc join the asc func (qb *MySQLQueryBuilder) Asc() QueryBuilder { - qb.tokens = append(qb.tokens, "ASC") + qb.Tokens = append(qb.Tokens, "ASC") return qb } // Desc join the desc func (qb *MySQLQueryBuilder) Desc() QueryBuilder { - qb.tokens = append(qb.tokens, "DESC") + qb.Tokens = append(qb.Tokens, "DESC") return qb } // Limit join the limit num func (qb *MySQLQueryBuilder) Limit(limit int) QueryBuilder { - qb.tokens = append(qb.tokens, "LIMIT", strconv.Itoa(limit)) + qb.Tokens = append(qb.Tokens, "LIMIT", strconv.Itoa(limit)) return qb } // Offset join the offset num func (qb *MySQLQueryBuilder) Offset(offset int) QueryBuilder { - qb.tokens = append(qb.tokens, "OFFSET", strconv.Itoa(offset)) + qb.Tokens = append(qb.Tokens, "OFFSET", strconv.Itoa(offset)) return qb } // GroupBy join the Group by fields func (qb *MySQLQueryBuilder) GroupBy(fields ...string) QueryBuilder { - qb.tokens = append(qb.tokens, "GROUP BY", strings.Join(fields, CommaSpace)) + qb.Tokens = append(qb.Tokens, "GROUP BY", strings.Join(fields, CommaSpace)) return qb } // Having join the Having cond func (qb *MySQLQueryBuilder) Having(cond string) QueryBuilder { - qb.tokens = append(qb.tokens, "HAVING", cond) + qb.Tokens = append(qb.Tokens, "HAVING", cond) return qb } // Update join the update table func (qb *MySQLQueryBuilder) Update(tables ...string) QueryBuilder { - qb.tokens = append(qb.tokens, "UPDATE", strings.Join(tables, CommaSpace)) + qb.Tokens = append(qb.Tokens, "UPDATE", strings.Join(tables, CommaSpace)) return qb } // Set join the set kv func (qb *MySQLQueryBuilder) Set(kv ...string) QueryBuilder { - qb.tokens = append(qb.tokens, "SET", strings.Join(kv, CommaSpace)) + qb.Tokens = append(qb.Tokens, "SET", strings.Join(kv, CommaSpace)) return qb } // Delete join the Delete tables func (qb *MySQLQueryBuilder) Delete(tables ...string) QueryBuilder { - qb.tokens = append(qb.tokens, "DELETE") + qb.Tokens = append(qb.Tokens, "DELETE") if len(tables) != 0 { - qb.tokens = append(qb.tokens, strings.Join(tables, CommaSpace)) + qb.Tokens = append(qb.Tokens, strings.Join(tables, CommaSpace)) } return qb } // InsertInto join the insert SQL func (qb *MySQLQueryBuilder) InsertInto(table string, fields ...string) QueryBuilder { - qb.tokens = append(qb.tokens, "INSERT INTO", table) + qb.Tokens = append(qb.Tokens, "INSERT INTO", table) if len(fields) != 0 { fieldsStr := strings.Join(fields, CommaSpace) - qb.tokens = append(qb.tokens, "(", fieldsStr, ")") + qb.Tokens = append(qb.Tokens, "(", fieldsStr, ")") } return qb } @@ -170,7 +170,7 @@ func (qb *MySQLQueryBuilder) InsertInto(table string, fields ...string) QueryBui // Values join the Values(vals) func (qb *MySQLQueryBuilder) Values(vals ...string) QueryBuilder { valsStr := strings.Join(vals, CommaSpace) - qb.tokens = append(qb.tokens, "VALUES", "(", valsStr, ")") + qb.Tokens = append(qb.Tokens, "VALUES", "(", valsStr, ")") return qb } @@ -179,9 +179,7 @@ func (qb *MySQLQueryBuilder) Subquery(sub string, alias string) string { return fmt.Sprintf("(%s) AS %s", sub, alias) } -// String join all tokens +// String join all Tokens func (qb *MySQLQueryBuilder) String() string { - s := strings.Join(qb.tokens, " ") - qb.tokens = qb.tokens[:0] - return s + return strings.Join(qb.Tokens, " ") } diff --git a/adapter/orm/qb_tidb.go b/orm/qb_tidb.go similarity index 60% rename from adapter/orm/qb_tidb.go rename to orm/qb_tidb.go index 18631ef0..87b3ae84 100644 --- a/adapter/orm/qb_tidb.go +++ b/orm/qb_tidb.go @@ -15,133 +15,168 @@ package orm import ( - "github.com/astaxie/beego/client/orm" + "fmt" + "strconv" + "strings" ) // TiDBQueryBuilder is the SQL build -type TiDBQueryBuilder orm.TiDBQueryBuilder +type TiDBQueryBuilder struct { + Tokens []string +} // Select will join the fields func (qb *TiDBQueryBuilder) Select(fields ...string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).Select(fields...) + qb.Tokens = append(qb.Tokens, "SELECT", strings.Join(fields, CommaSpace)) + return qb } // ForUpdate add the FOR UPDATE clause func (qb *TiDBQueryBuilder) ForUpdate() QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).ForUpdate() + qb.Tokens = append(qb.Tokens, "FOR UPDATE") + return qb } // From join the tables func (qb *TiDBQueryBuilder) From(tables ...string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).From(tables...) + qb.Tokens = append(qb.Tokens, "FROM", strings.Join(tables, CommaSpace)) + return qb } // InnerJoin INNER JOIN the table func (qb *TiDBQueryBuilder) InnerJoin(table string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).InnerJoin(table) + qb.Tokens = append(qb.Tokens, "INNER JOIN", table) + return qb } // LeftJoin LEFT JOIN the table func (qb *TiDBQueryBuilder) LeftJoin(table string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).LeftJoin(table) + qb.Tokens = append(qb.Tokens, "LEFT JOIN", table) + return qb } // RightJoin RIGHT JOIN the table func (qb *TiDBQueryBuilder) RightJoin(table string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).RightJoin(table) + qb.Tokens = append(qb.Tokens, "RIGHT JOIN", table) + return qb } // On join with on cond func (qb *TiDBQueryBuilder) On(cond string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).On(cond) + qb.Tokens = append(qb.Tokens, "ON", cond) + return qb } // Where join the Where cond func (qb *TiDBQueryBuilder) Where(cond string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).Where(cond) + qb.Tokens = append(qb.Tokens, "WHERE", cond) + return qb } // And join the and cond func (qb *TiDBQueryBuilder) And(cond string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).And(cond) + qb.Tokens = append(qb.Tokens, "AND", cond) + return qb } // Or join the or cond func (qb *TiDBQueryBuilder) Or(cond string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).Or(cond) + qb.Tokens = append(qb.Tokens, "OR", cond) + return qb } // In join the IN (vals) func (qb *TiDBQueryBuilder) In(vals ...string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).In(vals...) + qb.Tokens = append(qb.Tokens, "IN", "(", strings.Join(vals, CommaSpace), ")") + return qb } // OrderBy join the Order by fields func (qb *TiDBQueryBuilder) OrderBy(fields ...string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).OrderBy(fields...) + qb.Tokens = append(qb.Tokens, "ORDER BY", strings.Join(fields, CommaSpace)) + return qb } // Asc join the asc func (qb *TiDBQueryBuilder) Asc() QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).Asc() + qb.Tokens = append(qb.Tokens, "ASC") + return qb } // Desc join the desc func (qb *TiDBQueryBuilder) Desc() QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).Desc() + qb.Tokens = append(qb.Tokens, "DESC") + return qb } // Limit join the limit num func (qb *TiDBQueryBuilder) Limit(limit int) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).Limit(limit) + qb.Tokens = append(qb.Tokens, "LIMIT", strconv.Itoa(limit)) + return qb } // Offset join the offset num func (qb *TiDBQueryBuilder) Offset(offset int) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).Offset(offset) + qb.Tokens = append(qb.Tokens, "OFFSET", strconv.Itoa(offset)) + return qb } // GroupBy join the Group by fields func (qb *TiDBQueryBuilder) GroupBy(fields ...string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).GroupBy(fields...) + qb.Tokens = append(qb.Tokens, "GROUP BY", strings.Join(fields, CommaSpace)) + return qb } // Having join the Having cond func (qb *TiDBQueryBuilder) Having(cond string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).Having(cond) + qb.Tokens = append(qb.Tokens, "HAVING", cond) + return qb } // Update join the update table func (qb *TiDBQueryBuilder) Update(tables ...string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).Update(tables...) + qb.Tokens = append(qb.Tokens, "UPDATE", strings.Join(tables, CommaSpace)) + return qb } // Set join the set kv func (qb *TiDBQueryBuilder) Set(kv ...string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).Set(kv...) + qb.Tokens = append(qb.Tokens, "SET", strings.Join(kv, CommaSpace)) + return qb } // Delete join the Delete tables func (qb *TiDBQueryBuilder) Delete(tables ...string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).Delete(tables...) + qb.Tokens = append(qb.Tokens, "DELETE") + if len(tables) != 0 { + qb.Tokens = append(qb.Tokens, strings.Join(tables, CommaSpace)) + } + return qb } // InsertInto join the insert SQL func (qb *TiDBQueryBuilder) InsertInto(table string, fields ...string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).InsertInto(table, fields...) + qb.Tokens = append(qb.Tokens, "INSERT INTO", table) + if len(fields) != 0 { + fieldsStr := strings.Join(fields, CommaSpace) + qb.Tokens = append(qb.Tokens, "(", fieldsStr, ")") + } + return qb } // Values join the Values(vals) func (qb *TiDBQueryBuilder) Values(vals ...string) QueryBuilder { - return (*orm.TiDBQueryBuilder)(qb).Values(vals...) + valsStr := strings.Join(vals, CommaSpace) + qb.Tokens = append(qb.Tokens, "VALUES", "(", valsStr, ")") + return qb } // Subquery join the sub as alias func (qb *TiDBQueryBuilder) Subquery(sub string, alias string) string { - return (*orm.TiDBQueryBuilder)(qb).Subquery(sub, alias) + return fmt.Sprintf("(%s) AS %s", sub, alias) } // String join all Tokens func (qb *TiDBQueryBuilder) String() string { - return (*orm.TiDBQueryBuilder)(qb).String() + return strings.Join(qb.Tokens, " ") } diff --git a/client/orm/types.go b/orm/types.go similarity index 77% rename from client/orm/types.go rename to orm/types.go index 34c61d51..2fd10774 100644 --- a/client/orm/types.go +++ b/orm/types.go @@ -19,67 +19,8 @@ import ( "database/sql" "reflect" "time" - - "github.com/astaxie/beego/core/utils" ) -// TableNaming is usually used by model -// when you custom your table name, please implement this interfaces -// for example: -// type User struct { -// ... -// } -// func (u *User) TableName() string { -// return "USER_TABLE" -// } -type TableNameI interface { - TableName() string -} - -// TableEngineI is usually used by model -// when you want to use specific engine, like myisam, you can implement this interface -// for example: -// type User struct { -// ... -// } -// func (u *User) TableEngine() string { -// return "myisam" -// } -type TableEngineI interface { - TableEngine() string -} - -// TableIndexI is usually used by model -// when you want to create indexes, you can implement this interface -// for example: -// type User struct { -// ... -// } -// func (u *User) TableIndex() [][]string { -// return [][]string{{"Name"}} -// } -type TableIndexI interface { - TableIndex() [][]string -} - -// TableUniqueI is usually used by model -// when you want to create unique indexes, you can implement this interface -// for example: -// type User struct { -// ... -// } -// func (u *User) TableUnique() [][]string { -// return [][]string{{"Email"}} -// } -type TableUniqueI interface { - TableUnique() [][]string -} - -// IsApplicableTableForDB if return false, we won't create table to this db -type IsApplicableTableForDB interface { - IsApplicableTableForDB(db string) bool -} - // Driver define database driver type Driver interface { Name() string @@ -94,43 +35,35 @@ type Fielder interface { RawValue() interface{} } -type TxBeginner interface { - //self control transaction - Begin() (TxOrmer, error) - BeginWithCtx(ctx context.Context) (TxOrmer, error) - BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) - BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) - - //closure control transaction - DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error - DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error - DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error - DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error -} - -type TxCommitter interface { - Commit() error - Rollback() error -} - -//Data Manipulation Language -type DML interface { +// Ormer define the orm interface +type Ormer interface { + // read data to model + // for example: + // this will find User by Id field + // u = &User{Id: user.Id} + // err = Ormer.Read(u) + // this will find User by UserName field + // u = &User{UserName: "astaxie", Password: "pass"} + // err = Ormer.Read(u, "UserName") + Read(md interface{}, cols ...string) error + // Like Read(), but with "FOR UPDATE" clause, useful in transaction. + // Some databases are not support this feature. + ReadForUpdate(md interface{}, cols ...string) error + // Try to read a row from the database, or insert one if it doesn't exist + ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) // insert model data to database // for example: // user := new(User) // id, err = Ormer.Insert(user) // user must be a pointer and Insert will set user's pk field - Insert(md interface{}) (int64, error) - InsertWithCtx(ctx context.Context, md interface{}) (int64, error) + Insert(interface{}) (int64, error) // mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value") // if colu type is integer : can use(+-*/), string : convert(colu,"value") // postgres: InsertOrUpdate(model,"conflictColumnName") or InsertOrUpdate(model,"conflictColumnName","colu=colu+value") // if colu type is integer : can use(+-*/), string : colu || "value" InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) - InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) // insert some models to database InsertMulti(bulk int, mds interface{}) (int64, error) - InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) // update model to database. // cols set the columns those want to update. // find model by Id(pk) field and update columns specified by fields, if cols is null then update all columns @@ -141,90 +74,61 @@ type DML interface { // user.Extra.Data = "orm" // num, err = Ormer.Update(&user, "Langs", "Extra") Update(md interface{}, cols ...string) (int64, error) - UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) // delete model in database Delete(md interface{}, cols ...string) (int64, error) - DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) - - // return a raw query seter for raw sql string. - // for example: - // ormer.Raw("UPDATE `user` SET `user_name` = ? WHERE `user_name` = ?", "slene", "testing").Exec() - // // update user testing's name to slene - Raw(query string, args ...interface{}) RawSeter - RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter -} - -// Data Query Language -type DQL interface { - // read data to model - // for example: - // this will find User by Id field - // u = &User{Id: user.Id} - // err = Ormer.Read(u) - // this will find User by UserName field - // u = &User{UserName: "astaxie", Password: "pass"} - // err = Ormer.Read(u, "UserName") - Read(md interface{}, cols ...string) error - ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error - - // Like Read(), but with "FOR UPDATE" clause, useful in transaction. - // Some databases are not support this feature. - ReadForUpdate(md interface{}, cols ...string) error - ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error - - // Try to read a row from the database, or insert one if it doesn't exist - ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) - ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) - // load related models to md model. // args are limit, offset int and order string. // // example: // Ormer.LoadRelated(post,"Tags") // for _,tag := range post.Tags{...} - // hints.DefaultRelDepth useDefaultRelsDepth ; or depth 0 - // hints.RelDepth loadRelationDepth - // hints.Limit limit default limit 1000 - // hints.Offset int offset default offset 0 - // hints.OrderBy string order for example : "-Id" + //args[0] bool true useDefaultRelsDepth ; false depth 0 + //args[0] int loadRelationDepth + //args[1] int limit default limit 1000 + //args[2] int offset default offset 0 + //args[3] string order for example : "-Id" // make sure the relation is defined in model struct tags. - LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) - LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) - + LoadRelated(md interface{}, name string, args ...interface{}) (int64, error) // create a models to models queryer // for example: // post := Post{Id: 4} // m2m := Ormer.QueryM2M(&post, "Tags") QueryM2M(md interface{}, name string) QueryM2Mer - QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer - // return a QuerySeter for table operations. // table name can be string or struct. // e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)), QueryTable(ptrStructOrTableName interface{}) QuerySeter - QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) QuerySeter - - DBStats() *sql.DBStats -} - -type DriverGetter interface { + // switch to another registered database driver by given name. + Using(name string) error + // begin transaction + // for example: + // o := NewOrm() + // err := o.Begin() + // ... + // err = o.Rollback() + Begin() error + // begin transaction with provided context and option + // the provided context is used until the transaction is committed or rolled back. + // if the context is canceled, the transaction will be rolled back. + // the provided TxOptions is optional and may be nil if defaults should be used. + // if a non-default isolation level is used that the driver doesn't support, an error will be returned. + // for example: + // o := NewOrm() + // err := o.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead}) + // ... + // err = o.Rollback() + BeginTx(ctx context.Context, opts *sql.TxOptions) error + // commit transaction + Commit() error + // rollback transaction + Rollback() error + // return a raw query seter for raw sql string. + // for example: + // ormer.Raw("UPDATE `user` SET `user_name` = ? WHERE `user_name` = ?", "slene", "testing").Exec() + // // update user testing's name to slene + Raw(query string, args ...interface{}) RawSeter Driver() Driver -} - -type ormer interface { - DQL - DML - DriverGetter -} - -type Ormer interface { - ormer - TxBeginner -} - -type TxOrmer interface { - ormer - TxCommitter + DBStats() *sql.DBStats } // Inserter insert prepared statement @@ -289,21 +193,6 @@ type QuerySeter interface { // for example: // qs.OrderBy("-status") OrderBy(exprs ...string) QuerySeter - // add FORCE INDEX expression. - // for example: - // qs.ForceIndex(`idx_name1`,`idx_name2`) - // ForceIndex, UseIndex , IgnoreIndex are mutually exclusive - ForceIndex(indexes ...string) QuerySeter - // add USE INDEX expression. - // for example: - // qs.UseIndex(`idx_name1`,`idx_name2`) - // ForceIndex, UseIndex , IgnoreIndex are mutually exclusive - UseIndex(indexes ...string) QuerySeter - // add IGNORE INDEX expression. - // for example: - // qs.IgnoreIndex(`idx_name1`,`idx_name2`) - // ForceIndex, UseIndex , IgnoreIndex are mutually exclusive - IgnoreIndex(indexes ...string) QuerySeter // set relation model to query together. // it will query relation models and assign to parent model. // for example: @@ -340,7 +229,7 @@ type QuerySeter interface { // }) // user slene's name will change to slene2 Update(values Params) (int64, error) // delete from table - // for example: + //for example: // num ,err = qs.Filter("user_name__in", "testing1", "testing2").Delete() // //delete two user who's name is testing1 or testing2 Delete() (int64, error) @@ -425,8 +314,8 @@ type QueryM2Mer interface { // remove models following the origin model relationship // only delete rows from m2m table // for example: - // tag3 := &Tag{Id:5,Name: "TestTag3"} - // num, err = m2m.Remove(tag3) + //tag3 := &Tag{Id:5,Name: "TestTag3"} + //num, err = m2m.Remove(tag3) Remove(...interface{}) (int64, error) // check model is existed in relationship of origin model Exist(interface{}) bool @@ -448,10 +337,10 @@ type RawPreparer interface { // sql := fmt.Sprintf("SELECT %sid%s,%sname%s FROM %suser%s WHERE id = ?",Q,Q,Q,Q,Q,Q) // rs := Ormer.Raw(sql, 1) type RawSeter interface { - // execute sql and get result + //execute sql and get result Exec() (sql.Result, error) - // query data and map to container - // for example: + //query data and map to container + //for example: // var name string // var id int // rs.QueryRow(&id,&name) // id==2 name=="slene" @@ -507,11 +396,11 @@ type RawSeter interface { type stmtQuerier interface { Close() error Exec(args ...interface{}) (sql.Result, error) - // ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) + //ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) Query(args ...interface{}) (*sql.Rows, error) - // QueryContext(args ...interface{}) (*sql.Rows, error) + //QueryContext(args ...interface{}) (*sql.Rows, error) QueryRow(args ...interface{}) *sql.Row - // QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row + //QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row } // db querier @@ -549,27 +438,24 @@ type txEnder interface { // base database struct type dbBaser interface { Read(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string, bool) error - ReadBatch(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, *time.Location, []string) (int64, error) - Count(dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error) - ReadValues(dbQuerier, *querySet, *modelInfo, *Condition, []string, interface{}, *time.Location) (int64, error) - Insert(dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error) InsertOrUpdate(dbQuerier, *modelInfo, reflect.Value, *alias, ...string) (int64, error) InsertMulti(dbQuerier, *modelInfo, reflect.Value, int, *time.Location) (int64, error) InsertValue(dbQuerier, *modelInfo, bool, []string, []interface{}) (int64, error) InsertStmt(stmtQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error) - Update(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error) - UpdateBatch(dbQuerier, *querySet, *modelInfo, *Condition, Params, *time.Location) (int64, error) - Delete(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error) - DeleteBatch(dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error) - + ReadBatch(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, *time.Location, []string) (int64, error) SupportUpdateJoin() bool + UpdateBatch(dbQuerier, *querySet, *modelInfo, *Condition, Params, *time.Location) (int64, error) + DeleteBatch(dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error) + Count(dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error) OperatorSQL(string) string GenerateOperatorSQL(*modelInfo, *fieldInfo, string, []interface{}, *time.Location) (string, []interface{}) GenerateOperatorLeftCol(*fieldInfo, string, *string) PrepareInsert(dbQuerier, *modelInfo) (stmtQuerier, string, error) + ReadValues(dbQuerier, *querySet, *modelInfo, *Condition, []string, interface{}, *time.Location) (int64, error) + RowsTo(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, string, string, *time.Location) (int64, error) MaxLimit() uint64 TableQuote() string ReplaceMarks(*string) @@ -584,6 +470,4 @@ type dbBaser interface { IndexExists(dbQuerier, string, string) bool collectFieldValue(*modelInfo, *fieldInfo, reflect.Value, bool, *time.Location) (interface{}, error) setval(dbQuerier, *modelInfo, []string) error - - GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string } diff --git a/client/orm/utils.go b/orm/utils.go similarity index 100% rename from client/orm/utils.go rename to orm/utils.go diff --git a/adapter/orm/utils_test.go b/orm/utils_test.go similarity index 100% rename from adapter/orm/utils_test.go rename to orm/utils_test.go diff --git a/server/web/parser.go b/parser.go similarity index 93% rename from server/web/parser.go rename to parser.go index c3434501..3a311894 100644 --- a/server/web/parser.go +++ b/parser.go @@ -12,13 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "encoding/json" "errors" "fmt" "go/ast" + "go/parser" + "go/token" "io/ioutil" "os" "path/filepath" @@ -28,19 +30,16 @@ import ( "strings" "unicode" - "golang.org/x/tools/go/packages" - - "github.com/astaxie/beego/core/logs" - - "github.com/astaxie/beego/core/utils" - "github.com/astaxie/beego/server/web/context/param" + "github.com/astaxie/beego/context/param" + "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/utils" ) var globalRouterTemplate = `package {{.routersDir}} import ( - beego "github.com/astaxie/beego/server/web" - "github.com/astaxie/beego/server/web/context/param"{{.globalimport}} + "github.com/astaxie/beego" + "github.com/astaxie/beego/context/param"{{.globalimport}} ) func init() { @@ -77,7 +76,7 @@ func init() { pkgLastupdate = make(map[string]int64) } -func parserPkg(pkgRealpath string) error { +func parserPkg(pkgRealpath, pkgpath string) error { rep := strings.NewReplacer("\\", "_", "/", "_", ".", "_") commentFilename, _ = filepath.Rel(AppPath, pkgRealpath) commentFilename = commentPrefix + rep.Replace(commentFilename) + ".go" @@ -86,23 +85,24 @@ func parserPkg(pkgRealpath string) error { return nil } genInfoList = make(map[string][]ControllerComments) - pkgs, err := packages.Load(&packages.Config{ - Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedSyntax, - Dir: pkgRealpath, - }, "./...") + fileSet := token.NewFileSet() + astPkgs, err := parser.ParseDir(fileSet, pkgRealpath, func(info os.FileInfo) bool { + name := info.Name() + return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") + }, parser.ParseComments) if err != nil { return err } - for _, pkg := range pkgs { - for _, fl := range pkg.Syntax { + for _, pkg := range astPkgs { + for _, fl := range pkg.Files { for _, d := range fl.Decls { switch specDecl := d.(type) { case *ast.FuncDecl: if specDecl.Recv != nil { exp, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr) // Check that the type is correct first beforing throwing to parser if ok { - parserComments(specDecl, fmt.Sprint(exp.X), pkg.PkgPath) + parserComments(specDecl, fmt.Sprint(exp.X), pkgpath) } } } @@ -221,7 +221,7 @@ func buildMethodParams(funcParams []*ast.Field, pc *parsedComment) []*param.Meth func buildMethodParam(fparam *ast.Field, name string, pc *parsedComment) *param.MethodParam { options := []param.MethodParamOption{} if cparam, ok := pc.params[name]; ok { - // Build param from comment info + //Build param from comment info name = cparam.name if cparam.required { options = append(options, param.IsRequired) @@ -358,10 +358,10 @@ filterLoop: methods := matches[2] if methods == "" { pc.methods = []string{"get"} - // pc.hasGet = true + //pc.hasGet = true } else { pc.methods = strings.Split(methods, ",") - // pc.hasGet = strings.Contains(methods, "get") + //pc.hasGet = strings.Contains(methods, "get") } pcs = append(pcs, pc) } else { @@ -566,17 +566,8 @@ func getpathTime(pkgRealpath string) (lastupdate int64, err error) { return lastupdate, err } for _, f := range fl { - var t int64 - if f.IsDir() { - t, err = getpathTime(filepath.Join(pkgRealpath, f.Name())) - if err != nil { - return lastupdate, err - } - } else { - t = f.ModTime().UnixNano() - } - if lastupdate < t { - lastupdate = t + if lastupdate < f.ModTime().UnixNano() { + lastupdate = f.ModTime().UnixNano() } } return lastupdate, nil diff --git a/server/web/filter/apiauth/apiauth.go b/plugins/apiauth/apiauth.go similarity index 77% rename from server/web/filter/apiauth/apiauth.go rename to plugins/apiauth/apiauth.go index 58153f1d..10e25f3f 100644 --- a/server/web/filter/apiauth/apiauth.go +++ b/plugins/apiauth/apiauth.go @@ -65,15 +65,15 @@ import ( "sort" "time" - "github.com/astaxie/beego/server/web" - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego" + "github.com/astaxie/beego/context" ) -// AppIDToAppSecret gets appsecret through appid +// AppIDToAppSecret is used to get appsecret throw appid type AppIDToAppSecret func(string) string -// APIBasicAuth uses the basic appid/appkey as the AppIdToAppSecret -func APIBasicAuth(appid, appkey string) web.FilterFunc { +// APIBasicAuth use the basic appid/appkey as the AppIdToAppSecret +func APIBasicAuth(appid, appkey string) beego.FilterFunc { ft := func(aid string) string { if aid == appid { return appkey @@ -83,53 +83,56 @@ func APIBasicAuth(appid, appkey string) web.FilterFunc { return APISecretAuth(ft, 300) } -// APISecretAuth uses AppIdToAppSecret verify and -func APISecretAuth(f AppIDToAppSecret, timeout int) web.FilterFunc { +// APIBaiscAuth calls APIBasicAuth for previous callers +func APIBaiscAuth(appid, appkey string) beego.FilterFunc { + return APIBasicAuth(appid, appkey) +} + +// APISecretAuth use AppIdToAppSecret verify and +func APISecretAuth(f AppIDToAppSecret, timeout int) beego.FilterFunc { return func(ctx *context.Context) { if ctx.Input.Query("appid") == "" { ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("missing query parameter: appid") + ctx.WriteString("miss query param: appid") return } appsecret := f(ctx.Input.Query("appid")) if appsecret == "" { ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("appid query parameter missing") + ctx.WriteString("not exist this appid") return } if ctx.Input.Query("signature") == "" { ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("missing query parameter: signature") - + ctx.WriteString("miss query param: signature") return } if ctx.Input.Query("timestamp") == "" { ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("missing query parameter: timestamp") + ctx.WriteString("miss query param: timestamp") return } u, err := time.Parse("2006-01-02 15:04:05", ctx.Input.Query("timestamp")) if err != nil { ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("incorrect timestamp format. Should be in the form 2006-01-02 15:04:05") - + ctx.WriteString("timestamp format is error, should 2006-01-02 15:04:05") return } t := time.Now() if t.Sub(u).Seconds() > float64(timeout) { ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("request timer timeout exceeded. Please try again") + ctx.WriteString("timeout! the request time is long ago, please try again") return } if ctx.Input.Query("signature") != Signature(appsecret, ctx.Input.Method(), ctx.Request.Form, ctx.Input.URL()) { ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("authentication failed") + ctx.WriteString("auth failed") } } } -// Signature generates signature with appsecret/method/params/RequestURI +// Signature used to generate signature with the appsecret/method/params/RequestURI func Signature(appsecret, method string, params url.Values, RequestURL string) (result string) { var b bytes.Buffer keys := make([]string, len(params)) diff --git a/adapter/plugins/apiauth/apiauth_test.go b/plugins/apiauth/apiauth_test.go similarity index 100% rename from adapter/plugins/apiauth/apiauth_test.go rename to plugins/apiauth/apiauth_test.go diff --git a/server/web/filter/auth/basic.go b/plugins/auth/basic.go similarity index 94% rename from server/web/filter/auth/basic.go rename to plugins/auth/basic.go index ee6af6c3..c478044a 100644 --- a/server/web/filter/auth/basic.go +++ b/plugins/auth/basic.go @@ -40,14 +40,14 @@ import ( "net/http" "strings" - "github.com/astaxie/beego/server/web" - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego" + "github.com/astaxie/beego/context" ) var defaultRealm = "Authorization Required" // Basic is the http basic auth -func Basic(username string, password string) web.FilterFunc { +func Basic(username string, password string) beego.FilterFunc { secrets := func(user, pass string) bool { return user == username && pass == password } @@ -55,7 +55,7 @@ func Basic(username string, password string) web.FilterFunc { } // NewBasicAuthenticator return the BasicAuth -func NewBasicAuthenticator(secrets SecretProvider, Realm string) web.FilterFunc { +func NewBasicAuthenticator(secrets SecretProvider, Realm string) beego.FilterFunc { return func(ctx *context.Context) { a := &BasicAuth{Secrets: secrets, Realm: Realm} if username := a.CheckAuth(ctx.Request); username == "" { diff --git a/server/web/filter/authz/authz.go b/plugins/authz/authz.go similarity index 94% rename from server/web/filter/authz/authz.go rename to plugins/authz/authz.go index 857c52f2..9dc0db76 100644 --- a/server/web/filter/authz/authz.go +++ b/plugins/authz/authz.go @@ -40,17 +40,15 @@ package authz import ( - "net/http" - + "github.com/astaxie/beego" + "github.com/astaxie/beego/context" "github.com/casbin/casbin" - - "github.com/astaxie/beego/server/web" - "github.com/astaxie/beego/server/web/context" + "net/http" ) // NewAuthorizer returns the authorizer. // Use a casbin enforcer as input -func NewAuthorizer(e *casbin.Enforcer) web.FilterFunc { +func NewAuthorizer(e *casbin.Enforcer) beego.FilterFunc { return func(ctx *context.Context) { a := &BasicAuthorizer{enforcer: e} diff --git a/adapter/plugins/authz/authz_model.conf b/plugins/authz/authz_model.conf similarity index 100% rename from adapter/plugins/authz/authz_model.conf rename to plugins/authz/authz_model.conf diff --git a/adapter/plugins/authz/authz_policy.csv b/plugins/authz/authz_policy.csv similarity index 100% rename from adapter/plugins/authz/authz_policy.csv rename to plugins/authz/authz_policy.csv diff --git a/adapter/plugins/authz/authz_test.go b/plugins/authz/authz_test.go similarity index 96% rename from adapter/plugins/authz/authz_test.go rename to plugins/authz/authz_test.go index 9b4f21c2..49aed84c 100644 --- a/adapter/plugins/authz/authz_test.go +++ b/plugins/authz/authz_test.go @@ -15,14 +15,13 @@ package authz import ( + "github.com/astaxie/beego" + "github.com/astaxie/beego/context" + "github.com/astaxie/beego/plugins/auth" + "github.com/casbin/casbin" "net/http" "net/http/httptest" "testing" - - beego "github.com/astaxie/beego/adapter" - "github.com/astaxie/beego/adapter/context" - "github.com/astaxie/beego/adapter/plugins/auth" - "github.com/casbin/casbin" ) func testRequest(t *testing.T, handler *beego.ControllerRegister, user string, path string, method string, code int) { diff --git a/server/web/filter/cors/cors.go b/plugins/cors/cors.go similarity index 98% rename from server/web/filter/cors/cors.go rename to plugins/cors/cors.go index 3a6905ea..45c327ab 100644 --- a/server/web/filter/cors/cors.go +++ b/plugins/cors/cors.go @@ -42,8 +42,8 @@ import ( "strings" "time" - "github.com/astaxie/beego/server/web" - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego" + "github.com/astaxie/beego/context" ) const ( @@ -187,7 +187,7 @@ func (o *Options) IsOriginAllowed(origin string) (allowed bool) { } // Allow enables CORS for requests those match the provided options. -func Allow(opts *Options) web.FilterFunc { +func Allow(opts *Options) beego.FilterFunc { // Allow default headers if nothing is specified. if len(opts.AllowHeaders) == 0 { opts.AllowHeaders = defaultAllowHeaders diff --git a/server/web/filter/cors/cors_test.go b/plugins/cors/cors_test.go similarity index 88% rename from server/web/filter/cors/cors_test.go rename to plugins/cors/cors_test.go index 7649de25..34039143 100644 --- a/server/web/filter/cors/cors_test.go +++ b/plugins/cors/cors_test.go @@ -21,8 +21,8 @@ import ( "testing" "time" - "github.com/astaxie/beego/server/web" - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego" + "github.com/astaxie/beego/context" ) // HTTPHeaderGuardRecorder is httptest.ResponseRecorder with own http.Header @@ -55,8 +55,8 @@ func (gr *HTTPHeaderGuardRecorder) Header() http.Header { func Test_AllowAll(t *testing.T) { recorder := httptest.NewRecorder() - handler := web.NewControllerRegister() - handler.InsertFilter("*", web.BeforeRouter, Allow(&Options{ + handler := beego.NewControllerRegister() + handler.InsertFilter("*", beego.BeforeRouter, Allow(&Options{ AllowAllOrigins: true, })) handler.Any("/foo", func(ctx *context.Context) { @@ -72,8 +72,8 @@ func Test_AllowAll(t *testing.T) { func Test_AllowRegexMatch(t *testing.T) { recorder := httptest.NewRecorder() - handler := web.NewControllerRegister() - handler.InsertFilter("*", web.BeforeRouter, Allow(&Options{ + handler := beego.NewControllerRegister() + handler.InsertFilter("*", beego.BeforeRouter, Allow(&Options{ AllowOrigins: []string{"https://aaa.com", "https://*.foo.com"}, })) handler.Any("/foo", func(ctx *context.Context) { @@ -92,8 +92,8 @@ func Test_AllowRegexMatch(t *testing.T) { func Test_AllowRegexNoMatch(t *testing.T) { recorder := httptest.NewRecorder() - handler := web.NewControllerRegister() - handler.InsertFilter("*", web.BeforeRouter, Allow(&Options{ + handler := beego.NewControllerRegister() + handler.InsertFilter("*", beego.BeforeRouter, Allow(&Options{ AllowOrigins: []string{"https://*.foo.com"}, })) handler.Any("/foo", func(ctx *context.Context) { @@ -112,8 +112,8 @@ func Test_AllowRegexNoMatch(t *testing.T) { func Test_OtherHeaders(t *testing.T) { recorder := httptest.NewRecorder() - handler := web.NewControllerRegister() - handler.InsertFilter("*", web.BeforeRouter, Allow(&Options{ + handler := beego.NewControllerRegister() + handler.InsertFilter("*", beego.BeforeRouter, Allow(&Options{ AllowAllOrigins: true, AllowCredentials: true, AllowMethods: []string{"PATCH", "GET"}, @@ -156,8 +156,8 @@ func Test_OtherHeaders(t *testing.T) { func Test_DefaultAllowHeaders(t *testing.T) { recorder := httptest.NewRecorder() - handler := web.NewControllerRegister() - handler.InsertFilter("*", web.BeforeRouter, Allow(&Options{ + handler := beego.NewControllerRegister() + handler.InsertFilter("*", beego.BeforeRouter, Allow(&Options{ AllowAllOrigins: true, })) handler.Any("/foo", func(ctx *context.Context) { @@ -175,8 +175,8 @@ func Test_DefaultAllowHeaders(t *testing.T) { func Test_Preflight(t *testing.T) { recorder := NewRecorder() - handler := web.NewControllerRegister() - handler.InsertFilter("*", web.BeforeRouter, Allow(&Options{ + handler := beego.NewControllerRegister() + handler.InsertFilter("*", beego.BeforeRouter, Allow(&Options{ AllowAllOrigins: true, AllowMethods: []string{"PUT", "PATCH"}, AllowHeaders: []string{"Origin", "X-whatever", "X-CaseSensitive"}, @@ -219,8 +219,8 @@ func Test_Preflight(t *testing.T) { func Benchmark_WithoutCORS(b *testing.B) { recorder := httptest.NewRecorder() - handler := web.NewControllerRegister() - web.BConfig.RunMode = web.PROD + handler := beego.NewControllerRegister() + beego.BConfig.RunMode = beego.PROD handler.Any("/foo", func(ctx *context.Context) { ctx.Output.SetStatus(500) }) @@ -233,9 +233,9 @@ func Benchmark_WithoutCORS(b *testing.B) { func Benchmark_WithCORS(b *testing.B) { recorder := httptest.NewRecorder() - handler := web.NewControllerRegister() - web.BConfig.RunMode = web.PROD - handler.InsertFilter("*", web.BeforeRouter, Allow(&Options{ + handler := beego.NewControllerRegister() + beego.BConfig.RunMode = beego.PROD + handler.InsertFilter("*", beego.BeforeRouter, Allow(&Options{ AllowAllOrigins: true, AllowCredentials: true, AllowMethods: []string{"PATCH", "GET"}, diff --git a/server/web/policy.go b/policy.go similarity index 97% rename from server/web/policy.go rename to policy.go index 14673422..ab23f927 100644 --- a/server/web/policy.go +++ b/policy.go @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "strings" - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/context" ) // PolicyFunc defines a policy function which is invoked before the controller handler is executed. diff --git a/server/web/router.go b/router.go similarity index 79% rename from server/web/router.go rename to router.go index 7bb89d82..b19a199d 100644 --- a/server/web/router.go +++ b/router.go @@ -12,24 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "errors" "fmt" "net/http" + "os" "path" + "path/filepath" "reflect" "strconv" "strings" "sync" "time" - "github.com/astaxie/beego/core/logs" - - "github.com/astaxie/beego/core/utils" - beecontext "github.com/astaxie/beego/server/web/context" - "github.com/astaxie/beego/server/web/context/param" + beecontext "github.com/astaxie/beego/context" + "github.com/astaxie/beego/context/param" + "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/toolbox" + "github.com/astaxie/beego/utils" ) // default filter execution points @@ -132,22 +134,11 @@ type ControllerRegister struct { enableFilter bool filters [FinishRouter + 1][]*FilterRouter pool sync.Pool - - // the filter created by FilterChain - chainRoot *FilterRouter - - cfg *Config } // NewControllerRegister returns a new ControllerRegister. -// Usually you should not use this method -// please use NewControllerRegisterWithCfg func NewControllerRegister() *ControllerRegister { - return NewControllerRegisterWithCfg(BeeApp.Cfg) -} - -func NewControllerRegisterWithCfg(cfg *Config) *ControllerRegister { - res := &ControllerRegister{ + return &ControllerRegister{ routers: make(map[string]*Tree), policies: make(map[string]*Tree), pool: sync.Pool{ @@ -155,10 +146,7 @@ func NewControllerRegisterWithCfg(cfg *Config) *ControllerRegister { return beecontext.NewContext() }, }, - cfg: cfg, } - res.chainRoot = newFilterRouter("/*", res.serveHttp, WithCaseSensitive(false)) - return res } // Add controller handler and pattern rules to ControllerRegister. @@ -249,7 +237,7 @@ func (p *ControllerRegister) addWithMethodParams(pattern string, c ControllerInt } func (p *ControllerRegister) addToRouter(method, pattern string, r *ControllerInfo) { - if !p.cfg.RouterCaseSensitive { + if !BConfig.RouterCaseSensitive { pattern = strings.ToLower(pattern) } if t, ok := p.routers[method]; ok { @@ -264,6 +252,45 @@ func (p *ControllerRegister) addToRouter(method, pattern string, r *ControllerIn // Include only when the Runmode is dev will generate router file in the router/auto.go from the controller // Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) func (p *ControllerRegister) Include(cList ...ControllerInterface) { + if BConfig.RunMode == DEV { + skip := make(map[string]bool, 10) + wgopath := utils.GetGOPATHs() + go111module := os.Getenv(`GO111MODULE`) + for _, c := range cList { + reflectVal := reflect.ValueOf(c) + t := reflect.Indirect(reflectVal).Type() + // for go modules + if go111module == `on` { + pkgpath := filepath.Join(WorkPath, "..", t.PkgPath()) + if utils.FileExists(pkgpath) { + if pkgpath != "" { + if _, ok := skip[pkgpath]; !ok { + skip[pkgpath] = true + parserPkg(pkgpath, t.PkgPath()) + } + } + } + } else { + if len(wgopath) == 0 { + panic("you are in dev mode. So please set gopath") + } + pkgpath := "" + for _, wg := range wgopath { + wg, _ = filepath.EvalSymlinks(filepath.Join(wg, "src", t.PkgPath())) + if utils.FileExists(wg) { + pkgpath = wg + break + } + } + if pkgpath != "" { + if _, ok := skip[pkgpath]; !ok { + skip[pkgpath] = true + parserPkg(pkgpath, t.PkgPath()) + } + } + } + } + } for _, c := range cList { reflectVal := reflect.ValueOf(c) t := reflect.Indirect(reflectVal).Type() @@ -271,7 +298,7 @@ func (p *ControllerRegister) Include(cList ...ControllerInterface) { if comm, ok := GlobalControllerRouter[key]; ok { for _, a := range comm { for _, f := range a.Filters { - p.InsertFilter(f.Pattern, f.Pos, f.Filter, WithReturnOnOutput(f.ReturnOnOutput), WithResetParams(f.ResetParams)) + p.InsertFilter(f.Pattern, f.Pos, f.Filter, f.ReturnOnOutput, f.ResetParams) } p.addWithMethodParams(a.Router, c, a.MethodParams, strings.Join(a.AllowHTTPMethods, ",")+":"+a.Method) @@ -461,32 +488,28 @@ func (p *ControllerRegister) AddAutoPrefix(prefix string, c ControllerInterface) // params is for: // 1. setting the returnOnOutput value (false allows multiple filters to execute) // 2. determining whether or not params need to be reset. -func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter FilterFunc, opts ...FilterOpt) error { - opts = append(opts, WithCaseSensitive(p.cfg.RouterCaseSensitive)) - mr := newFilterRouter(pattern, filter, opts...) +func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) error { + mr := &FilterRouter{ + tree: NewTree(), + pattern: pattern, + filterFunc: filter, + returnOnOutput: true, + } + if !BConfig.RouterCaseSensitive { + mr.pattern = strings.ToLower(pattern) + } + + paramsLen := len(params) + if paramsLen > 0 { + mr.returnOnOutput = params[0] + } + if paramsLen > 1 { + mr.resetParams = params[1] + } + mr.tree.AddRouter(pattern, true) return p.insertFilterRouter(pos, mr) } -// InsertFilterChain is similar to InsertFilter, -// but it will using chainRoot.filterFunc as input to build a new filterFunc -// for example, assume that chainRoot is funcA -// and we add new FilterChain -// fc := func(next) { -// return func(ctx) { -// // do something -// next(ctx) -// // do something -// } -// } -func (p *ControllerRegister) InsertFilterChain(pattern string, chain FilterChain, opts ...FilterOpt) { - root := p.chainRoot - filterFunc := chain(root.filterFunc) - opts = append(opts, WithCaseSensitive(p.cfg.RouterCaseSensitive)) - p.chainRoot = newFilterRouter(pattern, filterFunc, opts...) - p.chainRoot.next = root - -} - // add Filter into func (p *ControllerRegister) insertFilterRouter(pos int, mr *FilterRouter) (err error) { if pos < BeforeStatic || pos > FinishRouter { @@ -645,9 +668,23 @@ func (p *ControllerRegister) getURL(t *Tree, url, controllerName, methodName str func (p *ControllerRegister) execFilter(context *beecontext.Context, urlPath string, pos int) (started bool) { var preFilterParams map[string]string for _, filterR := range p.filters[pos] { - b, done := filterR.filter(context, urlPath, preFilterParams) - if done { - return b + if filterR.returnOnOutput && context.ResponseWriter.Started { + return true + } + if filterR.resetParams { + preFilterParams = context.Input.Params() + } + if ok := filterR.ValidRouter(urlPath, context); ok { + filterR.filterFunc(context) + if filterR.resetParams { + context.Input.ResetParams() + for k, v := range preFilterParams { + context.Input.SetParam(k, v) + } + } + } + if filterR.returnOnOutput && context.ResponseWriter.Started { + return true } } return false @@ -655,21 +692,7 @@ func (p *ControllerRegister) execFilter(context *beecontext.Context, urlPath str // Implement http.Handler interface. func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - - ctx := p.GetContext() - - ctx.Reset(rw, r) - defer p.GiveBackContext(ctx) - - var preFilterParams map[string]string - p.chainRoot.filter(ctx, p.getUrlPath(ctx), preFilterParams) -} - -func (p *ControllerRegister) serveHttp(ctx *beecontext.Context) { - var err error startTime := time.Now() - r := ctx.Request - rw := ctx.ResponseWriter.ResponseWriter var ( runRouter reflect.Type findRouter bool @@ -678,118 +701,102 @@ func (p *ControllerRegister) serveHttp(ctx *beecontext.Context) { routerInfo *ControllerInfo isRunnable bool ) + context := p.GetContext() - if p.cfg.RecoverFunc != nil { - defer p.cfg.RecoverFunc(ctx, p.cfg) + context.Reset(rw, r) + + defer p.GiveBackContext(context) + if BConfig.RecoverFunc != nil { + defer BConfig.RecoverFunc(context) } - ctx.Output.EnableGzip = p.cfg.EnableGzip + context.Output.EnableGzip = BConfig.EnableGzip - if p.cfg.RunMode == DEV { - ctx.Output.Header("Server", p.cfg.ServerName) + if BConfig.RunMode == DEV { + context.Output.Header("Server", BConfig.ServerName) } - urlPath := p.getUrlPath(ctx) + var urlPath = r.URL.Path + + if !BConfig.RouterCaseSensitive { + urlPath = strings.ToLower(urlPath) + } // filter wrong http method if !HTTPMETHOD[r.Method] { - exception("405", ctx) + exception("405", context) goto Admin } // filter for static file - if len(p.filters[BeforeStatic]) > 0 && p.execFilter(ctx, urlPath, BeforeStatic) { + if len(p.filters[BeforeStatic]) > 0 && p.execFilter(context, urlPath, BeforeStatic) { goto Admin } - serverStaticRouter(ctx) + serverStaticRouter(context) - if ctx.ResponseWriter.Started { + if context.ResponseWriter.Started { findRouter = true goto Admin } if r.Method != http.MethodGet && r.Method != http.MethodHead { - - if ctx.Input.IsUpload() { - ctx.Input.Context.Request.Body = http.MaxBytesReader(ctx.Input.Context.ResponseWriter, - ctx.Input.Context.Request.Body, - p.cfg.MaxUploadSize) - } else if p.cfg.CopyRequestBody { - // connection will close if the incoming data are larger (RFC 7231, 6.5.11) - if r.ContentLength > p.cfg.MaxMemory { - logs.Error(errors.New("payload too large")) - exception("413", ctx) - goto Admin - } - ctx.Input.CopyBody(p.cfg.MaxMemory) - } else { - ctx.Input.Context.Request.Body = http.MaxBytesReader(ctx.Input.Context.ResponseWriter, - ctx.Input.Context.Request.Body, - p.cfg.MaxMemory) - } - - err = ctx.Input.ParseFormOrMultiForm(p.cfg.MaxMemory) - if err != nil { - logs.Error(err) - if strings.Contains(err.Error(), `http: request body too large`) { - exception("413", ctx) - } else { - exception("500", ctx) - } - goto Admin + if BConfig.CopyRequestBody && !context.Input.IsUpload() { + context.Input.CopyBody(BConfig.MaxMemory) } + context.Input.ParseFormOrMulitForm(BConfig.MaxMemory) } // session init - if p.cfg.WebConfig.Session.SessionOn { - ctx.Input.CruSession, err = GlobalSessions.SessionStart(rw, r) + if BConfig.WebConfig.Session.SessionOn { + var err error + context.Input.CruSession, err = GlobalSessions.SessionStart(rw, r) if err != nil { logs.Error(err) - exception("503", ctx) + exception("503", context) goto Admin } defer func() { - if ctx.Input.CruSession != nil { - ctx.Input.CruSession.SessionRelease(nil, rw) + if context.Input.CruSession != nil { + context.Input.CruSession.SessionRelease(rw) } }() } - if len(p.filters[BeforeRouter]) > 0 && p.execFilter(ctx, urlPath, BeforeRouter) { + if len(p.filters[BeforeRouter]) > 0 && p.execFilter(context, urlPath, BeforeRouter) { goto Admin } // User can define RunController and RunMethod in filter - if ctx.Input.RunController != nil && ctx.Input.RunMethod != "" { + if context.Input.RunController != nil && context.Input.RunMethod != "" { findRouter = true - runMethod = ctx.Input.RunMethod - runRouter = ctx.Input.RunController + runMethod = context.Input.RunMethod + runRouter = context.Input.RunController } else { - routerInfo, findRouter = p.FindRouter(ctx) + routerInfo, findRouter = p.FindRouter(context) } // if no matches to url, throw a not found exception if !findRouter { - exception("404", ctx) + exception("404", context) goto Admin } - if splat := ctx.Input.Param(":splat"); splat != "" { + if splat := context.Input.Param(":splat"); splat != "" { for k, v := range strings.Split(splat, "/") { - ctx.Input.SetParam(strconv.Itoa(k), v) + context.Input.SetParam(strconv.Itoa(k), v) } } if routerInfo != nil { // store router pattern into context - ctx.Input.SetData("RouterPattern", routerInfo.pattern) + context.Input.SetData("RouterPattern", routerInfo.pattern) } // execute middleware filters - if len(p.filters[BeforeExec]) > 0 && p.execFilter(ctx, urlPath, BeforeExec) { + if len(p.filters[BeforeExec]) > 0 && p.execFilter(context, urlPath, BeforeExec) { goto Admin } // check policies - if p.execPolicy(ctx, urlPath) { + if p.execPolicy(context, urlPath) { goto Admin } @@ -797,22 +804,22 @@ func (p *ControllerRegister) serveHttp(ctx *beecontext.Context) { if routerInfo.routerType == routerTypeRESTFul { if _, ok := routerInfo.methods[r.Method]; ok { isRunnable = true - routerInfo.runFunction(ctx) + routerInfo.runFunction(context) } else { - exception("405", ctx) + exception("405", context) goto Admin } } else if routerInfo.routerType == routerTypeHandler { isRunnable = true - routerInfo.handler.ServeHTTP(ctx.ResponseWriter, ctx.Request) + routerInfo.handler.ServeHTTP(context.ResponseWriter, context.Request) } else { runRouter = routerInfo.controllerType methodParams = routerInfo.methodParams method := r.Method - if r.Method == http.MethodPost && ctx.Input.Query("_method") == http.MethodPut { + if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodPut { method = http.MethodPut } - if r.Method == http.MethodPost && ctx.Input.Query("_method") == http.MethodDelete { + if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodDelete { method = http.MethodDelete } if m, ok := routerInfo.methods[method]; ok { @@ -841,23 +848,23 @@ func (p *ControllerRegister) serveHttp(ctx *beecontext.Context) { } // call the controller init function - execController.Init(ctx, runRouter.Name(), runMethod, execController) + execController.Init(context, runRouter.Name(), runMethod, execController) // call prepare function execController.Prepare() // if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf - if p.cfg.WebConfig.EnableXSRF { + if BConfig.WebConfig.EnableXSRF { execController.XSRFToken() if r.Method == http.MethodPost || r.Method == http.MethodDelete || r.Method == http.MethodPut || - (r.Method == http.MethodPost && (ctx.Input.Query("_method") == http.MethodDelete || ctx.Input.Query("_method") == http.MethodPut)) { + (r.Method == http.MethodPost && (context.Input.Query("_method") == http.MethodDelete || context.Input.Query("_method") == http.MethodPut)) { execController.CheckXSRFCookie() } } execController.URLMapping() - if !ctx.ResponseWriter.Started { + if !context.ResponseWriter.Started { // exec main logic switch runMethod { case http.MethodGet: @@ -880,19 +887,19 @@ func (p *ControllerRegister) serveHttp(ctx *beecontext.Context) { if !execController.HandlerFunc(runMethod) { vc := reflect.ValueOf(execController) method := vc.MethodByName(runMethod) - in := param.ConvertParams(methodParams, method.Type(), ctx) + in := param.ConvertParams(methodParams, method.Type(), context) out := method.Call(in) // For backward compatibility we only handle response if we had incoming methodParams if methodParams != nil { - p.handleParamResponse(ctx, execController, out) + p.handleParamResponse(context, execController, out) } } } // render template - if !ctx.ResponseWriter.Started && ctx.Output.Status == 0 { - if p.cfg.WebConfig.AutoRender { + if !context.ResponseWriter.Started && context.Output.Status == 0 { + if BConfig.WebConfig.AutoRender { if err := execController.Render(); err != nil { logs.Error(err) } @@ -905,27 +912,27 @@ func (p *ControllerRegister) serveHttp(ctx *beecontext.Context) { } // execute middleware filters - if len(p.filters[AfterExec]) > 0 && p.execFilter(ctx, urlPath, AfterExec) { + if len(p.filters[AfterExec]) > 0 && p.execFilter(context, urlPath, AfterExec) { goto Admin } - if len(p.filters[FinishRouter]) > 0 && p.execFilter(ctx, urlPath, FinishRouter) { + if len(p.filters[FinishRouter]) > 0 && p.execFilter(context, urlPath, FinishRouter) { goto Admin } Admin: // admin module record QPS - statusCode := ctx.ResponseWriter.Status + statusCode := context.ResponseWriter.Status if statusCode == 0 { statusCode = 200 } - LogAccess(ctx, &startTime, statusCode) + LogAccess(context, &startTime, statusCode) timeDur := time.Since(startTime) - ctx.ResponseWriter.Elapsed = timeDur - if p.cfg.Listen.EnableAdmin { + context.ResponseWriter.Elapsed = timeDur + if BConfig.Listen.EnableAdmin { pattern := "" if routerInfo != nil { pattern = routerInfo.pattern @@ -936,14 +943,14 @@ Admin: if runRouter != nil { routerName = runRouter.Name() } - go StatisticsMap.AddStatistics(r.Method, r.URL.Path, routerName, timeDur) + go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, routerName, timeDur) } } - if p.cfg.RunMode == DEV && !p.cfg.Log.AccessLogs { + if BConfig.RunMode == DEV && !BConfig.Log.AccessLogs { match := map[bool]string{true: "match", false: "nomatch"} devInfo := fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", - ctx.Input.IP(), + context.Input.IP(), logs.ColorByStatus(statusCode), statusCode, logs.ResetColor(), timeDur.String(), match[findRouter], @@ -956,19 +963,11 @@ Admin: logs.Debug(devInfo) } // Call WriteHeader if status code has been set changed - if ctx.Output.Status != 0 { - ctx.ResponseWriter.WriteHeader(ctx.Output.Status) + if context.Output.Status != 0 { + context.ResponseWriter.WriteHeader(context.Output.Status) } } -func (p *ControllerRegister) getUrlPath(ctx *beecontext.Context) string { - urlPath := ctx.Request.URL.Path - if !p.cfg.RouterCaseSensitive { - urlPath = strings.ToLower(urlPath) - } - return urlPath -} - func (p *ControllerRegister) handleParamResponse(context *beecontext.Context, execController ControllerInterface, results []reflect.Value) { // looping in reverse order for the case when both error and value are returned and error sets the response status code for i := len(results) - 1; i >= 0; i-- { @@ -986,7 +985,7 @@ func (p *ControllerRegister) handleParamResponse(context *beecontext.Context, ex // FindRouter Find Router info for URL func (p *ControllerRegister) FindRouter(context *beecontext.Context) (routerInfo *ControllerInfo, isFind bool) { var urlPath = context.Input.URL() - if !p.cfg.RouterCaseSensitive { + if !BConfig.RouterCaseSensitive { urlPath = strings.ToLower(urlPath) } httpMethod := context.Input.Method() @@ -1012,5 +1011,36 @@ func toURL(params map[string]string) string { // LogAccess logging info HTTP Access func LogAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) { - BeeApp.LogAccess(ctx, startTime, statusCode) + // Skip logging if AccessLogs config is false + if !BConfig.Log.AccessLogs { + return + } + // Skip logging static requests unless EnableStaticLogs config is true + if !BConfig.Log.EnableStaticLogs && DefaultAccessLogFilter.Filter(ctx) { + return + } + var ( + requestTime time.Time + elapsedTime time.Duration + r = ctx.Request + ) + if startTime != nil { + requestTime = *startTime + elapsedTime = time.Since(*startTime) + } + record := &logs.AccessLogRecord{ + RemoteAddr: ctx.Input.IP(), + RequestTime: requestTime, + RequestMethod: r.Method, + Request: fmt.Sprintf("%s %s %s", r.Method, r.RequestURI, r.Proto), + ServerProtocol: r.Proto, + Host: r.Host, + Status: statusCode, + ElapsedTime: elapsedTime, + HTTPReferrer: r.Header.Get("Referer"), + HTTPUserAgent: r.Header.Get("User-Agent"), + RemoteUser: r.Header.Get("Remote-User"), + BodyBytesSent: 0, // @todo this one is missing! + } + logs.AccessLog(record, BConfig.Log.AccessLogsFormat) } diff --git a/server/web/router_test.go b/router_test.go similarity index 88% rename from server/web/router_test.go rename to router_test.go index 59ccd1fc..2797b33a 100644 --- a/server/web/router_test.go +++ b/router_test.go @@ -12,18 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( - "bytes" "net/http" "net/http/httptest" "strings" "testing" - "github.com/astaxie/beego/core/logs" - - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/context" + "github.com/astaxie/beego/logs" ) type TestController struct { @@ -73,6 +71,7 @@ func (tc *TestController) GetEmptyBody() { tc.Ctx.Output.Body(res) } + type JSONController struct { Controller } @@ -212,23 +211,6 @@ func TestAutoExtFunc(t *testing.T) { } } -func TestEscape(t *testing.T) { - - r, _ := http.NewRequest("GET", "/search/%E4%BD%A0%E5%A5%BD", nil) - w := httptest.NewRecorder() - - handler := NewControllerRegister() - handler.Get("/search/:keyword(.+)", func(ctx *context.Context) { - value := ctx.Input.Param(":keyword") - ctx.Output.Body([]byte(value)) - }) - handler.ServeHTTP(w, r) - str := w.Body.String() - if str != "你好" { - t.Errorf("incorrect, %s", str) - } -} - func TestRouteOk(t *testing.T) { r, _ := http.NewRequest("GET", "/person/anderson/thomas?learn=kungfu", nil) @@ -381,7 +363,7 @@ func TestRouterHandlerAll(t *testing.T) { } // -// Benchmarks NewHttpSever: +// Benchmarks NewApp: // func beegoFilterFunc(ctx *context.Context) { @@ -440,7 +422,7 @@ func TestInsertFilter(t *testing.T) { testName := "TestInsertFilter" mux := NewControllerRegister() - mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, WithReturnOnOutput(true)) + mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}) if !mux.filters[BeforeRouter][0].returnOnOutput { t.Errorf( "%s: passing no variadic params should set returnOnOutput to true", @@ -453,7 +435,7 @@ func TestInsertFilter(t *testing.T) { } mux = NewControllerRegister() - mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, WithReturnOnOutput(false)) + mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, false) if mux.filters[BeforeRouter][0].returnOnOutput { t.Errorf( "%s: passing false as 1st variadic param should set returnOnOutput to false", @@ -461,7 +443,7 @@ func TestInsertFilter(t *testing.T) { } mux = NewControllerRegister() - mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, WithReturnOnOutput(true), WithResetParams(true)) + mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, true, true) if !mux.filters[BeforeRouter][0].resetParams { t.Errorf( "%s: passing true as 2nd variadic param should set resetParams to true", @@ -478,7 +460,7 @@ func TestParamResetFilter(t *testing.T) { mux := NewControllerRegister() - mux.InsertFilter("*", BeforeExec, beegoResetParams, WithReturnOnOutput(true), WithResetParams(true)) + mux.InsertFilter("*", BeforeExec, beegoResetParams, true, true) mux.Get(route, beegoHandleResetParams) @@ -531,8 +513,8 @@ func TestFilterBeforeExec(t *testing.T) { url := "/beforeExec" mux := NewControllerRegister() - mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput, WithReturnOnOutput(true)) - mux.InsertFilter(url, BeforeExec, beegoBeforeExec1, WithReturnOnOutput(true)) + mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput) + mux.InsertFilter(url, BeforeExec, beegoBeforeExec1) mux.Get(url, beegoFilterFunc) @@ -559,7 +541,7 @@ func TestFilterAfterExec(t *testing.T) { mux := NewControllerRegister() mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput) mux.InsertFilter(url, BeforeExec, beegoFilterNoOutput) - mux.InsertFilter(url, AfterExec, beegoAfterExec1, WithReturnOnOutput(false)) + mux.InsertFilter(url, AfterExec, beegoAfterExec1, false) mux.Get(url, beegoFilterFunc) @@ -587,10 +569,10 @@ func TestFilterFinishRouter(t *testing.T) { url := "/finishRouter" mux := NewControllerRegister() - mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput, WithReturnOnOutput(true)) - mux.InsertFilter(url, BeforeExec, beegoFilterNoOutput, WithReturnOnOutput(true)) - mux.InsertFilter(url, AfterExec, beegoFilterNoOutput, WithReturnOnOutput(true)) - mux.InsertFilter(url, FinishRouter, beegoFinishRouter1, WithReturnOnOutput(true)) + mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput) + mux.InsertFilter(url, BeforeExec, beegoFilterNoOutput) + mux.InsertFilter(url, AfterExec, beegoFilterNoOutput) + mux.InsertFilter(url, FinishRouter, beegoFinishRouter1) mux.Get(url, beegoFilterFunc) @@ -621,7 +603,7 @@ func TestFilterFinishRouterMultiFirstOnly(t *testing.T) { url := "/finishRouterMultiFirstOnly" mux := NewControllerRegister() - mux.InsertFilter(url, FinishRouter, beegoFinishRouter1, WithReturnOnOutput(false)) + mux.InsertFilter(url, FinishRouter, beegoFinishRouter1, false) mux.InsertFilter(url, FinishRouter, beegoFinishRouter2) mux.Get(url, beegoFilterFunc) @@ -648,8 +630,8 @@ func TestFilterFinishRouterMulti(t *testing.T) { url := "/finishRouterMulti" mux := NewControllerRegister() - mux.InsertFilter(url, FinishRouter, beegoFinishRouter1, WithReturnOnOutput(false)) - mux.InsertFilter(url, FinishRouter, beegoFinishRouter2, WithReturnOnOutput(false)) + mux.InsertFilter(url, FinishRouter, beegoFinishRouter1, false) + mux.InsertFilter(url, FinishRouter, beegoFinishRouter2, false) mux.Get(url, beegoFilterFunc) @@ -674,14 +656,17 @@ func beegoBeforeRouter1(ctx *context.Context) { ctx.WriteString("|BeforeRouter1") } + func beegoBeforeExec1(ctx *context.Context) { ctx.WriteString("|BeforeExec1") } + func beegoAfterExec1(ctx *context.Context) { ctx.WriteString("|AfterExec1") } + func beegoFinishRouter1(ctx *context.Context) { ctx.WriteString("|FinishRouter1") } @@ -724,29 +709,3 @@ func TestYAMLPrepare(t *testing.T) { t.Errorf(w.Body.String()) } } - -func TestRouterEntityTooLargeCopyBody(t *testing.T) { - _MaxMemory := BConfig.MaxMemory - _CopyRequestBody := BConfig.CopyRequestBody - BConfig.CopyRequestBody = true - BConfig.MaxMemory = 20 - - BeeApp.Cfg.CopyRequestBody = true - BeeApp.Cfg.MaxMemory = 20 - b := bytes.NewBuffer([]byte("barbarbarbarbarbarbarbarbarbar")) - r, _ := http.NewRequest("POST", "/user/123", b) - w := httptest.NewRecorder() - - handler := NewControllerRegister() - handler.Post("/user/:id", func(ctx *context.Context) { - ctx.Output.Body([]byte(ctx.Input.Param(":id"))) - }) - handler.ServeHTTP(w, r) - - BConfig.CopyRequestBody = _CopyRequestBody - BConfig.MaxMemory = _MaxMemory - - if w.Code != http.StatusRequestEntityTooLarge { - t.Errorf("TestRouterRequestEntityTooLarge can't run") - } -} diff --git a/scripts/gobuild.sh b/scripts/gobuild.sh new file mode 100755 index 00000000..031eafc2 --- /dev/null +++ b/scripts/gobuild.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY +# +# The original version of this file is located in the https://github.com/istio/common-files repo. +# If you're looking at this file in a different repo and want to make a change, please go to the +# common-files repo, make the change there and check it in. Then come back to this repo and run +# "make update-common". + +# Copyright Istio Authors. All Rights Reserved. +# +# Licensed 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. + +# This script builds and version stamps the output + +# adatp to beego + +VERBOSE=${VERBOSE:-"0"} +V="" +if [[ "${VERBOSE}" == "1" ]];then + V="-x" + set -x +fi + +SCRIPTPATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +OUT=${1:?"output path"} +shift + +set -e + +BUILD_GOOS=${GOOS:-linux} +BUILD_GOARCH=${GOARCH:-amd64} +GOBINARY=${GOBINARY:-go} +GOPKG="$GOPATH/pkg" +BUILDINFO=${BUILDINFO:-""} +STATIC=${STATIC:-1} +LDFLAGS=${LDFLAGS:--extldflags -static} +GOBUILDFLAGS=${GOBUILDFLAGS:-""} +# Split GOBUILDFLAGS by spaces into an array called GOBUILDFLAGS_ARRAY. +IFS=' ' read -r -a GOBUILDFLAGS_ARRAY <<< "$GOBUILDFLAGS" + +GCFLAGS=${GCFLAGS:-} +export CGO_ENABLED=0 + +if [[ "${STATIC}" != "1" ]];then + LDFLAGS="" +fi + +# gather buildinfo if not already provided +# For a release build BUILDINFO should be produced +# at the beginning of the build and used throughout +if [[ -z ${BUILDINFO} ]];then + BUILDINFO=$(mktemp) + "${SCRIPTPATH}/report_build_info.sh" > "${BUILDINFO}" +fi + + +# BUILD LD_EXTRAFLAGS +LD_EXTRAFLAGS="" + +while read -r line; do + LD_EXTRAFLAGS="${LD_EXTRAFLAGS} -X ${line}" +done < "${BUILDINFO}" + +# verify go version before build +# NB. this was copied verbatim from Kubernetes hack +minimum_go_version=go1.13 # supported patterns: go1.x, go1.x.x (x should be a number) +IFS=" " read -ra go_version <<< "$(${GOBINARY} version)" +if [[ "${minimum_go_version}" != $(echo -e "${minimum_go_version}\n${go_version[2]}" | sort -s -t. -k 1,1 -k 2,2n -k 3,3n | head -n1) && "${go_version[2]}" != "devel" ]]; then + echo "Warning: Detected that you are using an older version of the Go compiler. Beego requires ${minimum_go_version} or greater." +fi + +CURRENT_BRANCH=$(git branch | grep '*') +CURRENT_BRANCH=${CURRENT_BRANCH:2} + +BUILD_TIME=$(date +%Y-%m-%d--%T) + +LD_EXTRAFLAGS="${LD_EXTRAFLAGS} -X github.com/astaxie/beego.GoVersion=${go_version[2]:2}" +LD_EXTRAFLAGS="${LD_EXTRAFLAGS} -X github.com/astaxie/beego.GitBranch=${CURRENT_BRANCH}" +LD_EXTRAFLAGS="${LD_EXTRAFLAGS} -X github.com/astaxie/beego.BuildTime=$BUILD_TIME" + +OPTIMIZATION_FLAGS="-trimpath" +if [ "${DEBUG}" == "1" ]; then + OPTIMIZATION_FLAGS="" +fi + + + +echo "BUILD_GOARCH: $BUILD_GOARCH" +echo "GOPKG: $GOPKG" +echo "LD_EXTRAFLAGS: $LD_EXTRAFLAGS" +echo "GO_VERSION: ${go_version[2]}" +echo "BRANCH: $CURRENT_BRANCH" +echo "BUILD_TIME: $BUILD_TIME" + +time GOOS=${BUILD_GOOS} GOARCH=${BUILD_GOARCH} ${GOBINARY} build \ + ${V} "${GOBUILDFLAGS_ARRAY[@]}" ${GCFLAGS:+-gcflags "${GCFLAGS}"} \ + -o "${OUT}" \ + ${OPTIMIZATION_FLAGS} \ + -pkgdir="${GOPKG}/${BUILD_GOOS}_${BUILD_GOARCH}" \ + -ldflags "${LDFLAGS} ${LD_EXTRAFLAGS}" "${@}" \ No newline at end of file diff --git a/scripts/report_build_info.sh b/scripts/report_build_info.sh new file mode 100755 index 00000000..65ba3748 --- /dev/null +++ b/scripts/report_build_info.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY +# +# The original version of this file is located in the https://github.com/istio/common-files repo. +# If you're looking at this file in a different repo and want to make a change, please go to the +# common-files repo, make the change there and check it in. Then come back to this repo and run +# "make update-common". + +# Copyright Istio Authors +# +# Licensed 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. + +# adapt to beego + +if BUILD_GIT_REVISION=$(git rev-parse HEAD 2> /dev/null); then + if [[ -n "$(git status --porcelain 2>/dev/null)" ]]; then + BUILD_GIT_REVISION=${BUILD_GIT_REVISION}"-dirty" + fi +else + BUILD_GIT_REVISION=unknown +fi + +# Check for local changes +if git diff-index --quiet HEAD --; then + tree_status="Clean" +else + tree_status="Modified" +fi + +# security wanted VERSION='unknown' +VERSION="${BUILD_GIT_REVISION}" +if [[ -n ${BEEGO_VERSION} ]]; then + VERSION="${BEEGO_VERSION}" +fi + +GIT_DESCRIBE_TAG=$(git describe --tags) + +echo "github.com/astaxie/beego.BuildVersion=${VERSION}" +echo "github.com/astaxie/beego.BuildGitRevision=${BUILD_GIT_REVISION}" +echo "github.com/astaxie/beego.BuildStatus=${tree_status}" +echo "github.com/astaxie/beego.BuildTag=${GIT_DESCRIBE_TAG}" \ No newline at end of file diff --git a/server/web/LICENSE b/server/web/LICENSE deleted file mode 100644 index 5dbd4243..00000000 --- a/server/web/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2014 astaxie - -Licensed 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. \ No newline at end of file diff --git a/server/web/admin.go b/server/web/admin.go deleted file mode 100644 index 1b06f486..00000000 --- a/server/web/admin.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 web - -import ( - "fmt" - "net/http" - "reflect" - "time" - - "github.com/astaxie/beego/core/logs" -) - -// BeeAdminApp is the default adminApp used by admin module. -var beeAdminApp *adminApp - -// FilterMonitorFunc is default monitor filter when admin module is enable. -// if this func returns, admin module records qps for this request by condition of this function logic. -// usage: -// func MyFilterMonitor(method, requestPath string, t time.Duration, pattern string, statusCode int) bool { -// if method == "POST" { -// return false -// } -// if t.Nanoseconds() < 100 { -// return false -// } -// if strings.HasPrefix(requestPath, "/astaxie") { -// return false -// } -// return true -// } -// beego.FilterMonitorFunc = MyFilterMonitor. -var FilterMonitorFunc func(string, string, time.Duration, string, int) bool - -func init() { - - FilterMonitorFunc = func(string, string, time.Duration, string, int) bool { return true } - -} - -func list(root string, p interface{}, m M) { - pt := reflect.TypeOf(p) - pv := reflect.ValueOf(p) - if pt.Kind() == reflect.Ptr { - pt = pt.Elem() - pv = pv.Elem() - } - for i := 0; i < pv.NumField(); i++ { - var key string - if root == "" { - key = pt.Field(i).Name - } else { - key = root + "." + pt.Field(i).Name - } - if pv.Field(i).Kind() == reflect.Struct { - list(key, pv.Field(i).Interface(), m) - } else { - m[key] = pv.Field(i).Interface() - } - } -} - -func writeJSON(rw http.ResponseWriter, jsonData []byte) { - rw.Header().Set("Content-Type", "application/json") - rw.Write(jsonData) -} - -// adminApp is an http.HandlerFunc map used as beeAdminApp. -type adminApp struct { - *HttpServer -} - -// Route adds http.HandlerFunc to adminApp with url pattern. -func (admin *adminApp) Run() { - - // if len(task.AdminTaskList) > 0 { - // task.StartTask() - // } - logs.Warning("now we don't start tasks here, if you use task module," + - " please invoke task.StartTask, or task will not be executed") - - addr := BConfig.Listen.AdminAddr - - if BConfig.Listen.AdminPort != 0 { - addr = fmt.Sprintf("%s:%d", BConfig.Listen.AdminAddr, BConfig.Listen.AdminPort) - } - - logs.Info("Admin server Running on %s", addr) - - admin.HttpServer.Run(addr) -} - -func registerAdmin() error { - if BConfig.Listen.EnableAdmin { - - c := &adminController{ - servers: make([]*HttpServer, 0, 2), - } - beeAdminApp = &adminApp{ - HttpServer: NewHttpServerWithCfg(BConfig), - } - // keep in mind that all data should be html escaped to avoid XSS attack - beeAdminApp.Router("/", c, "get:AdminIndex") - beeAdminApp.Router("/qps", c, "get:QpsIndex") - beeAdminApp.Router("/prof", c, "get:ProfIndex") - beeAdminApp.Router("/healthcheck", c, "get:Healthcheck") - beeAdminApp.Router("/task", c, "get:TaskStatus") - beeAdminApp.Router("/listconf", c, "get:ListConf") - beeAdminApp.Router("/metrics", c, "get:PrometheusMetrics") - - go beeAdminApp.Run() - } - return nil -} diff --git a/server/web/admin_controller.go b/server/web/admin_controller.go deleted file mode 100644 index 2998c8d4..00000000 --- a/server/web/admin_controller.go +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 web - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "strconv" - "text/template" - - "github.com/prometheus/client_golang/prometheus/promhttp" - - "github.com/astaxie/beego/core/governor" -) - -type adminController struct { - Controller - servers []*HttpServer -} - -func (a *adminController) registerHttpServer(svr *HttpServer) { - a.servers = append(a.servers, svr) -} - -// ProfIndex is a http.Handler for showing profile command. -// it's in url pattern "/prof" in admin module. -func (a *adminController) ProfIndex() { - rw, r := a.Ctx.ResponseWriter, a.Ctx.Request - r.ParseForm() - command := r.Form.Get("command") - if command == "" { - return - } - - var ( - format = r.Form.Get("format") - data = make(map[interface{}]interface{}) - result bytes.Buffer - ) - governor.ProcessInput(command, &result) - data["Content"] = template.HTMLEscapeString(result.String()) - - if format == "json" && command == "gc summary" { - dataJSON, err := json.Marshal(data) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - writeJSON(rw, dataJSON) - return - } - - data["Title"] = template.HTMLEscapeString(command) - defaultTpl := defaultScriptsTpl - if command == "gc summary" { - defaultTpl = gcAjaxTpl - } - writeTemplate(rw, data, profillingTpl, defaultTpl) -} - -func (a *adminController) PrometheusMetrics() { - promhttp.Handler().ServeHTTP(a.Ctx.ResponseWriter, a.Ctx.Request) -} - -// TaskStatus is a http.Handler with running task status (task name, status and the last execution). -// it's in "/task" pattern in admin module. -func (a *adminController) TaskStatus() { - - rw, req := a.Ctx.ResponseWriter, a.Ctx.Request - - data := make(map[interface{}]interface{}) - - // Run Task - req.ParseForm() - taskname := req.Form.Get("taskname") - if taskname != "" { - cmd := governor.GetCommand("task", "run") - res := cmd.Execute(taskname) - if res.IsSuccess() { - - data["Message"] = []string{"success", - template.HTMLEscapeString(fmt.Sprintf("%s run success,Now the Status is
%s", - taskname, res.Content.(string)))} - - } else { - data["Message"] = []string{"error", template.HTMLEscapeString(fmt.Sprintf("%s", res.Error))} - } - } - - // List Tasks - content := make(M) - resultList := governor.GetCommand("task", "list").Execute().Content.([][]string) - var fields = []string{ - "Task Name", - "Task Spec", - "Task Status", - "Last Time", - "", - } - - content["Fields"] = fields - content["Data"] = resultList - data["Content"] = content - data["Title"] = "Tasks" - writeTemplate(rw, data, tasksTpl, defaultScriptsTpl) -} - -func (a *adminController) AdminIndex() { - // AdminIndex is the default http.Handler for admin module. - // it matches url pattern "/". - writeTemplate(a.Ctx.ResponseWriter, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl) -} - -// Healthcheck is a http.Handler calling health checking and showing the result. -// it's in "/healthcheck" pattern in admin module. -func (a *adminController) Healthcheck() { - heathCheck(a.Ctx.ResponseWriter, a.Ctx.Request) -} - -func heathCheck(rw http.ResponseWriter, r *http.Request) { - var ( - result []string - data = make(map[interface{}]interface{}) - resultList = new([][]string) - content = M{ - "Fields": []string{"Name", "Message", "Status"}, - } - ) - - for name, h := range governor.AdminCheckList { - if err := h.Check(); err != nil { - result = []string{ - "error", - template.HTMLEscapeString(name), - template.HTMLEscapeString(err.Error()), - } - } else { - result = []string{ - "success", - template.HTMLEscapeString(name), - "OK", - } - } - *resultList = append(*resultList, result) - } - - queryParams := r.URL.Query() - jsonFlag := queryParams.Get("json") - shouldReturnJSON, _ := strconv.ParseBool(jsonFlag) - - if shouldReturnJSON { - response := buildHealthCheckResponseList(resultList) - jsonResponse, err := json.Marshal(response) - - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - } else { - writeJSON(rw, jsonResponse) - } - return - } - - content["Data"] = resultList - data["Content"] = content - data["Title"] = "Health Check" - - writeTemplate(rw, data, healthCheckTpl, defaultScriptsTpl) -} - -// QpsIndex is the http.Handler for writing qps statistics map result info in http.ResponseWriter. -// it's registered with url pattern "/qps" in admin module. -func (a *adminController) QpsIndex() { - data := make(map[interface{}]interface{}) - data["Content"] = StatisticsMap.GetMap() - - // do html escape before display path, avoid xss - if content, ok := (data["Content"]).(M); ok { - if resultLists, ok := (content["Data"]).([][]string); ok { - for i := range resultLists { - if len(resultLists[i]) > 0 { - resultLists[i][0] = template.HTMLEscapeString(resultLists[i][0]) - } - } - } - } - writeTemplate(a.Ctx.ResponseWriter, data, qpsTpl, defaultScriptsTpl) -} - -// ListConf is the http.Handler of displaying all beego configuration values as key/value pair. -// it's registered with url pattern "/listconf" in admin module. -func (a *adminController) ListConf() { - rw := a.Ctx.ResponseWriter - r := a.Ctx.Request - r.ParseForm() - command := r.Form.Get("command") - if command == "" { - rw.Write([]byte("command not support")) - return - } - - data := make(map[interface{}]interface{}) - switch command { - case "conf": - m := make(M) - list("BConfig", BConfig, m) - m["appConfigPath"] = template.HTMLEscapeString(appConfigPath) - m["appConfigProvider"] = template.HTMLEscapeString(appConfigProvider) - tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) - tmpl = template.Must(tmpl.Parse(configTpl)) - tmpl = template.Must(tmpl.Parse(defaultScriptsTpl)) - - data["Content"] = m - - tmpl.Execute(rw, data) - - case "router": - content := BeeApp.PrintTree() - content["Fields"] = []string{ - "Router Pattern", - "Methods", - "Controller", - } - data["Content"] = content - data["Title"] = "Routers" - writeTemplate(rw, data, routerAndFilterTpl, defaultScriptsTpl) - case "filter": - var ( - content = M{ - "Fields": []string{ - "Router Pattern", - "Filter Function", - }, - } - ) - - filterTypeData := BeeApp.reportFilter() - - filterTypes := make([]string, 0, len(filterTypeData)) - for k, _ := range filterTypeData { - filterTypes = append(filterTypes, k) - } - - content["Data"] = filterTypeData - content["Methods"] = filterTypes - - data["Content"] = content - data["Title"] = "Filters" - writeTemplate(rw, data, routerAndFilterTpl, defaultScriptsTpl) - default: - rw.Write([]byte("command not support")) - } -} - -func writeTemplate(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) { - tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) - for _, tpl := range tpls { - tmpl = template.Must(tmpl.Parse(tpl)) - } - tmpl.Execute(rw, data) -} - -func buildHealthCheckResponseList(healthCheckResults *[][]string) []map[string]interface{} { - response := make([]map[string]interface{}, len(*healthCheckResults)) - - for i, healthCheckResult := range *healthCheckResults { - currentResultMap := make(map[string]interface{}) - - currentResultMap["name"] = healthCheckResult[0] - currentResultMap["message"] = healthCheckResult[1] - currentResultMap["status"] = healthCheckResult[2] - - response[i] = currentResultMap - } - - return response - -} - -// PrintTree print all routers -// Deprecated using BeeApp directly -func PrintTree() M { - return BeeApp.PrintTree() -} diff --git a/server/web/admin_test.go b/server/web/admin_test.go deleted file mode 100644 index 5ef57323..00000000 --- a/server/web/admin_test.go +++ /dev/null @@ -1,249 +0,0 @@ -package web - -import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/core/governor" -) - -type SampleDatabaseCheck struct { -} - -type SampleCacheCheck struct { -} - -func (dc *SampleDatabaseCheck) Check() error { - return nil -} - -func (cc *SampleCacheCheck) Check() error { - return errors.New("no cache detected") -} - -func TestList_01(t *testing.T) { - m := make(M) - list("BConfig", BConfig, m) - t.Log(m) - om := oldMap() - for k, v := range om { - if fmt.Sprint(m[k]) != fmt.Sprint(v) { - t.Log(k, "old-key", v, "new-key", m[k]) - t.FailNow() - } - } -} - -func oldMap() M { - m := make(M) - m["BConfig.AppName"] = BConfig.AppName - m["BConfig.RunMode"] = BConfig.RunMode - m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive - m["BConfig.ServerName"] = BConfig.ServerName - m["BConfig.RecoverPanic"] = BConfig.RecoverPanic - m["BConfig.CopyRequestBody"] = BConfig.CopyRequestBody - m["BConfig.EnableGzip"] = BConfig.EnableGzip - m["BConfig.MaxMemory"] = BConfig.MaxMemory - m["BConfig.EnableErrorsShow"] = BConfig.EnableErrorsShow - m["BConfig.Listen.Graceful"] = BConfig.Listen.Graceful - m["BConfig.Listen.ServerTimeOut"] = BConfig.Listen.ServerTimeOut - m["BConfig.Listen.ListenTCP4"] = BConfig.Listen.ListenTCP4 - m["BConfig.Listen.EnableHTTP"] = BConfig.Listen.EnableHTTP - m["BConfig.Listen.HTTPAddr"] = BConfig.Listen.HTTPAddr - m["BConfig.Listen.HTTPPort"] = BConfig.Listen.HTTPPort - m["BConfig.Listen.EnableHTTPS"] = BConfig.Listen.EnableHTTPS - m["BConfig.Listen.HTTPSAddr"] = BConfig.Listen.HTTPSAddr - m["BConfig.Listen.HTTPSPort"] = BConfig.Listen.HTTPSPort - m["BConfig.Listen.HTTPSCertFile"] = BConfig.Listen.HTTPSCertFile - m["BConfig.Listen.HTTPSKeyFile"] = BConfig.Listen.HTTPSKeyFile - m["BConfig.Listen.EnableAdmin"] = BConfig.Listen.EnableAdmin - m["BConfig.Listen.AdminAddr"] = BConfig.Listen.AdminAddr - m["BConfig.Listen.AdminPort"] = BConfig.Listen.AdminPort - m["BConfig.Listen.EnableFcgi"] = BConfig.Listen.EnableFcgi - m["BConfig.Listen.EnableStdIo"] = BConfig.Listen.EnableStdIo - m["BConfig.WebConfig.AutoRender"] = BConfig.WebConfig.AutoRender - m["BConfig.WebConfig.EnableDocs"] = BConfig.WebConfig.EnableDocs - m["BConfig.WebConfig.FlashName"] = BConfig.WebConfig.FlashName - m["BConfig.WebConfig.FlashSeparator"] = BConfig.WebConfig.FlashSeparator - m["BConfig.WebConfig.DirectoryIndex"] = BConfig.WebConfig.DirectoryIndex - m["BConfig.WebConfig.StaticDir"] = BConfig.WebConfig.StaticDir - m["BConfig.WebConfig.StaticExtensionsToGzip"] = BConfig.WebConfig.StaticExtensionsToGzip - m["BConfig.WebConfig.StaticCacheFileSize"] = BConfig.WebConfig.StaticCacheFileSize - m["BConfig.WebConfig.StaticCacheFileNum"] = BConfig.WebConfig.StaticCacheFileNum - m["BConfig.WebConfig.TemplateLeft"] = BConfig.WebConfig.TemplateLeft - m["BConfig.WebConfig.TemplateRight"] = BConfig.WebConfig.TemplateRight - m["BConfig.WebConfig.ViewsPath"] = BConfig.WebConfig.ViewsPath - m["BConfig.WebConfig.EnableXSRF"] = BConfig.WebConfig.EnableXSRF - m["BConfig.WebConfig.XSRFExpire"] = BConfig.WebConfig.XSRFExpire - m["BConfig.WebConfig.Session.SessionOn"] = BConfig.WebConfig.Session.SessionOn - m["BConfig.WebConfig.Session.SessionProvider"] = BConfig.WebConfig.Session.SessionProvider - m["BConfig.WebConfig.Session.SessionName"] = BConfig.WebConfig.Session.SessionName - m["BConfig.WebConfig.Session.SessionGCMaxLifetime"] = BConfig.WebConfig.Session.SessionGCMaxLifetime - m["BConfig.WebConfig.Session.SessionProviderConfig"] = BConfig.WebConfig.Session.SessionProviderConfig - m["BConfig.WebConfig.Session.SessionCookieLifeTime"] = BConfig.WebConfig.Session.SessionCookieLifeTime - m["BConfig.WebConfig.Session.SessionAutoSetCookie"] = BConfig.WebConfig.Session.SessionAutoSetCookie - m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain - m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly - m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs - m["BConfig.Log.EnableStaticLogs"] = BConfig.Log.EnableStaticLogs - m["BConfig.Log.AccessLogsFormat"] = BConfig.Log.AccessLogsFormat - m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum - m["BConfig.Log.Outputs"] = BConfig.Log.Outputs - return m -} - -func TestWriteJSON(t *testing.T) { - t.Log("Testing the adding of JSON to the response") - - w := httptest.NewRecorder() - originalBody := []int{1, 2, 3} - - res, _ := json.Marshal(originalBody) - - writeJSON(w, res) - - decodedBody := []int{} - err := json.NewDecoder(w.Body).Decode(&decodedBody) - - if err != nil { - t.Fatal("Could not decode response body into slice.") - } - - for i := range decodedBody { - if decodedBody[i] != originalBody[i] { - t.Fatalf("Expected %d but got %d in decoded body slice", originalBody[i], decodedBody[i]) - } - } -} - -func TestHealthCheckHandlerDefault(t *testing.T) { - endpointPath := "/healthcheck" - - governor.AddHealthCheck("database", &SampleDatabaseCheck{}) - governor.AddHealthCheck("cache", &SampleCacheCheck{}) - - req, err := http.NewRequest("GET", endpointPath, nil) - if err != nil { - t.Fatal(err) - } - - w := httptest.NewRecorder() - - handler := http.HandlerFunc(heathCheck) - - handler.ServeHTTP(w, req) - - if status := w.Code; status != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", - status, http.StatusOK) - } - if !strings.Contains(w.Body.String(), "database") { - t.Errorf("Expected 'database' in generated template.") - } - -} - -func TestBuildHealthCheckResponseList(t *testing.T) { - healthCheckResults := [][]string{ - []string{ - "error", - "Database", - "Error occured whie starting the db", - }, - []string{ - "success", - "Cache", - "Cache started successfully", - }, - } - - responseList := buildHealthCheckResponseList(&healthCheckResults) - - if len(responseList) != len(healthCheckResults) { - t.Errorf("invalid response map length: got %d want %d", - len(responseList), len(healthCheckResults)) - } - - responseFields := []string{"name", "message", "status"} - - for _, response := range responseList { - for _, field := range responseFields { - _, ok := response[field] - if !ok { - t.Errorf("expected %s to be in the response %v", field, response) - } - } - - } - -} - -func TestHealthCheckHandlerReturnsJSON(t *testing.T) { - - governor.AddHealthCheck("database", &SampleDatabaseCheck{}) - governor.AddHealthCheck("cache", &SampleCacheCheck{}) - - req, err := http.NewRequest("GET", "/healthcheck?json=true", nil) - if err != nil { - t.Fatal(err) - } - - w := httptest.NewRecorder() - - handler := http.HandlerFunc(heathCheck) - - handler.ServeHTTP(w, req) - if status := w.Code; status != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", - status, http.StatusOK) - } - - decodedResponseBody := []map[string]interface{}{} - expectedResponseBody := []map[string]interface{}{} - - expectedJSONString := []byte(` - [ - { - "message":"database", - "name":"success", - "status":"OK" - }, - { - "message":"cache", - "name":"error", - "status":"no cache detected" - } - ] - `) - - json.Unmarshal(expectedJSONString, &expectedResponseBody) - - json.Unmarshal(w.Body.Bytes(), &decodedResponseBody) - - if len(expectedResponseBody) != len(decodedResponseBody) { - t.Errorf("invalid response map length: got %d want %d", - len(decodedResponseBody), len(expectedResponseBody)) - } - assert.Equal(t, len(expectedResponseBody), len(decodedResponseBody)) - assert.Equal(t, 2, len(decodedResponseBody)) - - var database, cache map[string]interface{} - if decodedResponseBody[0]["message"] == "database" { - database = decodedResponseBody[0] - cache = decodedResponseBody[1] - } else { - database = decodedResponseBody[1] - cache = decodedResponseBody[0] - } - - assert.Equal(t, expectedResponseBody[0], database) - assert.Equal(t, expectedResponseBody[1], cache) - -} diff --git a/server/web/captcha/LICENSE b/server/web/captcha/LICENSE deleted file mode 100644 index 0ad73ae0..00000000 --- a/server/web/captcha/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2011-2014 Dmitry Chestnykh - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/server/web/captcha/README.md b/server/web/captcha/README.md deleted file mode 100644 index dbc2026b..00000000 --- a/server/web/captcha/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Captcha - -an example for use captcha - -``` -package controllers - -import ( - "github.com/astaxie/beego" - "github.com/astaxie/beego/cache" - "github.com/astaxie/beego/utils/captcha" -) - -var cpt *captcha.Captcha - -func init() { - // use beego cache system store the captcha data - store := cache.NewMemoryCache() - cpt = captcha.NewWithFilter("/captcha/", store) -} - -type MainController struct { - beego.Controller -} - -func (this *MainController) Get() { - this.TplName = "index.tpl" -} - -func (this *MainController) Post() { - this.TplName = "index.tpl" - - this.Data["Success"] = cpt.VerifyReq(this.Ctx.Request) -} -``` - -template usage - -``` -{{.Success}} -
- {{create_captcha}} - -
-``` diff --git a/server/web/context/response.go b/server/web/context/response.go deleted file mode 100644 index 7bd9a7e8..00000000 --- a/server/web/context/response.go +++ /dev/null @@ -1,26 +0,0 @@ -package context - -import ( - "net/http" - "strconv" -) - -const ( - //BadRequest indicates HTTP error 400 - BadRequest StatusCode = http.StatusBadRequest - - //NotFound indicates HTTP error 404 - NotFound StatusCode = http.StatusNotFound -) - -// StatusCode sets the HTTP response status code -type StatusCode int - -func (s StatusCode) Error() string { - return strconv.Itoa(int(s)) -} - -// Render sets the HTTP status code -func (s StatusCode) Render(ctx *Context) { - ctx.Output.SetStatus(int(s)) -} diff --git a/server/web/doc.go b/server/web/doc.go deleted file mode 100644 index a32bc576..00000000 --- a/server/web/doc.go +++ /dev/null @@ -1,17 +0,0 @@ -/* -Package beego provide a MVC framework -beego: an open-source, high-performance, modular, full-stack web framework - -It is used for rapid development of RESTful APIs, web apps and backend services in Go. -beego is inspired by Tornado, Sinatra and Flask with the added benefit of some Go-specific features such as interfaces and struct embedding. - - package main - import "github.com/astaxie/beego" - - func main() { - beego.Run() - } - -more information: http://beego.me -*/ -package web diff --git a/server/web/filter.go b/server/web/filter.go deleted file mode 100644 index 967de8c9..00000000 --- a/server/web/filter.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 web - -import ( - "strings" - - "github.com/astaxie/beego/server/web/context" -) - -// FilterChain is different from pure FilterFunc -// when you use this, you must invoke next(ctx) inside the FilterFunc which is returned -// And all those FilterChain will be invoked before other FilterFunc -type FilterChain func(next FilterFunc) FilterFunc - -// FilterFunc defines a filter function which is invoked before the controller handler is executed. -type FilterFunc func(ctx *context.Context) - -// FilterRouter defines a filter operation which is invoked before the controller handler is executed. -// It can match the URL against a pattern, and execute a filter function -// when a request with a matching URL arrives. -type FilterRouter struct { - filterFunc FilterFunc - next *FilterRouter - tree *Tree - pattern string - returnOnOutput bool - resetParams bool -} - -// params is for: -// 1. setting the returnOnOutput value (false allows multiple filters to execute) -// 2. determining whether or not params need to be reset. -func newFilterRouter(pattern string, filter FilterFunc, opts ...FilterOpt) *FilterRouter { - mr := &FilterRouter{ - tree: NewTree(), - pattern: pattern, - filterFunc: filter, - } - - fos := &filterOpts{ - returnOnOutput: true, - } - - for _, o := range opts { - o(fos) - } - - if !fos.routerCaseSensitive { - mr.pattern = strings.ToLower(pattern) - } - - mr.returnOnOutput = fos.returnOnOutput - mr.resetParams = fos.resetParams - mr.tree.AddRouter(pattern, true) - return mr -} - -// filter will check whether we need to execute the filter logic -// return (started, done) -func (f *FilterRouter) filter(ctx *context.Context, urlPath string, preFilterParams map[string]string) (bool, bool) { - if f.returnOnOutput && ctx.ResponseWriter.Started { - return true, true - } - if f.resetParams { - preFilterParams = ctx.Input.Params() - } - if ok := f.ValidRouter(urlPath, ctx); ok { - f.filterFunc(ctx) - if f.resetParams { - ctx.Input.ResetParams() - for k, v := range preFilterParams { - ctx.Input.SetParam(k, v) - } - } - } else if f.next != nil { - return f.next.filter(ctx, urlPath, preFilterParams) - } - if f.returnOnOutput && ctx.ResponseWriter.Started { - return true, true - } - return false, false -} - -// ValidRouter checks if the current request is matched by this filter. -// If the request is matched, the values of the URL parameters defined -// by the filter pattern are also returned. -func (f *FilterRouter) ValidRouter(url string, ctx *context.Context) bool { - isOk := f.tree.Match(url, ctx) - if isOk != nil { - if b, ok := isOk.(bool); ok { - return b - } - } - return false -} - -type filterOpts struct { - returnOnOutput bool - resetParams bool - routerCaseSensitive bool -} - -type FilterOpt func(opts *filterOpts) - -func WithReturnOnOutput(ret bool) FilterOpt { - return func(opts *filterOpts) { - opts.returnOnOutput = ret - } -} - -func WithResetParams(reset bool) FilterOpt { - return func(opts *filterOpts) { - opts.resetParams = reset - } -} - -func WithCaseSensitive(sensitive bool) FilterOpt { - return func(opts *filterOpts) { - opts.routerCaseSensitive = sensitive - } -} diff --git a/server/web/filter/apiauth/apiauth_test.go b/server/web/filter/apiauth/apiauth_test.go deleted file mode 100644 index 1f56cb0f..00000000 --- a/server/web/filter/apiauth/apiauth_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package apiauth - -import ( - "net/url" - "testing" -) - -func TestSignature(t *testing.T) { - appsecret := "beego secret" - method := "GET" - RequestURL := "http://localhost/test/url" - params := make(url.Values) - params.Add("arg1", "hello") - params.Add("arg2", "beego") - - signature := "mFdpvLh48ca4mDVEItE9++AKKQ/IVca7O/ZyyB8hR58=" - if Signature(appsecret, method, params, RequestURL) != signature { - t.Error("Signature error") - } -} diff --git a/server/web/filter/authz/authz_model.conf b/server/web/filter/authz/authz_model.conf deleted file mode 100644 index d1b3dbd7..00000000 --- a/server/web/filter/authz/authz_model.conf +++ /dev/null @@ -1,14 +0,0 @@ -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act - -[role_definition] -g = _, _ - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*") \ No newline at end of file diff --git a/server/web/filter/authz/authz_policy.csv b/server/web/filter/authz/authz_policy.csv deleted file mode 100644 index c062dd3e..00000000 --- a/server/web/filter/authz/authz_policy.csv +++ /dev/null @@ -1,7 +0,0 @@ -p, alice, /dataset1/*, GET -p, alice, /dataset1/resource1, POST -p, bob, /dataset2/resource1, * -p, bob, /dataset2/resource2, GET -p, bob, /dataset2/folder1/*, POST -p, dataset1_admin, /dataset1/*, * -g, cathy, dataset1_admin \ No newline at end of file diff --git a/server/web/filter/authz/authz_test.go b/server/web/filter/authz/authz_test.go deleted file mode 100644 index c0d0dde5..00000000 --- a/server/web/filter/authz/authz_test.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 authz - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/casbin/casbin" - - "github.com/astaxie/beego/server/web" - "github.com/astaxie/beego/server/web/context" - "github.com/astaxie/beego/server/web/filter/auth" -) - -func testRequest(t *testing.T, handler *web.ControllerRegister, user string, path string, method string, code int) { - r, _ := http.NewRequest(method, path, nil) - r.SetBasicAuth(user, "123") - w := httptest.NewRecorder() - handler.ServeHTTP(w, r) - - if w.Code != code { - t.Errorf("%s, %s, %s: %d, supposed to be %d", user, path, method, w.Code, code) - } -} - -func TestBasic(t *testing.T) { - handler := web.NewControllerRegister() - - handler.InsertFilter("*", web.BeforeRouter, auth.Basic("alice", "123")) - handler.InsertFilter("*", web.BeforeRouter, NewAuthorizer(casbin.NewEnforcer("authz_model.conf", "authz_policy.csv"))) - - handler.Any("*", func(ctx *context.Context) { - ctx.Output.SetStatus(200) - }) - - testRequest(t, handler, "alice", "/dataset1/resource1", "GET", 200) - testRequest(t, handler, "alice", "/dataset1/resource1", "POST", 200) - testRequest(t, handler, "alice", "/dataset1/resource2", "GET", 200) - testRequest(t, handler, "alice", "/dataset1/resource2", "POST", 403) -} - -func TestPathWildcard(t *testing.T) { - handler := web.NewControllerRegister() - - handler.InsertFilter("*", web.BeforeRouter, auth.Basic("bob", "123")) - handler.InsertFilter("*", web.BeforeRouter, NewAuthorizer(casbin.NewEnforcer("authz_model.conf", "authz_policy.csv"))) - - handler.Any("*", func(ctx *context.Context) { - ctx.Output.SetStatus(200) - }) - - testRequest(t, handler, "bob", "/dataset2/resource1", "GET", 200) - testRequest(t, handler, "bob", "/dataset2/resource1", "POST", 200) - testRequest(t, handler, "bob", "/dataset2/resource1", "DELETE", 200) - testRequest(t, handler, "bob", "/dataset2/resource2", "GET", 200) - testRequest(t, handler, "bob", "/dataset2/resource2", "POST", 403) - testRequest(t, handler, "bob", "/dataset2/resource2", "DELETE", 403) - - testRequest(t, handler, "bob", "/dataset2/folder1/item1", "GET", 403) - testRequest(t, handler, "bob", "/dataset2/folder1/item1", "POST", 200) - testRequest(t, handler, "bob", "/dataset2/folder1/item1", "DELETE", 403) - testRequest(t, handler, "bob", "/dataset2/folder1/item2", "GET", 403) - testRequest(t, handler, "bob", "/dataset2/folder1/item2", "POST", 200) - testRequest(t, handler, "bob", "/dataset2/folder1/item2", "DELETE", 403) -} - -func TestRBAC(t *testing.T) { - handler := web.NewControllerRegister() - - handler.InsertFilter("*", web.BeforeRouter, auth.Basic("cathy", "123")) - e := casbin.NewEnforcer("authz_model.conf", "authz_policy.csv") - handler.InsertFilter("*", web.BeforeRouter, NewAuthorizer(e)) - - handler.Any("*", func(ctx *context.Context) { - ctx.Output.SetStatus(200) - }) - - // cathy can access all /dataset1/* resources via all methods because it has the dataset1_admin role. - testRequest(t, handler, "cathy", "/dataset1/item", "GET", 200) - testRequest(t, handler, "cathy", "/dataset1/item", "POST", 200) - testRequest(t, handler, "cathy", "/dataset1/item", "DELETE", 200) - testRequest(t, handler, "cathy", "/dataset2/item", "GET", 403) - testRequest(t, handler, "cathy", "/dataset2/item", "POST", 403) - testRequest(t, handler, "cathy", "/dataset2/item", "DELETE", 403) - - // delete all roles on user cathy, so cathy cannot access any resources now. - e.DeleteRolesForUser("cathy") - - testRequest(t, handler, "cathy", "/dataset1/item", "GET", 403) - testRequest(t, handler, "cathy", "/dataset1/item", "POST", 403) - testRequest(t, handler, "cathy", "/dataset1/item", "DELETE", 403) - testRequest(t, handler, "cathy", "/dataset2/item", "GET", 403) - testRequest(t, handler, "cathy", "/dataset2/item", "POST", 403) - testRequest(t, handler, "cathy", "/dataset2/item", "DELETE", 403) -} diff --git a/server/web/filter/opentracing/filter.go b/server/web/filter/opentracing/filter.go deleted file mode 100644 index c2defa18..00000000 --- a/server/web/filter/opentracing/filter.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 opentracing - -import ( - "context" - - "github.com/astaxie/beego/server/web" - beegoCtx "github.com/astaxie/beego/server/web/context" - logKit "github.com/go-kit/kit/log" - opentracingKit "github.com/go-kit/kit/tracing/opentracing" - "github.com/opentracing/opentracing-go" -) - -// FilterChainBuilder provides an extension point that we can support more configurations if necessary -type FilterChainBuilder struct { - // CustomSpanFunc makes users to custom the span. - CustomSpanFunc func(span opentracing.Span, ctx *beegoCtx.Context) -} - -func (builder *FilterChainBuilder) FilterChain(next web.FilterFunc) web.FilterFunc { - return func(ctx *beegoCtx.Context) { - var ( - spanCtx context.Context - span opentracing.Span - ) - operationName := builder.operationName(ctx) - - if preSpan := opentracing.SpanFromContext(ctx.Request.Context()); preSpan == nil { - inject := opentracingKit.HTTPToContext(opentracing.GlobalTracer(), operationName, logKit.NewNopLogger()) - spanCtx = inject(ctx.Request.Context(), ctx.Request) - span = opentracing.SpanFromContext(spanCtx) - } else { - span, spanCtx = opentracing.StartSpanFromContext(ctx.Request.Context(), operationName) - } - - defer span.Finish() - - newReq := ctx.Request.Clone(spanCtx) - ctx.Reset(ctx.ResponseWriter.ResponseWriter, newReq) - - next(ctx) - // if you think we need to do more things, feel free to create an issue to tell us - span.SetTag("http.status_code", ctx.ResponseWriter.Status) - span.SetTag("http.method", ctx.Input.Method()) - span.SetTag("peer.hostname", ctx.Request.Host) - span.SetTag("http.url", ctx.Request.URL.String()) - span.SetTag("http.scheme", ctx.Request.URL.Scheme) - span.SetTag("span.kind", "server") - span.SetTag("component", "beego") - if ctx.Output.IsServerError() || ctx.Output.IsClientError() { - span.SetTag("error", true) - } - span.SetTag("peer.address", ctx.Request.RemoteAddr) - span.SetTag("http.proto", ctx.Request.Proto) - - span.SetTag("beego.route", ctx.Input.GetData("RouterPattern")) - - if builder.CustomSpanFunc != nil { - builder.CustomSpanFunc(span, ctx) - } - } -} - -func (builder *FilterChainBuilder) operationName(ctx *beegoCtx.Context) string { - operationName := ctx.Input.URL() - // it means that there is not any span, so we create a span as the root span. - // TODO, if we support multiple servers, this need to be changed - route, found := web.BeeApp.Handlers.FindRouter(ctx) - if found { - operationName = ctx.Input.Method() + "#" + route.GetPattern() - } - return operationName -} diff --git a/server/web/filter/opentracing/filter_test.go b/server/web/filter/opentracing/filter_test.go deleted file mode 100644 index d7222c37..00000000 --- a/server/web/filter/opentracing/filter_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 opentracing - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/opentracing/opentracing-go" - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/server/web/context" -) - -func TestFilterChainBuilder_FilterChain(t *testing.T) { - builder := &FilterChainBuilder{ - CustomSpanFunc: func(span opentracing.Span, ctx *context.Context) { - span.SetTag("aa", "bbb") - }, - } - - ctx := context.NewContext() - r, _ := http.NewRequest("GET", "/prometheus/user", nil) - w := httptest.NewRecorder() - ctx.Reset(w, r) - ctx.Input.SetData("RouterPattern", "my-route") - - filterFunc := builder.FilterChain(func(ctx *context.Context) { - ctx.Input.SetData("opentracing", true) - }) - - filterFunc(ctx) - assert.True(t, ctx.Input.GetData("opentracing").(bool)) -} diff --git a/server/web/filter/prometheus/filter.go b/server/web/filter/prometheus/filter.go deleted file mode 100644 index 7daabd5a..00000000 --- a/server/web/filter/prometheus/filter.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 prometheus - -import ( - "strconv" - "strings" - "time" - - "github.com/prometheus/client_golang/prometheus" - - "github.com/astaxie/beego" - "github.com/astaxie/beego/server/web" - "github.com/astaxie/beego/server/web/context" -) - -// FilterChainBuilder is an extension point, -// when we want to support some configuration, -// please use this structure -type FilterChainBuilder struct { -} - -// FilterChain returns a FilterFunc. The filter will records some metrics -func (builder *FilterChainBuilder) FilterChain(next web.FilterFunc) web.FilterFunc { - summaryVec := prometheus.NewSummaryVec(prometheus.SummaryOpts{ - Name: "beego", - Subsystem: "http_request", - ConstLabels: map[string]string{ - "server": web.BConfig.ServerName, - "env": web.BConfig.RunMode, - "appname": web.BConfig.AppName, - }, - Help: "The statics info for http request", - }, []string{"pattern", "method", "status", "duration"}) - - prometheus.MustRegister(summaryVec) - - registerBuildInfo() - - return func(ctx *context.Context) { - startTime := time.Now() - next(ctx) - endTime := time.Now() - go report(endTime.Sub(startTime), ctx, summaryVec) - } -} - -func registerBuildInfo() { - buildInfo := prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Name: "beego", - Subsystem: "build_info", - Help: "The building information", - ConstLabels: map[string]string{ - "appname": web.BConfig.AppName, - "build_version": beego.BuildVersion, - "build_revision": beego.BuildGitRevision, - "build_status": beego.BuildStatus, - "build_tag": beego.BuildTag, - "build_time": strings.Replace(beego.BuildTime, "--", " ", 1), - "go_version": beego.GoVersion, - "git_branch": beego.GitBranch, - "start_time": time.Now().Format("2006-01-02 15:04:05"), - }, - }, []string{}) - - prometheus.MustRegister(buildInfo) - buildInfo.WithLabelValues().Set(1) -} - -func report(dur time.Duration, ctx *context.Context, vec *prometheus.SummaryVec) { - status := ctx.Output.Status - ptn := ctx.Input.GetData("RouterPattern").(string) - ms := dur / time.Millisecond - vec.WithLabelValues(ptn, ctx.Input.Method(), strconv.Itoa(status), strconv.Itoa(int(ms))).Observe(float64(ms)) -} diff --git a/server/web/filter/prometheus/filter_test.go b/server/web/filter/prometheus/filter_test.go deleted file mode 100644 index cb133a64..00000000 --- a/server/web/filter/prometheus/filter_test.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2020 beego -// -// Licensed 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 prometheus - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/server/web/context" -) - -func TestFilterChain(t *testing.T) { - filter := (&FilterChainBuilder{}).FilterChain(func(ctx *context.Context) { - // do nothing - ctx.Input.SetData("invocation", true) - }) - - ctx := context.NewContext() - r, _ := http.NewRequest("GET", "/prometheus/user", nil) - w := httptest.NewRecorder() - ctx.Reset(w, r) - ctx.Input.SetData("RouterPattern", "my-route") - filter(ctx) - assert.True(t, ctx.Input.GetData("invocation").(bool)) -} diff --git a/server/web/filter_chain_test.go b/server/web/filter_chain_test.go deleted file mode 100644 index e175ab29..00000000 --- a/server/web/filter_chain_test.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 web - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/server/web/context" -) - -func TestControllerRegister_InsertFilterChain(t *testing.T) { - - InsertFilterChain("/*", func(next FilterFunc) FilterFunc { - return func(ctx *context.Context) { - ctx.Output.Header("filter", "filter-chain") - next(ctx) - } - }) - - ns := NewNamespace("/chain") - - ns.Get("/*", func(ctx *context.Context) { - ctx.Output.Body([]byte("hello")) - }) - - r, _ := http.NewRequest("GET", "/chain/user", nil) - w := httptest.NewRecorder() - - BeeApp.Handlers.ServeHTTP(w, r) - - assert.Equal(t, "filter-chain", w.Header().Get("filter")) -} diff --git a/server/web/server.go b/server/web/server.go deleted file mode 100644 index 25841563..00000000 --- a/server/web/server.go +++ /dev/null @@ -1,751 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 web - -import ( - "crypto/tls" - "crypto/x509" - "fmt" - "io/ioutil" - "net" - "net/http" - "net/http/fcgi" - "os" - "path" - "strconv" - "strings" - "text/template" - "time" - - "golang.org/x/crypto/acme/autocert" - - "github.com/astaxie/beego/core/logs" - beecontext "github.com/astaxie/beego/server/web/context" - - "github.com/astaxie/beego/core/utils" - "github.com/astaxie/beego/server/web/grace" -) - -var ( - // BeeApp is an application instance - // If you are using single server, you could use this - // But if you need multiple servers, do not use this - BeeApp *HttpServer -) - -func init() { - // create beego application - BeeApp = NewHttpSever() -} - -// HttpServer defines beego application with a new PatternServeMux. -type HttpServer struct { - Handlers *ControllerRegister - Server *http.Server - Cfg *Config -} - -// NewHttpSever returns a new beego application. -// this method will use the BConfig as the configure to create HttpServer -// Be careful that when you update BConfig, the server's Cfg will be updated too -func NewHttpSever() *HttpServer { - return NewHttpServerWithCfg(BConfig) -} - -// NewHttpServerWithCfg will create an sever with specific cfg -func NewHttpServerWithCfg(cfg *Config) *HttpServer { - cr := NewControllerRegisterWithCfg(cfg) - app := &HttpServer{ - Handlers: cr, - Server: &http.Server{}, - Cfg: cfg, - } - - return app -} - -// MiddleWare function for http.Handler -type MiddleWare func(http.Handler) http.Handler - -// Run beego application. -func (app *HttpServer) Run(addr string, mws ...MiddleWare) { - - initBeforeHTTPRun() - - app.initAddr(addr) - - addr = app.Cfg.Listen.HTTPAddr - - if app.Cfg.Listen.HTTPPort != 0 { - addr = fmt.Sprintf("%s:%d", app.Cfg.Listen.HTTPAddr, app.Cfg.Listen.HTTPPort) - } - - var ( - err error - l net.Listener - endRunning = make(chan bool, 1) - ) - - // run cgi server - if app.Cfg.Listen.EnableFcgi { - if app.Cfg.Listen.EnableStdIo { - if err = fcgi.Serve(nil, app.Handlers); err == nil { // standard I/O - logs.Info("Use FCGI via standard I/O") - } else { - logs.Critical("Cannot use FCGI via standard I/O", err) - } - return - } - if app.Cfg.Listen.HTTPPort == 0 { - // remove the Socket file before start - if utils.FileExists(addr) { - os.Remove(addr) - } - l, err = net.Listen("unix", addr) - } else { - l, err = net.Listen("tcp", addr) - } - if err != nil { - logs.Critical("Listen: ", err) - } - if err = fcgi.Serve(l, app.Handlers); err != nil { - logs.Critical("fcgi.Serve: ", err) - } - return - } - - app.Server.Handler = app.Handlers - for i := len(mws) - 1; i >= 0; i-- { - if mws[i] == nil { - continue - } - app.Server.Handler = mws[i](app.Server.Handler) - } - app.Server.ReadTimeout = time.Duration(app.Cfg.Listen.ServerTimeOut) * time.Second - app.Server.WriteTimeout = time.Duration(app.Cfg.Listen.ServerTimeOut) * time.Second - app.Server.ErrorLog = logs.GetLogger("HTTP") - - // run graceful mode - if app.Cfg.Listen.Graceful { - httpsAddr := app.Cfg.Listen.HTTPSAddr - app.Server.Addr = httpsAddr - if app.Cfg.Listen.EnableHTTPS || app.Cfg.Listen.EnableMutualHTTPS { - go func() { - time.Sleep(1000 * time.Microsecond) - if app.Cfg.Listen.HTTPSPort != 0 { - httpsAddr = fmt.Sprintf("%s:%d", app.Cfg.Listen.HTTPSAddr, app.Cfg.Listen.HTTPSPort) - app.Server.Addr = httpsAddr - } - server := grace.NewServer(httpsAddr, app.Server.Handler) - server.Server.ReadTimeout = app.Server.ReadTimeout - server.Server.WriteTimeout = app.Server.WriteTimeout - if app.Cfg.Listen.EnableMutualHTTPS { - if err := server.ListenAndServeMutualTLS(app.Cfg.Listen.HTTPSCertFile, - app.Cfg.Listen.HTTPSKeyFile, - app.Cfg.Listen.TrustCaFile); err != nil { - logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid())) - time.Sleep(100 * time.Microsecond) - } - } else { - if app.Cfg.Listen.AutoTLS { - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist(app.Cfg.Listen.Domains...), - Cache: autocert.DirCache(app.Cfg.Listen.TLSCacheDir), - } - app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate} - app.Cfg.Listen.HTTPSCertFile, app.Cfg.Listen.HTTPSKeyFile = "", "" - } - if err := server.ListenAndServeTLS(app.Cfg.Listen.HTTPSCertFile, app.Cfg.Listen.HTTPSKeyFile); err != nil { - logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid())) - time.Sleep(100 * time.Microsecond) - } - } - endRunning <- true - }() - } - if app.Cfg.Listen.EnableHTTP { - go func() { - server := grace.NewServer(addr, app.Server.Handler) - server.Server.ReadTimeout = app.Server.ReadTimeout - server.Server.WriteTimeout = app.Server.WriteTimeout - if app.Cfg.Listen.ListenTCP4 { - server.Network = "tcp4" - } - if err := server.ListenAndServe(); err != nil { - logs.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid())) - time.Sleep(100 * time.Microsecond) - } - endRunning <- true - }() - } - <-endRunning - return - } - - // run normal mode - if app.Cfg.Listen.EnableHTTPS || app.Cfg.Listen.EnableMutualHTTPS { - go func() { - time.Sleep(1000 * time.Microsecond) - if app.Cfg.Listen.HTTPSPort != 0 { - app.Server.Addr = fmt.Sprintf("%s:%d", app.Cfg.Listen.HTTPSAddr, app.Cfg.Listen.HTTPSPort) - } else if app.Cfg.Listen.EnableHTTP { - logs.Info("Start https server error, conflict with http. Please reset https port") - return - } - logs.Info("https server Running on https://%s", app.Server.Addr) - if app.Cfg.Listen.AutoTLS { - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist(app.Cfg.Listen.Domains...), - Cache: autocert.DirCache(app.Cfg.Listen.TLSCacheDir), - } - app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate} - app.Cfg.Listen.HTTPSCertFile, app.Cfg.Listen.HTTPSKeyFile = "", "" - } else if app.Cfg.Listen.EnableMutualHTTPS { - pool := x509.NewCertPool() - data, err := ioutil.ReadFile(app.Cfg.Listen.TrustCaFile) - if err != nil { - logs.Info("MutualHTTPS should provide TrustCaFile") - return - } - pool.AppendCertsFromPEM(data) - app.Server.TLSConfig = &tls.Config{ - ClientCAs: pool, - ClientAuth: tls.ClientAuthType(app.Cfg.Listen.ClientAuth), - } - } - if err := app.Server.ListenAndServeTLS(app.Cfg.Listen.HTTPSCertFile, app.Cfg.Listen.HTTPSKeyFile); err != nil { - logs.Critical("ListenAndServeTLS: ", err) - time.Sleep(100 * time.Microsecond) - endRunning <- true - } - }() - - } - if app.Cfg.Listen.EnableHTTP { - go func() { - app.Server.Addr = addr - logs.Info("http server Running on http://%s", app.Server.Addr) - if app.Cfg.Listen.ListenTCP4 { - ln, err := net.Listen("tcp4", app.Server.Addr) - if err != nil { - logs.Critical("ListenAndServe: ", err) - time.Sleep(100 * time.Microsecond) - endRunning <- true - return - } - if err = app.Server.Serve(ln); err != nil { - logs.Critical("ListenAndServe: ", err) - time.Sleep(100 * time.Microsecond) - endRunning <- true - return - } - } else { - if err := app.Server.ListenAndServe(); err != nil { - logs.Critical("ListenAndServe: ", err) - time.Sleep(100 * time.Microsecond) - endRunning <- true - } - } - }() - } - <-endRunning -} - -// Router see HttpServer.Router -func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *HttpServer { - return BeeApp.Router(rootpath, c, mappingMethods...) -} - -// Router adds a patterned controller handler to BeeApp. -// it's an alias method of HttpServer.Router. -// usage: -// simple router -// beego.Router("/admin", &admin.UserController{}) -// beego.Router("/admin/index", &admin.ArticleController{}) -// -// regex router -// -// beego.Router("/api/:id([0-9]+)", &controllers.RController{}) -// -// custom rules -// beego.Router("/api/list",&RestController{},"*:ListFood") -// beego.Router("/api/create",&RestController{},"post:CreateFood") -// beego.Router("/api/update",&RestController{},"put:UpdateFood") -// beego.Router("/api/delete",&RestController{},"delete:DeleteFood") -func (app *HttpServer) Router(rootPath string, c ControllerInterface, mappingMethods ...string) *HttpServer { - app.Handlers.Add(rootPath, c, mappingMethods...) - return app -} - -// UnregisterFixedRoute see HttpServer.UnregisterFixedRoute -func UnregisterFixedRoute(fixedRoute string, method string) *HttpServer { - return BeeApp.UnregisterFixedRoute(fixedRoute, method) -} - -// UnregisterFixedRoute unregisters the route with the specified fixedRoute. It is particularly useful -// in web applications that inherit most routes from a base webapp via the underscore -// import, and aim to overwrite only certain paths. -// The method parameter can be empty or "*" for all HTTP methods, or a particular -// method type (e.g. "GET" or "POST") for selective removal. -// -// Usage (replace "GET" with "*" for all methods): -// beego.UnregisterFixedRoute("/yourpreviouspath", "GET") -// beego.Router("/yourpreviouspath", yourControllerAddress, "get:GetNewPage") -func (app *HttpServer) UnregisterFixedRoute(fixedRoute string, method string) *HttpServer { - subPaths := splitPath(fixedRoute) - if method == "" || method == "*" { - for m := range HTTPMETHOD { - if _, ok := app.Handlers.routers[m]; !ok { - continue - } - if app.Handlers.routers[m].prefix == strings.Trim(fixedRoute, "/ ") { - findAndRemoveSingleTree(app.Handlers.routers[m]) - continue - } - findAndRemoveTree(subPaths, app.Handlers.routers[m], m) - } - return app - } - // Single HTTP method - um := strings.ToUpper(method) - if _, ok := app.Handlers.routers[um]; ok { - if app.Handlers.routers[um].prefix == strings.Trim(fixedRoute, "/ ") { - findAndRemoveSingleTree(app.Handlers.routers[um]) - return app - } - findAndRemoveTree(subPaths, app.Handlers.routers[um], um) - } - return app -} - -func findAndRemoveTree(paths []string, entryPointTree *Tree, method string) { - for i := range entryPointTree.fixrouters { - if entryPointTree.fixrouters[i].prefix == paths[0] { - if len(paths) == 1 { - if len(entryPointTree.fixrouters[i].fixrouters) > 0 { - // If the route had children subtrees, remove just the functional leaf, - // to allow children to function as before - if len(entryPointTree.fixrouters[i].leaves) > 0 { - entryPointTree.fixrouters[i].leaves[0] = nil - entryPointTree.fixrouters[i].leaves = entryPointTree.fixrouters[i].leaves[1:] - } - } else { - // Remove the *Tree from the fixrouters slice - entryPointTree.fixrouters[i] = nil - - if i == len(entryPointTree.fixrouters)-1 { - entryPointTree.fixrouters = entryPointTree.fixrouters[:i] - } else { - entryPointTree.fixrouters = append(entryPointTree.fixrouters[:i], entryPointTree.fixrouters[i+1:len(entryPointTree.fixrouters)]...) - } - } - return - } - findAndRemoveTree(paths[1:], entryPointTree.fixrouters[i], method) - } - } -} - -func findAndRemoveSingleTree(entryPointTree *Tree) { - if entryPointTree == nil { - return - } - if len(entryPointTree.fixrouters) > 0 { - // If the route had children subtrees, remove just the functional leaf, - // to allow children to function as before - if len(entryPointTree.leaves) > 0 { - entryPointTree.leaves[0] = nil - entryPointTree.leaves = entryPointTree.leaves[1:] - } - } -} - -// Include see HttpServer.Include -func Include(cList ...ControllerInterface) *HttpServer { - return BeeApp.Include(cList...) -} - -// Include will generate router file in the router/xxx.go from the controller's comments -// usage: -// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) -// type BankAccount struct{ -// beego.Controller -// } -// -// register the function -// func (b *BankAccount)Mapping(){ -// b.Mapping("ShowAccount" , b.ShowAccount) -// b.Mapping("ModifyAccount", b.ModifyAccount) -// } -// -// //@router /account/:id [get] -// func (b *BankAccount) ShowAccount(){ -// //logic -// } -// -// -// //@router /account/:id [post] -// func (b *BankAccount) ModifyAccount(){ -// //logic -// } -// -// the comments @router url methodlist -// url support all the function Router's pattern -// methodlist [get post head put delete options *] -func (app *HttpServer) Include(cList ...ControllerInterface) *HttpServer { - app.Handlers.Include(cList...) - return app -} - -// RESTRouter see HttpServer.RESTRouter -func RESTRouter(rootpath string, c ControllerInterface) *HttpServer { - return BeeApp.RESTRouter(rootpath, c) -} - -// RESTRouter adds a restful controller handler to BeeApp. -// its' controller implements beego.ControllerInterface and -// defines a param "pattern/:objectId" to visit each resource. -func (app *HttpServer) RESTRouter(rootpath string, c ControllerInterface) *HttpServer { - app.Router(rootpath, c) - app.Router(path.Join(rootpath, ":objectId"), c) - return app -} - -// AutoRouter see HttpServer.AutoRouter -func AutoRouter(c ControllerInterface) *HttpServer { - return BeeApp.AutoRouter(c) -} - -// AutoRouter adds defined controller handler to BeeApp. -// it's same to HttpServer.AutoRouter. -// if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page, -// visit the url /main/list to exec List function or /main/page to exec Page function. -func (app *HttpServer) AutoRouter(c ControllerInterface) *HttpServer { - app.Handlers.AddAuto(c) - return app -} - -// AutoPrefix see HttpServer.AutoPrefix -func AutoPrefix(prefix string, c ControllerInterface) *HttpServer { - return BeeApp.AutoPrefix(prefix, c) -} - -// AutoPrefix adds controller handler to BeeApp with prefix. -// it's same to HttpServer.AutoRouterWithPrefix. -// if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page, -// visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function. -func (app *HttpServer) AutoPrefix(prefix string, c ControllerInterface) *HttpServer { - app.Handlers.AddAutoPrefix(prefix, c) - return app -} - -// Get see HttpServer.Get -func Get(rootpath string, f FilterFunc) *HttpServer { - return BeeApp.Get(rootpath, f) -} - -// Get used to register router for Get method -// usage: -// beego.Get("/", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (app *HttpServer) Get(rootpath string, f FilterFunc) *HttpServer { - app.Handlers.Get(rootpath, f) - return app -} - -// Post see HttpServer.Post -func Post(rootpath string, f FilterFunc) *HttpServer { - return BeeApp.Post(rootpath, f) -} - -// Post used to register router for Post method -// usage: -// beego.Post("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (app *HttpServer) Post(rootpath string, f FilterFunc) *HttpServer { - app.Handlers.Post(rootpath, f) - return app -} - -// Delete see HttpServer.Delete -func Delete(rootpath string, f FilterFunc) *HttpServer { - return BeeApp.Delete(rootpath, f) -} - -// Delete used to register router for Delete method -// usage: -// beego.Delete("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (app *HttpServer) Delete(rootpath string, f FilterFunc) *HttpServer { - app.Handlers.Delete(rootpath, f) - return app -} - -// Put see HttpServer.Put -func Put(rootpath string, f FilterFunc) *HttpServer { - return BeeApp.Put(rootpath, f) -} - -// Put used to register router for Put method -// usage: -// beego.Put("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (app *HttpServer) Put(rootpath string, f FilterFunc) *HttpServer { - app.Handlers.Put(rootpath, f) - return app -} - -// Head see HttpServer.Head -func Head(rootpath string, f FilterFunc) *HttpServer { - return BeeApp.Head(rootpath, f) -} - -// Head used to register router for Head method -// usage: -// beego.Head("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (app *HttpServer) Head(rootpath string, f FilterFunc) *HttpServer { - app.Handlers.Head(rootpath, f) - return app -} - -// Options see HttpServer.Options -func Options(rootpath string, f FilterFunc) *HttpServer { - BeeApp.Handlers.Options(rootpath, f) - return BeeApp -} - -// Options used to register router for Options method -// usage: -// beego.Options("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (app *HttpServer) Options(rootpath string, f FilterFunc) *HttpServer { - app.Handlers.Options(rootpath, f) - return app -} - -// Patch see HttpServer.Patch -func Patch(rootpath string, f FilterFunc) *HttpServer { - return BeeApp.Patch(rootpath, f) -} - -// Patch used to register router for Patch method -// usage: -// beego.Patch("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (app *HttpServer) Patch(rootpath string, f FilterFunc) *HttpServer { - app.Handlers.Patch(rootpath, f) - return app -} - -// Any see HttpServer.Any -func Any(rootpath string, f FilterFunc) *HttpServer { - return BeeApp.Any(rootpath, f) -} - -// Any used to register router for all methods -// usage: -// beego.Any("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func (app *HttpServer) Any(rootpath string, f FilterFunc) *HttpServer { - app.Handlers.Any(rootpath, f) - return app -} - -// Handler see HttpServer.Handler -func Handler(rootpath string, h http.Handler, options ...interface{}) *HttpServer { - return BeeApp.Handler(rootpath, h, options...) -} - -// Handler used to register a Handler router -// usage: -// beego.Handler("/api", http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { -// fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) -// })) -func (app *HttpServer) Handler(rootpath string, h http.Handler, options ...interface{}) *HttpServer { - app.Handlers.Handler(rootpath, h, options...) - return app -} - -// InserFilter see HttpServer.InsertFilter -func InsertFilter(pattern string, pos int, filter FilterFunc, opts ...FilterOpt) *HttpServer { - return BeeApp.InsertFilter(pattern, pos, filter, opts...) -} - -// InsertFilter adds a FilterFunc with pattern condition and action constant. -// The pos means action constant including -// beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter. -// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute) -func (app *HttpServer) InsertFilter(pattern string, pos int, filter FilterFunc, opts ...FilterOpt) *HttpServer { - app.Handlers.InsertFilter(pattern, pos, filter, opts...) - return app -} - -// InsertFilterChain see HttpServer.InsertFilterChain -func InsertFilterChain(pattern string, filterChain FilterChain, opts ...FilterOpt) *HttpServer { - return BeeApp.InsertFilterChain(pattern, filterChain, opts...) -} - -// InsertFilterChain adds a FilterFunc built by filterChain. -// This filter will be executed before all filters. -// the filter's behavior like stack's behavior -// and the last filter is serving the http request -func (app *HttpServer) InsertFilterChain(pattern string, filterChain FilterChain, opts ...FilterOpt) *HttpServer { - app.Handlers.InsertFilterChain(pattern, filterChain, opts...) - return app -} - -func (app *HttpServer) initAddr(addr string) { - strs := strings.Split(addr, ":") - if len(strs) > 0 && strs[0] != "" { - app.Cfg.Listen.HTTPAddr = strs[0] - app.Cfg.Listen.Domains = []string{strs[0]} - } - if len(strs) > 1 && strs[1] != "" { - app.Cfg.Listen.HTTPPort, _ = strconv.Atoi(strs[1]) - } -} - -func (app *HttpServer) LogAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) { - // Skip logging if AccessLogs config is false - if !app.Cfg.Log.AccessLogs { - return - } - // Skip logging static requests unless EnableStaticLogs config is true - if !app.Cfg.Log.EnableStaticLogs && DefaultAccessLogFilter.Filter(ctx) { - return - } - var ( - requestTime time.Time - elapsedTime time.Duration - r = ctx.Request - ) - if startTime != nil { - requestTime = *startTime - elapsedTime = time.Since(*startTime) - } - record := &logs.AccessLogRecord{ - RemoteAddr: ctx.Input.IP(), - RequestTime: requestTime, - RequestMethod: r.Method, - Request: fmt.Sprintf("%s %s %s", r.Method, r.RequestURI, r.Proto), - ServerProtocol: r.Proto, - Host: r.Host, - Status: statusCode, - ElapsedTime: elapsedTime, - HTTPReferrer: r.Header.Get("Referer"), - HTTPUserAgent: r.Header.Get("User-Agent"), - RemoteUser: r.Header.Get("Remote-User"), - BodyBytesSent: r.ContentLength, - } - logs.AccessLog(record, app.Cfg.Log.AccessLogsFormat) -} - -// PrintTree prints all registered routers. -func (app *HttpServer) PrintTree() M { - var ( - content = M{} - methods = []string{} - methodsData = make(M) - ) - for method, t := range app.Handlers.routers { - - resultList := new([][]string) - - printTree(resultList, t) - - methods = append(methods, template.HTMLEscapeString(method)) - methodsData[template.HTMLEscapeString(method)] = resultList - } - - content["Data"] = methodsData - content["Methods"] = methods - return content -} - -func printTree(resultList *[][]string, t *Tree) { - for _, tr := range t.fixrouters { - printTree(resultList, tr) - } - if t.wildcard != nil { - printTree(resultList, t.wildcard) - } - for _, l := range t.leaves { - if v, ok := l.runObject.(*ControllerInfo); ok { - if v.routerType == routerTypeBeego { - var result = []string{ - template.HTMLEscapeString(v.pattern), - template.HTMLEscapeString(fmt.Sprintf("%s", v.methods)), - template.HTMLEscapeString(v.controllerType.String()), - } - *resultList = append(*resultList, result) - } else if v.routerType == routerTypeRESTFul { - var result = []string{ - template.HTMLEscapeString(v.pattern), - template.HTMLEscapeString(fmt.Sprintf("%s", v.methods)), - "", - } - *resultList = append(*resultList, result) - } else if v.routerType == routerTypeHandler { - var result = []string{ - template.HTMLEscapeString(v.pattern), - "", - "", - } - *resultList = append(*resultList, result) - } - } - } -} - -func (app *HttpServer) reportFilter() M { - filterTypeData := make(M) - // filterTypes := []string{} - if app.Handlers.enableFilter { - // var filterType string - for k, fr := range map[int]string{ - BeforeStatic: "Before Static", - BeforeRouter: "Before Router", - BeforeExec: "Before Exec", - AfterExec: "After Exec", - FinishRouter: "Finish Router", - } { - if bf := app.Handlers.filters[k]; len(bf) > 0 { - resultList := new([][]string) - for _, f := range bf { - var result = []string{ - // void xss - template.HTMLEscapeString(f.pattern), - template.HTMLEscapeString(utils.GetFuncName(f.filterFunc)), - } - *resultList = append(*resultList, result) - } - filterTypeData[fr] = resultList - } - } - } - - return filterTypeData -} diff --git a/server/web/server_test.go b/server/web/server_test.go deleted file mode 100644 index 0b0c601c..00000000 --- a/server/web/server_test.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 web - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNewHttpServerWithCfg(t *testing.T) { - - BConfig.AppName = "Before" - svr := NewHttpServerWithCfg(BConfig) - svr.Cfg.AppName = "hello" - assert.Equal(t, "hello", BConfig.AppName) - -} diff --git a/server/web/session/couchbase/sess_couchbase_test.go b/server/web/session/couchbase/sess_couchbase_test.go deleted file mode 100644 index 5959f9c3..00000000 --- a/server/web/session/couchbase/sess_couchbase_test.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 couchbase - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestProvider_SessionInit(t *testing.T) { - // using old style - savePath := `http://host:port/,Pool,Bucket` - cp := &Provider{} - cp.SessionInit(context.Background(), 12, savePath) - assert.Equal(t, "http://host:port/", cp.SavePath) - assert.Equal(t, "Pool", cp.Pool) - assert.Equal(t, "Bucket", cp.Bucket) - assert.Equal(t, int64(12), cp.maxlifetime) - - savePath = ` -{ "save_path": "my save path", "pool": "mypool", "bucket": "mybucket"} -` - cp = &Provider{} - cp.SessionInit(context.Background(), 12, savePath) - assert.Equal(t, "my save path", cp.SavePath) - assert.Equal(t, "mypool", cp.Pool) - assert.Equal(t, "mybucket", cp.Bucket) - assert.Equal(t, int64(12), cp.maxlifetime) -} diff --git a/server/web/session/ledis/ledis_session_test.go b/server/web/session/ledis/ledis_session_test.go deleted file mode 100644 index 1cfb3ed1..00000000 --- a/server/web/session/ledis/ledis_session_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 ledis - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestProvider_SessionInit(t *testing.T) { - // using old style - savePath := `http://host:port/,100` - cp := &Provider{} - cp.SessionInit(context.Background(), 12, savePath) - assert.Equal(t, "http://host:port/", cp.SavePath) - assert.Equal(t, 100, cp.Db) - assert.Equal(t, int64(12), cp.maxlifetime) - - savePath = ` -{ "save_path": "my save path", "db": 100} -` - cp = &Provider{} - cp.SessionInit(context.Background(), 12, savePath) - assert.Equal(t, "my save path", cp.SavePath) - assert.Equal(t, 100, cp.Db) - assert.Equal(t, int64(12), cp.maxlifetime) -} diff --git a/server/web/session/redis/sess_redis_test.go b/server/web/session/redis/sess_redis_test.go deleted file mode 100644 index 64dbc9f9..00000000 --- a/server/web/session/redis/sess_redis_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package redis - -import ( - "context" - "fmt" - "net/http" - "net/http/httptest" - "os" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/server/web/session" -) - -func TestRedis(t *testing.T) { - sessionConfig := &session.ManagerConfig{ - CookieName: "gosessionid", - EnableSetCookie: true, - Gclifetime: 3600, - Maxlifetime: 3600, - Secure: false, - CookieLifeTime: 3600, - } - - redisAddr := os.Getenv("REDIS_ADDR") - if redisAddr == "" { - redisAddr = "127.0.0.1:6379" - } - - sessionConfig.ProviderConfig = fmt.Sprintf("%s,100,,0,30", redisAddr) - globalSession, err := session.NewManager("redis", sessionConfig) - if err != nil { - t.Fatal("could not create manager:", err) - } - - go globalSession.GC() - - r, _ := http.NewRequest("GET", "/", nil) - w := httptest.NewRecorder() - - sess, err := globalSession.SessionStart(w, r) - if err != nil { - t.Fatal("session start failed:", err) - } - defer sess.SessionRelease(nil, w) - - // SET AND GET - err = sess.Set(nil, "username", "astaxie") - if err != nil { - t.Fatal("set username failed:", err) - } - username := sess.Get(nil, "username") - if username != "astaxie" { - t.Fatal("get username failed") - } - - // DELETE - err = sess.Delete(nil, "username") - if err != nil { - t.Fatal("delete username failed:", err) - } - username = sess.Get(nil, "username") - if username != nil { - t.Fatal("delete username failed") - } - - // FLUSH - err = sess.Set(nil, "username", "astaxie") - if err != nil { - t.Fatal("set failed:", err) - } - err = sess.Set(nil, "password", "1qaz2wsx") - if err != nil { - t.Fatal("set failed:", err) - } - username = sess.Get(nil, "username") - if username != "astaxie" { - t.Fatal("get username failed") - } - password := sess.Get(nil, "password") - if password != "1qaz2wsx" { - t.Fatal("get password failed") - } - err = sess.Flush(nil) - if err != nil { - t.Fatal("flush failed:", err) - } - username = sess.Get(nil, "username") - if username != nil { - t.Fatal("flush failed") - } - password = sess.Get(nil, "password") - if password != nil { - t.Fatal("flush failed") - } - - sess.SessionRelease(nil, w) -} - -func TestProvider_SessionInit(t *testing.T) { - - savePath := ` -{ "save_path": "my save path", "idle_timeout": "3s"} -` - cp := &Provider{} - cp.SessionInit(context.Background(), 12, savePath) - assert.Equal(t, "my save path", cp.SavePath) - assert.Equal(t, 3*time.Second, cp.idleTimeout) - assert.Equal(t, int64(12), cp.maxlifetime) -} diff --git a/server/web/session/redis_cluster/redis_cluster_test.go b/server/web/session/redis_cluster/redis_cluster_test.go deleted file mode 100644 index 0192cd87..00000000 --- a/server/web/session/redis_cluster/redis_cluster_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 redis_cluster - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestProvider_SessionInit(t *testing.T) { - - savePath := ` -{ "save_path": "my save path", "idle_timeout": "3s"} -` - cp := &Provider{} - cp.SessionInit(context.Background(), 12, savePath) - assert.Equal(t, "my save path", cp.SavePath) - assert.Equal(t, 3*time.Second, cp.idleTimeout) - assert.Equal(t, int64(12), cp.maxlifetime) -} diff --git a/server/web/session/redis_sentinel/sess_redis_sentinel_test.go b/server/web/session/redis_sentinel/sess_redis_sentinel_test.go deleted file mode 100644 index f052a14a..00000000 --- a/server/web/session/redis_sentinel/sess_redis_sentinel_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package redis_sentinel - -import ( - "context" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/server/web/session" -) - -func TestRedisSentinel(t *testing.T) { - sessionConfig := &session.ManagerConfig{ - CookieName: "gosessionid", - EnableSetCookie: true, - Gclifetime: 3600, - Maxlifetime: 3600, - Secure: false, - CookieLifeTime: 3600, - ProviderConfig: "127.0.0.1:6379,100,,0,master", - } - globalSessions, e := session.NewManager("redis_sentinel", sessionConfig) - if e != nil { - t.Log(e) - return - } - // todo test if e==nil - go globalSessions.GC() - - r, _ := http.NewRequest("GET", "/", nil) - w := httptest.NewRecorder() - - sess, err := globalSessions.SessionStart(w, r) - if err != nil { - t.Fatal("session start failed:", err) - } - defer sess.SessionRelease(nil, w) - - // SET AND GET - err = sess.Set(nil, "username", "astaxie") - if err != nil { - t.Fatal("set username failed:", err) - } - username := sess.Get(nil, "username") - if username != "astaxie" { - t.Fatal("get username failed") - } - - // DELETE - err = sess.Delete(nil, "username") - if err != nil { - t.Fatal("delete username failed:", err) - } - username = sess.Get(nil, "username") - if username != nil { - t.Fatal("delete username failed") - } - - // FLUSH - err = sess.Set(nil, "username", "astaxie") - if err != nil { - t.Fatal("set failed:", err) - } - err = sess.Set(nil, "password", "1qaz2wsx") - if err != nil { - t.Fatal("set failed:", err) - } - username = sess.Get(nil, "username") - if username != "astaxie" { - t.Fatal("get username failed") - } - password := sess.Get(nil, "password") - if password != "1qaz2wsx" { - t.Fatal("get password failed") - } - err = sess.Flush(nil) - if err != nil { - t.Fatal("flush failed:", err) - } - username = sess.Get(nil, "username") - if username != nil { - t.Fatal("flush failed") - } - password = sess.Get(nil, "password") - if password != nil { - t.Fatal("flush failed") - } - - sess.SessionRelease(nil, w) - -} - -func TestProvider_SessionInit(t *testing.T) { - - savePath := ` -{ "save_path": "my save path", "idle_timeout": "3s"} -` - cp := &Provider{} - cp.SessionInit(context.Background(), 12, savePath) - assert.Equal(t, "my save path", cp.SavePath) - assert.Equal(t, 3*time.Second, cp.idleTimeout) - assert.Equal(t, int64(12), cp.maxlifetime) -} diff --git a/server/web/session/sess_cookie_test.go b/server/web/session/sess_cookie_test.go deleted file mode 100644 index a9fc876d..00000000 --- a/server/web/session/sess_cookie_test.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 session - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "strings" - "testing" -) - -func TestCookie(t *testing.T) { - config := `{"cookieName":"gosessionid","enableSetCookie":false,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}` - conf := new(ManagerConfig) - if err := json.Unmarshal([]byte(config), conf); err != nil { - t.Fatal("json decode error", err) - } - globalSessions, err := NewManager("cookie", conf) - if err != nil { - t.Fatal("init cookie session err", err) - } - r, _ := http.NewRequest("GET", "/", nil) - w := httptest.NewRecorder() - sess, err := globalSessions.SessionStart(w, r) - if err != nil { - t.Fatal("set error,", err) - } - err = sess.Set(nil, "username", "astaxie") - if err != nil { - t.Fatal("set error,", err) - } - if username := sess.Get(nil, "username"); username != "astaxie" { - t.Fatal("get username error") - } - sess.SessionRelease(nil, w) - if cookiestr := w.Header().Get("Set-Cookie"); cookiestr == "" { - t.Fatal("setcookie error") - } else { - parts := strings.Split(strings.TrimSpace(cookiestr), ";") - for k, v := range parts { - nameval := strings.Split(v, "=") - if k == 0 && nameval[0] != "gosessionid" { - t.Fatal("error") - } - } - } -} - -func TestDestorySessionCookie(t *testing.T) { - config := `{"cookieName":"gosessionid","enableSetCookie":true,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}` - conf := new(ManagerConfig) - if err := json.Unmarshal([]byte(config), conf); err != nil { - t.Fatal("json decode error", err) - } - globalSessions, err := NewManager("cookie", conf) - if err != nil { - t.Fatal("init cookie session err", err) - } - - r, _ := http.NewRequest("GET", "/", nil) - w := httptest.NewRecorder() - session, err := globalSessions.SessionStart(w, r) - if err != nil { - t.Fatal("session start err,", err) - } - - // request again ,will get same sesssion id . - r1, _ := http.NewRequest("GET", "/", nil) - r1.Header.Set("Cookie", w.Header().Get("Set-Cookie")) - w = httptest.NewRecorder() - newSession, err := globalSessions.SessionStart(w, r1) - if err != nil { - t.Fatal("session start err,", err) - } - if newSession.SessionID(nil) != session.SessionID(nil) { - t.Fatal("get cookie session id is not the same again.") - } - - // After destroy session , will get a new session id . - globalSessions.SessionDestroy(w, r1) - r2, _ := http.NewRequest("GET", "/", nil) - r2.Header.Set("Cookie", w.Header().Get("Set-Cookie")) - - w = httptest.NewRecorder() - newSession, err = globalSessions.SessionStart(w, r2) - if err != nil { - t.Fatal("session start error") - } - if newSession.SessionID(nil) == session.SessionID(nil) { - t.Fatal("after destroy session and reqeust again ,get cookie session id is same.") - } -} diff --git a/server/web/session/sess_file_test.go b/server/web/session/sess_file_test.go deleted file mode 100644 index f40de69f..00000000 --- a/server/web/session/sess_file_test.go +++ /dev/null @@ -1,427 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 session - -import ( - "context" - "fmt" - "os" - "sync" - "testing" - "time" -) - -const sid = "Session_id" -const sidNew = "Session_id_new" -const sessionPath = "./_session_runtime" - -var ( - mutex sync.Mutex -) - -func TestFileProvider_SessionInit(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(context.Background(), 180, sessionPath) - if fp.maxlifetime != 180 { - t.Error() - } - - if fp.savePath != sessionPath { - t.Error() - } -} - -func TestFileProvider_SessionExist(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(context.Background(), 180, sessionPath) - - exists, err := fp.SessionExist(context.Background(), sid) - if err != nil { - t.Error(err) - } - if exists { - t.Error() - } - - _, err = fp.SessionRead(context.Background(), sid) - if err != nil { - t.Error(err) - } - - exists, err = fp.SessionExist(context.Background(), sid) - if err != nil { - t.Error(err) - } - if !exists { - t.Error() - } -} - -func TestFileProvider_SessionExist2(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(context.Background(), 180, sessionPath) - - exists, err := fp.SessionExist(context.Background(), sid) - if err != nil { - t.Error(err) - } - if exists { - t.Error() - } - - exists, err = fp.SessionExist(context.Background(), "") - if err == nil { - t.Error() - } - if exists { - t.Error() - } - - exists, err = fp.SessionExist(context.Background(), "1") - if err == nil { - t.Error() - } - if exists { - t.Error() - } -} - -func TestFileProvider_SessionRead(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(context.Background(), 180, sessionPath) - - s, err := fp.SessionRead(context.Background(), sid) - if err != nil { - t.Error(err) - } - - _ = s.Set(nil, "sessionValue", 18975) - v := s.Get(nil, "sessionValue") - - if v.(int) != 18975 { - t.Error() - } -} - -func TestFileProvider_SessionRead1(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(context.Background(), 180, sessionPath) - - _, err := fp.SessionRead(context.Background(), "") - if err == nil { - t.Error(err) - } - - _, err = fp.SessionRead(context.Background(), "1") - if err == nil { - t.Error(err) - } -} - -func TestFileProvider_SessionAll(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(context.Background(), 180, sessionPath) - - sessionCount := 546 - - for i := 1; i <= sessionCount; i++ { - _, err := fp.SessionRead(context.Background(), fmt.Sprintf("%s_%d", sid, i)) - if err != nil { - t.Error(err) - } - } - - if fp.SessionAll(nil) != sessionCount { - t.Error() - } -} - -func TestFileProvider_SessionRegenerate(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(context.Background(), 180, sessionPath) - - _, err := fp.SessionRead(context.Background(), sid) - if err != nil { - t.Error(err) - } - - exists, err := fp.SessionExist(context.Background(), sid) - if err != nil { - t.Error(err) - } - if !exists { - t.Error() - } - - _, err = fp.SessionRegenerate(context.Background(), sid, sidNew) - if err != nil { - t.Error(err) - } - - exists, err = fp.SessionExist(context.Background(), sid) - if err != nil { - t.Error(err) - } - if exists { - t.Error() - } - - exists, err = fp.SessionExist(context.Background(), sidNew) - if err != nil { - t.Error(err) - } - if !exists { - t.Error() - } -} - -func TestFileProvider_SessionDestroy(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(context.Background(), 180, sessionPath) - - _, err := fp.SessionRead(context.Background(), sid) - if err != nil { - t.Error(err) - } - - exists, err := fp.SessionExist(context.Background(), sid) - if err != nil { - t.Error(err) - } - if !exists { - t.Error() - } - - err = fp.SessionDestroy(context.Background(), sid) - if err != nil { - t.Error(err) - } - - exists, err = fp.SessionExist(context.Background(), sid) - if err != nil { - t.Error(err) - } - if exists { - t.Error() - } -} - -func TestFileProvider_SessionGC(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(context.Background(), 1, sessionPath) - - sessionCount := 412 - - for i := 1; i <= sessionCount; i++ { - _, err := fp.SessionRead(context.Background(), fmt.Sprintf("%s_%d", sid, i)) - if err != nil { - t.Error(err) - } - } - - time.Sleep(2 * time.Second) - - fp.SessionGC(nil) - if fp.SessionAll(nil) != 0 { - t.Error() - } -} - -func TestFileSessionStore_Set(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(context.Background(), 180, sessionPath) - - sessionCount := 100 - s, _ := fp.SessionRead(context.Background(), sid) - for i := 1; i <= sessionCount; i++ { - err := s.Set(nil, i, i) - if err != nil { - t.Error(err) - } - } -} - -func TestFileSessionStore_Get(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(context.Background(), 180, sessionPath) - - sessionCount := 100 - s, _ := fp.SessionRead(context.Background(), sid) - for i := 1; i <= sessionCount; i++ { - _ = s.Set(nil, i, i) - - v := s.Get(nil, i) - if v.(int) != i { - t.Error() - } - } -} - -func TestFileSessionStore_Delete(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(context.Background(), 180, sessionPath) - - s, _ := fp.SessionRead(context.Background(), sid) - s.Set(nil, "1", 1) - - if s.Get(nil, "1") == nil { - t.Error() - } - - s.Delete(nil, "1") - - if s.Get(nil, "1") != nil { - t.Error() - } -} - -func TestFileSessionStore_Flush(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(context.Background(), 180, sessionPath) - - sessionCount := 100 - s, _ := fp.SessionRead(context.Background(), sid) - for i := 1; i <= sessionCount; i++ { - _ = s.Set(nil, i, i) - } - - _ = s.Flush(nil) - - for i := 1; i <= sessionCount; i++ { - if s.Get(nil, i) != nil { - t.Error() - } - } -} - -func TestFileSessionStore_SessionID(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(context.Background(), 180, sessionPath) - - sessionCount := 85 - - for i := 1; i <= sessionCount; i++ { - s, err := fp.SessionRead(context.Background(), fmt.Sprintf("%s_%d", sid, i)) - if err != nil { - t.Error(err) - } - if s.SessionID(nil) != fmt.Sprintf("%s_%d", sid, i) { - t.Error(err) - } - } -} - -func TestFileSessionStore_SessionRelease(t *testing.T) { - mutex.Lock() - defer mutex.Unlock() - os.RemoveAll(sessionPath) - defer os.RemoveAll(sessionPath) - fp := &FileProvider{} - - _ = fp.SessionInit(context.Background(), 180, sessionPath) - filepder.savePath = sessionPath - sessionCount := 85 - - for i := 1; i <= sessionCount; i++ { - s, err := fp.SessionRead(context.Background(), fmt.Sprintf("%s_%d", sid, i)) - if err != nil { - t.Error(err) - } - - s.Set(nil, i, i) - s.SessionRelease(nil, nil) - } - - for i := 1; i <= sessionCount; i++ { - s, err := fp.SessionRead(context.Background(), fmt.Sprintf("%s_%d", sid, i)) - if err != nil { - t.Error(err) - } - - if s.Get(nil, i).(int) != i { - t.Error() - } - } -} diff --git a/server/web/session/sess_mem_test.go b/server/web/session/sess_mem_test.go deleted file mode 100644 index e6d35476..00000000 --- a/server/web/session/sess_mem_test.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 session - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "strings" - "testing" -) - -func TestMem(t *testing.T) { - config := `{"cookieName":"gosessionid","gclifetime":10, "enableSetCookie":true}` - conf := new(ManagerConfig) - if err := json.Unmarshal([]byte(config), conf); err != nil { - t.Fatal("json decode error", err) - } - globalSessions, _ := NewManager("memory", conf) - go globalSessions.GC() - r, _ := http.NewRequest("GET", "/", nil) - w := httptest.NewRecorder() - sess, err := globalSessions.SessionStart(w, r) - if err != nil { - t.Fatal("set error,", err) - } - defer sess.SessionRelease(nil, w) - err = sess.Set(nil, "username", "astaxie") - if err != nil { - t.Fatal("set error,", err) - } - if username := sess.Get(nil, "username"); username != "astaxie" { - t.Fatal("get username error") - } - if cookiestr := w.Header().Get("Set-Cookie"); cookiestr == "" { - t.Fatal("setcookie error") - } else { - parts := strings.Split(strings.TrimSpace(cookiestr), ";") - for k, v := range parts { - nameval := strings.Split(v, "=") - if k == 0 && nameval[0] != "gosessionid" { - t.Fatal("error") - } - } - } -} diff --git a/server/web/session/ssdb/sess_ssdb_test.go b/server/web/session/ssdb/sess_ssdb_test.go deleted file mode 100644 index 3de5da0a..00000000 --- a/server/web/session/ssdb/sess_ssdb_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 ssdb - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestProvider_SessionInit(t *testing.T) { - // using old style - savePath := `localhost:8080` - cp := &Provider{} - cp.SessionInit(context.Background(), 12, savePath) - assert.Equal(t, "localhost", cp.Host) - assert.Equal(t, 8080, cp.Port) - assert.Equal(t, int64(12), cp.maxLifetime) - - savePath = ` -{ "host": "localhost", "port": 8080} -` - cp = &Provider{} - cp.SessionInit(context.Background(), 12, savePath) - assert.Equal(t, "localhost", cp.Host) - assert.Equal(t, 8080, cp.Port) - assert.Equal(t, int64(12), cp.maxLifetime) -} diff --git a/server/web/statistics_test.go b/server/web/statistics_test.go deleted file mode 100644 index 7c83e15a..00000000 --- a/server/web/statistics_test.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 web - -import ( - "encoding/json" - "testing" - "time" -) - -func TestStatics(t *testing.T) { - StatisticsMap.AddStatistics("POST", "/api/user", "&admin.user", time.Duration(2000)) - StatisticsMap.AddStatistics("POST", "/api/user", "&admin.user", time.Duration(120000)) - StatisticsMap.AddStatistics("GET", "/api/user", "&admin.user", time.Duration(13000)) - StatisticsMap.AddStatistics("POST", "/api/admin", "&admin.user", time.Duration(14000)) - StatisticsMap.AddStatistics("POST", "/api/user/astaxie", "&admin.user", time.Duration(12000)) - StatisticsMap.AddStatistics("POST", "/api/user/xiemengjun", "&admin.user", time.Duration(13000)) - StatisticsMap.AddStatistics("DELETE", "/api/user", "&admin.user", time.Duration(1400)) - t.Log(StatisticsMap.GetMap()) - - data := StatisticsMap.GetMapData() - b, err := json.Marshal(data) - if err != nil { - t.Errorf(err.Error()) - } - - t.Log(string(b)) -} diff --git a/server/web/session/README.md b/session/README.md similarity index 98% rename from server/web/session/README.md rename to session/README.md index a5c3bd6d..6d0a297e 100644 --- a/server/web/session/README.md +++ b/session/README.md @@ -101,7 +101,7 @@ Maybe you will find the **memory** provider is a good example. type Provider interface { SessionInit(gclifetime int64, config string) error SessionRead(sid string) (SessionStore, error) - SessionExist(sid string) (bool, error) + SessionExist(sid string) bool SessionRegenerate(oldsid, sid string) (SessionStore, error) SessionDestroy(sid string) error SessionAll() int //get all active session diff --git a/server/web/session/couchbase/sess_couchbase.go b/session/couchbase/sess_couchbase.go similarity index 69% rename from server/web/session/couchbase/sess_couchbase.go rename to session/couchbase/sess_couchbase.go index 7f15956a..707d042c 100644 --- a/server/web/session/couchbase/sess_couchbase.go +++ b/session/couchbase/sess_couchbase.go @@ -33,15 +33,13 @@ package couchbase import ( - "context" - "encoding/json" "net/http" "strings" "sync" couchbase "github.com/couchbase/go-couchbase" - "github.com/astaxie/beego/server/web/session" + "github.com/astaxie/beego/session" ) var couchbpder = &Provider{} @@ -58,14 +56,14 @@ type SessionStore struct { // Provider couchabse provided type Provider struct { maxlifetime int64 - SavePath string `json:"save_path"` - Pool string `json:"pool"` - Bucket string `json:"bucket"` + savePath string + pool string + bucket string b *couchbase.Bucket } // Set value to couchabse session -func (cs *SessionStore) Set(ctx context.Context, key, value interface{}) error { +func (cs *SessionStore) Set(key, value interface{}) error { cs.lock.Lock() defer cs.lock.Unlock() cs.values[key] = value @@ -73,7 +71,7 @@ func (cs *SessionStore) Set(ctx context.Context, key, value interface{}) error { } // Get value from couchabse session -func (cs *SessionStore) Get(ctx context.Context, key interface{}) interface{} { +func (cs *SessionStore) Get(key interface{}) interface{} { cs.lock.RLock() defer cs.lock.RUnlock() if v, ok := cs.values[key]; ok { @@ -83,7 +81,7 @@ func (cs *SessionStore) Get(ctx context.Context, key interface{}) interface{} { } // Delete value in couchbase session by given key -func (cs *SessionStore) Delete(ctx context.Context, key interface{}) error { +func (cs *SessionStore) Delete(key interface{}) error { cs.lock.Lock() defer cs.lock.Unlock() delete(cs.values, key) @@ -91,7 +89,7 @@ func (cs *SessionStore) Delete(ctx context.Context, key interface{}) error { } // Flush Clean all values in couchbase session -func (cs *SessionStore) Flush(context.Context) error { +func (cs *SessionStore) Flush() error { cs.lock.Lock() defer cs.lock.Unlock() cs.values = make(map[interface{}]interface{}) @@ -99,12 +97,12 @@ func (cs *SessionStore) Flush(context.Context) error { } // SessionID Get couchbase session store id -func (cs *SessionStore) SessionID(context.Context) string { +func (cs *SessionStore) SessionID() string { return cs.sid } // SessionRelease Write couchbase session with Gob string -func (cs *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { +func (cs *SessionStore) SessionRelease(w http.ResponseWriter) { defer cs.b.Close() bo, err := session.EncodeGob(cs.values) @@ -116,17 +114,17 @@ func (cs *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWrite } func (cp *Provider) getBucket() *couchbase.Bucket { - c, err := couchbase.Connect(cp.SavePath) + c, err := couchbase.Connect(cp.savePath) if err != nil { return nil } - pool, err := c.GetPool(cp.Pool) + pool, err := c.GetPool(cp.pool) if err != nil { return nil } - bucket, err := pool.GetBucket(cp.Bucket) + bucket, err := pool.GetBucket(cp.bucket) if err != nil { return nil } @@ -136,38 +134,25 @@ func (cp *Provider) getBucket() *couchbase.Bucket { // SessionInit init couchbase session // savepath like couchbase server REST/JSON URL -// For v1.x e.g. http://host:port/, Pool, Bucket -// For v2.x, you should pass json string. -// e.g. { "save_path": "http://host:port/", "pool": "mypool", "bucket": "mybucket"} -func (cp *Provider) SessionInit(ctx context.Context, maxlifetime int64, cfg string) error { +// e.g. http://host:port/, Pool, Bucket +func (cp *Provider) SessionInit(maxlifetime int64, savePath string) error { cp.maxlifetime = maxlifetime - cfg = strings.TrimSpace(cfg) - // we think this is v2.0, using json to init the session - if strings.HasPrefix(cfg, "{") { - return json.Unmarshal([]byte(cfg), cp) - } else { - return cp.initOldStyle(cfg) - } -} - -// initOldStyle keep compatible with v1.x -func (cp *Provider) initOldStyle(savePath string) error { configs := strings.Split(savePath, ",") if len(configs) > 0 { - cp.SavePath = configs[0] + cp.savePath = configs[0] } if len(configs) > 1 { - cp.Pool = configs[1] + cp.pool = configs[1] } if len(configs) > 2 { - cp.Bucket = configs[2] + cp.bucket = configs[2] } return nil } // SessionRead read couchbase session by sid -func (cp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { +func (cp *Provider) SessionRead(sid string) (session.Store, error) { cp.b = cp.getBucket() var ( @@ -194,20 +179,20 @@ func (cp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, // SessionExist Check couchbase session exist. // it checkes sid exist or not. -func (cp *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { +func (cp *Provider) SessionExist(sid string) bool { cp.b = cp.getBucket() defer cp.b.Close() var doc []byte if err := cp.b.Get(sid, &doc); err != nil || doc == nil { - return false, err + return false } - return true, nil + return true } // SessionRegenerate remove oldsid and use sid to generate new session -func (cp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { +func (cp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { cp.b = cp.getBucket() var doc []byte @@ -239,8 +224,8 @@ func (cp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) ( return cs, nil } -// SessionDestroy Remove Bucket in this couchbase -func (cp *Provider) SessionDestroy(ctx context.Context, sid string) error { +// SessionDestroy Remove bucket in this couchbase +func (cp *Provider) SessionDestroy(sid string) error { cp.b = cp.getBucket() defer cp.b.Close() @@ -249,11 +234,11 @@ func (cp *Provider) SessionDestroy(ctx context.Context, sid string) error { } // SessionGC Recycle -func (cp *Provider) SessionGC(context.Context) { +func (cp *Provider) SessionGC() { } // SessionAll return all active session -func (cp *Provider) SessionAll(context.Context) int { +func (cp *Provider) SessionAll() int { return 0 } diff --git a/server/web/session/ledis/ledis_session.go b/session/ledis/ledis_session.go similarity index 59% rename from server/web/session/ledis/ledis_session.go rename to session/ledis/ledis_session.go index 5b930fcd..ee81df67 100644 --- a/server/web/session/ledis/ledis_session.go +++ b/session/ledis/ledis_session.go @@ -2,8 +2,6 @@ package ledis import ( - "context" - "encoding/json" "net/http" "strconv" "strings" @@ -12,7 +10,7 @@ import ( "github.com/ledisdb/ledisdb/config" "github.com/ledisdb/ledisdb/ledis" - "github.com/astaxie/beego/server/web/session" + "github.com/astaxie/beego/session" ) var ( @@ -29,7 +27,7 @@ type SessionStore struct { } // Set value in ledis session -func (ls *SessionStore) Set(ctx context.Context, key, value interface{}) error { +func (ls *SessionStore) Set(key, value interface{}) error { ls.lock.Lock() defer ls.lock.Unlock() ls.values[key] = value @@ -37,7 +35,7 @@ func (ls *SessionStore) Set(ctx context.Context, key, value interface{}) error { } // Get value in ledis session -func (ls *SessionStore) Get(ctx context.Context, key interface{}) interface{} { +func (ls *SessionStore) Get(key interface{}) interface{} { ls.lock.RLock() defer ls.lock.RUnlock() if v, ok := ls.values[key]; ok { @@ -47,7 +45,7 @@ func (ls *SessionStore) Get(ctx context.Context, key interface{}) interface{} { } // Delete value in ledis session -func (ls *SessionStore) Delete(ctx context.Context, key interface{}) error { +func (ls *SessionStore) Delete(key interface{}) error { ls.lock.Lock() defer ls.lock.Unlock() delete(ls.values, key) @@ -55,7 +53,7 @@ func (ls *SessionStore) Delete(ctx context.Context, key interface{}) error { } // Flush clear all values in ledis session -func (ls *SessionStore) Flush(context.Context) error { +func (ls *SessionStore) Flush() error { ls.lock.Lock() defer ls.lock.Unlock() ls.values = make(map[interface{}]interface{}) @@ -63,12 +61,12 @@ func (ls *SessionStore) Flush(context.Context) error { } // SessionID get ledis session id -func (ls *SessionStore) SessionID(context.Context) string { +func (ls *SessionStore) SessionID() string { return ls.sid } // SessionRelease save session values to ledis -func (ls *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { +func (ls *SessionStore) SessionRelease(w http.ResponseWriter) { b, err := session.EncodeGob(ls.values) if err != nil { return @@ -80,56 +78,40 @@ func (ls *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWrite // Provider ledis session provider type Provider struct { maxlifetime int64 - SavePath string `json:"save_path"` - Db int `json:"db"` + savePath string + db int } // SessionInit init ledis session // savepath like ledis server saveDataPath,pool size -// v1.x e.g. 127.0.0.1:6379,100 -// v2.x you should pass a json string -// e.g. { "save_path": "my save path", "db": 100} -func (lp *Provider) SessionInit(ctx context.Context, maxlifetime int64, cfgStr string) error { +// e.g. 127.0.0.1:6379,100,astaxie +func (lp *Provider) SessionInit(maxlifetime int64, savePath string) error { var err error lp.maxlifetime = maxlifetime - cfgStr = strings.TrimSpace(cfgStr) - // we think cfgStr is v2.0, using json to init the session - if strings.HasPrefix(cfgStr, "{") { - err = json.Unmarshal([]byte(cfgStr), lp) - } else { - err = lp.initOldStyle(cfgStr) + configs := strings.Split(savePath, ",") + if len(configs) == 1 { + lp.savePath = configs[0] + } else if len(configs) == 2 { + lp.savePath = configs[0] + lp.db, err = strconv.Atoi(configs[1]) + if err != nil { + return err + } } - - if err != nil { - return err - } - cfg := new(config.Config) - cfg.DataDir = lp.SavePath + cfg.DataDir = lp.savePath var ledisInstance *ledis.Ledis ledisInstance, err = ledis.Open(cfg) if err != nil { return err } - c, err = ledisInstance.Select(lp.Db) - return err -} - -func (lp *Provider) initOldStyle(cfgStr string) error { - var err error - configs := strings.Split(cfgStr, ",") - if len(configs) == 1 { - lp.SavePath = configs[0] - } else if len(configs) == 2 { - lp.SavePath = configs[0] - lp.Db, err = strconv.Atoi(configs[1]) - } + c, err = ledisInstance.Select(lp.db) return err } // SessionRead read ledis session by sid -func (lp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { +func (lp *Provider) SessionRead(sid string) (session.Store, error) { var ( kv map[interface{}]interface{} err error @@ -150,13 +132,13 @@ func (lp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, } // SessionExist check ledis session exist by sid -func (lp *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { +func (lp *Provider) SessionExist(sid string) bool { count, _ := c.Exists([]byte(sid)) - return count != 0, nil + return count != 0 } // SessionRegenerate generate new sid for ledis session -func (lp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { +func (lp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { count, _ := c.Exists([]byte(sid)) if count == 0 { // oldsid doesn't exists, set the new sid directly @@ -169,21 +151,21 @@ func (lp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) ( c.Set([]byte(sid), data) c.Expire([]byte(sid), lp.maxlifetime) } - return lp.SessionRead(context.Background(), sid) + return lp.SessionRead(sid) } // SessionDestroy delete ledis session by id -func (lp *Provider) SessionDestroy(ctx context.Context, sid string) error { +func (lp *Provider) SessionDestroy(sid string) error { c.Del([]byte(sid)) return nil } // SessionGC Impelment method, no used. -func (lp *Provider) SessionGC(context.Context) { +func (lp *Provider) SessionGC() { } // SessionAll return all active session -func (lp *Provider) SessionAll(context.Context) int { +func (lp *Provider) SessionAll() int { return 0 } func init() { diff --git a/server/web/session/memcache/sess_memcache.go b/session/memcache/sess_memcache.go similarity index 81% rename from server/web/session/memcache/sess_memcache.go rename to session/memcache/sess_memcache.go index 168116ef..85a2d815 100644 --- a/server/web/session/memcache/sess_memcache.go +++ b/session/memcache/sess_memcache.go @@ -33,12 +33,11 @@ package memcache import ( - "context" "net/http" "strings" "sync" - "github.com/astaxie/beego/server/web/session" + "github.com/astaxie/beego/session" "github.com/bradfitz/gomemcache/memcache" ) @@ -55,7 +54,7 @@ type SessionStore struct { } // Set value in memcache session -func (rs *SessionStore) Set(ctx context.Context, key, value interface{}) error { +func (rs *SessionStore) Set(key, value interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() rs.values[key] = value @@ -63,7 +62,7 @@ func (rs *SessionStore) Set(ctx context.Context, key, value interface{}) error { } // Get value in memcache session -func (rs *SessionStore) Get(ctx context.Context, key interface{}) interface{} { +func (rs *SessionStore) Get(key interface{}) interface{} { rs.lock.RLock() defer rs.lock.RUnlock() if v, ok := rs.values[key]; ok { @@ -73,7 +72,7 @@ func (rs *SessionStore) Get(ctx context.Context, key interface{}) interface{} { } // Delete value in memcache session -func (rs *SessionStore) Delete(ctx context.Context, key interface{}) error { +func (rs *SessionStore) Delete(key interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() delete(rs.values, key) @@ -81,7 +80,7 @@ func (rs *SessionStore) Delete(ctx context.Context, key interface{}) error { } // Flush clear all values in memcache session -func (rs *SessionStore) Flush(context.Context) error { +func (rs *SessionStore) Flush() error { rs.lock.Lock() defer rs.lock.Unlock() rs.values = make(map[interface{}]interface{}) @@ -89,12 +88,12 @@ func (rs *SessionStore) Flush(context.Context) error { } // SessionID get memcache session id -func (rs *SessionStore) SessionID(context.Context) string { +func (rs *SessionStore) SessionID() string { return rs.sid } // SessionRelease save session values to memcache -func (rs *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { +func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { b, err := session.EncodeGob(rs.values) if err != nil { return @@ -114,7 +113,7 @@ type MemProvider struct { // SessionInit init memcache session // savepath like // e.g. 127.0.0.1:9090 -func (rp *MemProvider) SessionInit(ctx context.Context, maxlifetime int64, savePath string) error { +func (rp *MemProvider) SessionInit(maxlifetime int64, savePath string) error { rp.maxlifetime = maxlifetime rp.conninfo = strings.Split(savePath, ";") client = memcache.New(rp.conninfo...) @@ -122,7 +121,7 @@ func (rp *MemProvider) SessionInit(ctx context.Context, maxlifetime int64, saveP } // SessionRead read memcache session by sid -func (rp *MemProvider) SessionRead(ctx context.Context, sid string) (session.Store, error) { +func (rp *MemProvider) SessionRead(sid string) (session.Store, error) { if client == nil { if err := rp.connectInit(); err != nil { return nil, err @@ -150,20 +149,20 @@ func (rp *MemProvider) SessionRead(ctx context.Context, sid string) (session.Sto } // SessionExist check memcache session exist by sid -func (rp *MemProvider) SessionExist(ctx context.Context, sid string) (bool, error) { +func (rp *MemProvider) SessionExist(sid string) bool { if client == nil { if err := rp.connectInit(); err != nil { - return false, err + return false } } if item, err := client.Get(sid); err != nil || len(item.Value) == 0 { - return false, err + return false } - return true, nil + return true } // SessionRegenerate generate new sid for memcache session -func (rp *MemProvider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { +func (rp *MemProvider) SessionRegenerate(oldsid, sid string) (session.Store, error) { if client == nil { if err := rp.connectInit(); err != nil { return nil, err @@ -202,7 +201,7 @@ func (rp *MemProvider) SessionRegenerate(ctx context.Context, oldsid, sid string } // SessionDestroy delete memcache session by id -func (rp *MemProvider) SessionDestroy(ctx context.Context, sid string) error { +func (rp *MemProvider) SessionDestroy(sid string) error { if client == nil { if err := rp.connectInit(); err != nil { return err @@ -218,11 +217,11 @@ func (rp *MemProvider) connectInit() error { } // SessionGC Impelment method, no used. -func (rp *MemProvider) SessionGC(context.Context) { +func (rp *MemProvider) SessionGC() { } // SessionAll return all activeSession -func (rp *MemProvider) SessionAll(context.Context) int { +func (rp *MemProvider) SessionAll() int { return 0 } diff --git a/server/web/session/mysql/sess_mysql.go b/session/mysql/sess_mysql.go similarity index 82% rename from server/web/session/mysql/sess_mysql.go rename to session/mysql/sess_mysql.go index 89da361d..301353ab 100644 --- a/server/web/session/mysql/sess_mysql.go +++ b/session/mysql/sess_mysql.go @@ -41,13 +41,12 @@ package mysql import ( - "context" "database/sql" "net/http" "sync" "time" - "github.com/astaxie/beego/server/web/session" + "github.com/astaxie/beego/session" // import mysql driver _ "github.com/go-sql-driver/mysql" ) @@ -68,7 +67,7 @@ type SessionStore struct { // Set value in mysql session. // it is temp value in map. -func (st *SessionStore) Set(ctx context.Context, key, value interface{}) error { +func (st *SessionStore) Set(key, value interface{}) error { st.lock.Lock() defer st.lock.Unlock() st.values[key] = value @@ -76,7 +75,7 @@ func (st *SessionStore) Set(ctx context.Context, key, value interface{}) error { } // Get value from mysql session -func (st *SessionStore) Get(ctx context.Context, key interface{}) interface{} { +func (st *SessionStore) Get(key interface{}) interface{} { st.lock.RLock() defer st.lock.RUnlock() if v, ok := st.values[key]; ok { @@ -86,7 +85,7 @@ func (st *SessionStore) Get(ctx context.Context, key interface{}) interface{} { } // Delete value in mysql session -func (st *SessionStore) Delete(ctx context.Context, key interface{}) error { +func (st *SessionStore) Delete(key interface{}) error { st.lock.Lock() defer st.lock.Unlock() delete(st.values, key) @@ -94,7 +93,7 @@ func (st *SessionStore) Delete(ctx context.Context, key interface{}) error { } // Flush clear all values in mysql session -func (st *SessionStore) Flush(context.Context) error { +func (st *SessionStore) Flush() error { st.lock.Lock() defer st.lock.Unlock() st.values = make(map[interface{}]interface{}) @@ -102,13 +101,13 @@ func (st *SessionStore) Flush(context.Context) error { } // SessionID get session id of this mysql session store -func (st *SessionStore) SessionID(context.Context) string { +func (st *SessionStore) SessionID() string { return st.sid } // SessionRelease save mysql session values to database. // must call this method to save values to database. -func (st *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { +func (st *SessionStore) SessionRelease(w http.ResponseWriter) { defer st.c.Close() b, err := session.EncodeGob(st.values) if err != nil { @@ -135,14 +134,14 @@ func (mp *Provider) connectInit() *sql.DB { // SessionInit init mysql session. // savepath is the connection string of mysql. -func (mp *Provider) SessionInit(ctx context.Context, maxlifetime int64, savePath string) error { +func (mp *Provider) SessionInit(maxlifetime int64, savePath string) error { mp.maxlifetime = maxlifetime mp.savePath = savePath return nil } // SessionRead get mysql session by sid -func (mp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { +func (mp *Provider) SessionRead(sid string) (session.Store, error) { c := mp.connectInit() row := c.QueryRow("select session_data from "+TableName+" where session_key=?", sid) var sessiondata []byte @@ -165,23 +164,17 @@ func (mp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, } // SessionExist check mysql session exist -func (mp *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { +func (mp *Provider) SessionExist(sid string) bool { c := mp.connectInit() defer c.Close() row := c.QueryRow("select session_data from "+TableName+" where session_key=?", sid) var sessiondata []byte err := row.Scan(&sessiondata) - if err != nil { - if err == sql.ErrNoRows { - return false, nil - } - return false, err - } - return true, nil + return err != sql.ErrNoRows } // SessionRegenerate generate new sid for mysql session -func (mp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { +func (mp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { c := mp.connectInit() row := c.QueryRow("select session_data from "+TableName+" where session_key=?", oldsid) var sessiondata []byte @@ -204,7 +197,7 @@ func (mp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) ( } // SessionDestroy delete mysql session by sid -func (mp *Provider) SessionDestroy(ctx context.Context, sid string) error { +func (mp *Provider) SessionDestroy(sid string) error { c := mp.connectInit() c.Exec("DELETE FROM "+TableName+" where session_key=?", sid) c.Close() @@ -212,14 +205,14 @@ func (mp *Provider) SessionDestroy(ctx context.Context, sid string) error { } // SessionGC delete expired values in mysql session -func (mp *Provider) SessionGC(context.Context) { +func (mp *Provider) SessionGC() { c := mp.connectInit() c.Exec("DELETE from "+TableName+" where session_expiry < ?", time.Now().Unix()-mp.maxlifetime) c.Close() } // SessionAll count values in mysql session -func (mp *Provider) SessionAll(context.Context) int { +func (mp *Provider) SessionAll() int { c := mp.connectInit() defer c.Close() var total int diff --git a/server/web/session/postgres/sess_postgresql.go b/session/postgres/sess_postgresql.go similarity index 83% rename from server/web/session/postgres/sess_postgresql.go rename to session/postgres/sess_postgresql.go index a83ac083..0b8b9645 100644 --- a/server/web/session/postgres/sess_postgresql.go +++ b/session/postgres/sess_postgresql.go @@ -51,13 +51,12 @@ package postgres import ( - "context" "database/sql" "net/http" "sync" "time" - "github.com/astaxie/beego/server/web/session" + "github.com/astaxie/beego/session" // import postgresql Driver _ "github.com/lib/pq" ) @@ -74,7 +73,7 @@ type SessionStore struct { // Set value in postgresql session. // it is temp value in map. -func (st *SessionStore) Set(ctx context.Context, key, value interface{}) error { +func (st *SessionStore) Set(key, value interface{}) error { st.lock.Lock() defer st.lock.Unlock() st.values[key] = value @@ -82,7 +81,7 @@ func (st *SessionStore) Set(ctx context.Context, key, value interface{}) error { } // Get value from postgresql session -func (st *SessionStore) Get(ctx context.Context, key interface{}) interface{} { +func (st *SessionStore) Get(key interface{}) interface{} { st.lock.RLock() defer st.lock.RUnlock() if v, ok := st.values[key]; ok { @@ -92,7 +91,7 @@ func (st *SessionStore) Get(ctx context.Context, key interface{}) interface{} { } // Delete value in postgresql session -func (st *SessionStore) Delete(ctx context.Context, key interface{}) error { +func (st *SessionStore) Delete(key interface{}) error { st.lock.Lock() defer st.lock.Unlock() delete(st.values, key) @@ -100,7 +99,7 @@ func (st *SessionStore) Delete(ctx context.Context, key interface{}) error { } // Flush clear all values in postgresql session -func (st *SessionStore) Flush(context.Context) error { +func (st *SessionStore) Flush() error { st.lock.Lock() defer st.lock.Unlock() st.values = make(map[interface{}]interface{}) @@ -108,13 +107,13 @@ func (st *SessionStore) Flush(context.Context) error { } // SessionID get session id of this postgresql session store -func (st *SessionStore) SessionID(context.Context) string { +func (st *SessionStore) SessionID() string { return st.sid } // SessionRelease save postgresql session values to database. // must call this method to save values to database. -func (st *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { +func (st *SessionStore) SessionRelease(w http.ResponseWriter) { defer st.c.Close() b, err := session.EncodeGob(st.values) if err != nil { @@ -142,14 +141,14 @@ func (mp *Provider) connectInit() *sql.DB { // SessionInit init postgresql session. // savepath is the connection string of postgresql. -func (mp *Provider) SessionInit(ctx context.Context, maxlifetime int64, savePath string) error { +func (mp *Provider) SessionInit(maxlifetime int64, savePath string) error { mp.maxlifetime = maxlifetime mp.savePath = savePath return nil } // SessionRead get postgresql session by sid -func (mp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { +func (mp *Provider) SessionRead(sid string) (session.Store, error) { c := mp.connectInit() row := c.QueryRow("select session_data from session where session_key=$1", sid) var sessiondata []byte @@ -179,23 +178,17 @@ func (mp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, } // SessionExist check postgresql session exist -func (mp *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { +func (mp *Provider) SessionExist(sid string) bool { c := mp.connectInit() defer c.Close() row := c.QueryRow("select session_data from session where session_key=$1", sid) var sessiondata []byte err := row.Scan(&sessiondata) - if err != nil { - if err == sql.ErrNoRows { - return false, nil - } - return false, err - } - return true, nil + return err != sql.ErrNoRows } // SessionRegenerate generate new sid for postgresql session -func (mp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { +func (mp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { c := mp.connectInit() row := c.QueryRow("select session_data from session where session_key=$1", oldsid) var sessiondata []byte @@ -219,7 +212,7 @@ func (mp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) ( } // SessionDestroy delete postgresql session by sid -func (mp *Provider) SessionDestroy(ctx context.Context, sid string) error { +func (mp *Provider) SessionDestroy(sid string) error { c := mp.connectInit() c.Exec("DELETE FROM session where session_key=$1", sid) c.Close() @@ -227,14 +220,14 @@ func (mp *Provider) SessionDestroy(ctx context.Context, sid string) error { } // SessionGC delete expired values in postgresql session -func (mp *Provider) SessionGC(context.Context) { +func (mp *Provider) SessionGC() { c := mp.connectInit() c.Exec("DELETE from session where EXTRACT(EPOCH FROM (current_timestamp - session_expiry)) > $1", mp.maxlifetime) c.Close() } // SessionAll count values in postgresql session -func (mp *Provider) SessionAll(context.Context) int { +func (mp *Provider) SessionAll() int { c := mp.connectInit() defer c.Close() var total int diff --git a/server/web/session/redis/sess_redis.go b/session/redis/sess_redis.go similarity index 50% rename from server/web/session/redis/sess_redis.go rename to session/redis/sess_redis.go index c6e3bcbb..5c382d61 100644 --- a/server/web/session/redis/sess_redis.go +++ b/session/redis/sess_redis.go @@ -33,17 +33,15 @@ package redis import ( - "context" - "encoding/json" "net/http" "strconv" "strings" "sync" "time" - "github.com/go-redis/redis/v7" + "github.com/astaxie/beego/session" - "github.com/astaxie/beego/server/web/session" + "github.com/gomodule/redigo/redis" ) var redispder = &Provider{} @@ -53,7 +51,7 @@ var MaxPoolSize = 100 // SessionStore redis session store type SessionStore struct { - p *redis.Client + p *redis.Pool sid string lock sync.RWMutex values map[interface{}]interface{} @@ -61,7 +59,7 @@ type SessionStore struct { } // Set value in redis session -func (rs *SessionStore) Set(ctx context.Context, key, value interface{}) error { +func (rs *SessionStore) Set(key, value interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() rs.values[key] = value @@ -69,7 +67,7 @@ func (rs *SessionStore) Set(ctx context.Context, key, value interface{}) error { } // Get value in redis session -func (rs *SessionStore) Get(ctx context.Context, key interface{}) interface{} { +func (rs *SessionStore) Get(key interface{}) interface{} { rs.lock.RLock() defer rs.lock.RUnlock() if v, ok := rs.values[key]; ok { @@ -79,7 +77,7 @@ func (rs *SessionStore) Get(ctx context.Context, key interface{}) interface{} { } // Delete value in redis session -func (rs *SessionStore) Delete(ctx context.Context, key interface{}) error { +func (rs *SessionStore) Delete(key interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() delete(rs.values, key) @@ -87,7 +85,7 @@ func (rs *SessionStore) Delete(ctx context.Context, key interface{}) error { } // Flush clear all values in redis session -func (rs *SessionStore) Flush(context.Context) error { +func (rs *SessionStore) Flush() error { rs.lock.Lock() defer rs.lock.Unlock() rs.values = make(map[interface{}]interface{}) @@ -95,132 +93,109 @@ func (rs *SessionStore) Flush(context.Context) error { } // SessionID get redis session id -func (rs *SessionStore) SessionID(context.Context) string { +func (rs *SessionStore) SessionID() string { return rs.sid } // SessionRelease save session values to redis -func (rs *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { +func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { b, err := session.EncodeGob(rs.values) if err != nil { return } - c := rs.p - c.Set(rs.sid, string(b), time.Duration(rs.maxlifetime)*time.Second) + c := rs.p.Get() + defer c.Close() + c.Do("SETEX", rs.sid, rs.maxlifetime, string(b)) } // Provider redis session provider type Provider struct { maxlifetime int64 - SavePath string `json:"save_path"` - Poolsize int `json:"poolsize"` - Password string `json:"password"` - DbNum int `json:"db_num"` - - idleTimeout time.Duration - IdleTimeoutStr string `json:"idle_timeout"` - - idleCheckFrequency time.Duration - IdleCheckFrequencyStr string `json:"idle_check_frequency"` - MaxRetries int `json:"max_retries"` - poollist *redis.Client + savePath string + poolsize int + password string + dbNum int + poollist *redis.Pool } // SessionInit init redis session // savepath like redis server addr,pool size,password,dbnum,IdleTimeout second -// v1.x e.g. 127.0.0.1:6379,100,astaxie,0,30 -// v2.0 you should pass json string -func (rp *Provider) SessionInit(ctx context.Context, maxlifetime int64, cfgStr string) error { +// e.g. 127.0.0.1:6379,100,astaxie,0,30 +func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error { rp.maxlifetime = maxlifetime - - cfgStr = strings.TrimSpace(cfgStr) - // we think cfgStr is v2.0, using json to init the session - if strings.HasPrefix(cfgStr, "{") { - err := json.Unmarshal([]byte(cfgStr), rp) - if err != nil { - return err - } - rp.idleTimeout, err = time.ParseDuration(rp.IdleTimeoutStr) - if err != nil { - return err - } - - rp.idleCheckFrequency, err = time.ParseDuration(rp.IdleCheckFrequencyStr) - if err != nil { - return err - } - - } else { - rp.initOldStyle(cfgStr) - } - - rp.poollist = redis.NewClient(&redis.Options{ - Addr: rp.SavePath, - Password: rp.Password, - PoolSize: rp.Poolsize, - DB: rp.DbNum, - IdleTimeout: rp.idleTimeout, - IdleCheckFrequency: rp.idleCheckFrequency, - MaxRetries: rp.MaxRetries, - }) - - return rp.poollist.Ping().Err() -} - -func (rp *Provider) initOldStyle(savePath string) { configs := strings.Split(savePath, ",") if len(configs) > 0 { - rp.SavePath = configs[0] + rp.savePath = configs[0] } if len(configs) > 1 { poolsize, err := strconv.Atoi(configs[1]) if err != nil || poolsize < 0 { - rp.Poolsize = MaxPoolSize + rp.poolsize = MaxPoolSize } else { - rp.Poolsize = poolsize + rp.poolsize = poolsize } } else { - rp.Poolsize = MaxPoolSize + rp.poolsize = MaxPoolSize } if len(configs) > 2 { - rp.Password = configs[2] + rp.password = configs[2] } if len(configs) > 3 { dbnum, err := strconv.Atoi(configs[3]) if err != nil || dbnum < 0 { - rp.DbNum = 0 + rp.dbNum = 0 } else { - rp.DbNum = dbnum + rp.dbNum = dbnum } } else { - rp.DbNum = 0 + rp.dbNum = 0 } + var idleTimeout time.Duration = 0 if len(configs) > 4 { timeout, err := strconv.Atoi(configs[4]) if err == nil && timeout > 0 { - rp.idleTimeout = time.Duration(timeout) * time.Second + idleTimeout = time.Duration(timeout) * time.Second } } - if len(configs) > 5 { - checkFrequency, err := strconv.Atoi(configs[5]) - if err == nil && checkFrequency > 0 { - rp.idleCheckFrequency = time.Duration(checkFrequency) * time.Second - } - } - if len(configs) > 6 { - retries, err := strconv.Atoi(configs[6]) - if err == nil && retries > 0 { - rp.MaxRetries = retries - } + rp.poollist = &redis.Pool{ + Dial: func() (redis.Conn, error) { + c, err := redis.Dial("tcp", rp.savePath) + if err != nil { + return nil, err + } + if rp.password != "" { + if _, err = c.Do("AUTH", rp.password); err != nil { + c.Close() + return nil, err + } + } + // some redis proxy such as twemproxy is not support select command + if rp.dbNum > 0 { + _, err = c.Do("SELECT", rp.dbNum) + if err != nil { + c.Close() + return nil, err + } + } + return c, err + }, + MaxIdle: rp.poolsize, } + + rp.poollist.IdleTimeout = idleTimeout + + return rp.poollist.Get().Err() } // SessionRead read redis session by sid -func (rp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { +func (rp *Provider) SessionRead(sid string) (session.Store, error) { + c := rp.poollist.Get() + defer c.Close() + var kv map[interface{}]interface{} - kvs, err := rp.poollist.Get(sid).Result() - if err != nil && err != redis.Nil { + kvs, err := redis.String(c.Do("GET", sid)) + if err != nil && err != redis.ErrNil { return nil, err } if len(kvs) == 0 { @@ -236,44 +211,48 @@ func (rp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, } // SessionExist check redis session exist by sid -func (rp *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { - c := rp.poollist +func (rp *Provider) SessionExist(sid string) bool { + c := rp.poollist.Get() + defer c.Close() - if existed, err := c.Exists(sid).Result(); err != nil || existed == 0 { - return false, err + if existed, err := redis.Int(c.Do("EXISTS", sid)); err != nil || existed == 0 { + return false } - return true, nil + return true } // SessionRegenerate generate new sid for redis session -func (rp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { - c := rp.poollist - if existed, _ := c.Exists(oldsid).Result(); existed == 0 { +func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { + c := rp.poollist.Get() + defer c.Close() + + if existed, _ := redis.Int(c.Do("EXISTS", oldsid)); existed == 0 { // oldsid doesn't exists, set the new sid directly // ignore error here, since if it return error // the existed value will be 0 - c.Do(c.Context(), "SET", sid, "", "EX", rp.maxlifetime) + c.Do("SET", sid, "", "EX", rp.maxlifetime) } else { - c.Rename(oldsid, sid) - c.Expire(sid, time.Duration(rp.maxlifetime)*time.Second) + c.Do("RENAME", oldsid, sid) + c.Do("EXPIRE", sid, rp.maxlifetime) } - return rp.SessionRead(context.Background(), sid) + return rp.SessionRead(sid) } // SessionDestroy delete redis session by id -func (rp *Provider) SessionDestroy(ctx context.Context, sid string) error { - c := rp.poollist +func (rp *Provider) SessionDestroy(sid string) error { + c := rp.poollist.Get() + defer c.Close() - c.Del(sid) + c.Do("DEL", sid) return nil } // SessionGC Impelment method, no used. -func (rp *Provider) SessionGC(context.Context) { +func (rp *Provider) SessionGC() { } // SessionAll return all activeSession -func (rp *Provider) SessionAll(context.Context) int { +func (rp *Provider) SessionAll() int { return 0 } diff --git a/server/web/session/redis_cluster/redis_cluster.go b/session/redis_cluster/redis_cluster.go similarity index 55% rename from server/web/session/redis_cluster/redis_cluster.go rename to session/redis_cluster/redis_cluster.go index d2971e71..2fe300df 100644 --- a/server/web/session/redis_cluster/redis_cluster.go +++ b/session/redis_cluster/redis_cluster.go @@ -31,19 +31,14 @@ // // more docs: http://beego.me/docs/module/session.md package redis_cluster - import ( - "context" - "encoding/json" "net/http" "strconv" "strings" "sync" + "github.com/astaxie/beego/session" + rediss "github.com/go-redis/redis" "time" - - rediss "github.com/go-redis/redis/v7" - - "github.com/astaxie/beego/server/web/session" ) var redispder = &Provider{} @@ -61,7 +56,7 @@ type SessionStore struct { } // Set value in redis_cluster session -func (rs *SessionStore) Set(ctx context.Context, key, value interface{}) error { +func (rs *SessionStore) Set(key, value interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() rs.values[key] = value @@ -69,7 +64,7 @@ func (rs *SessionStore) Set(ctx context.Context, key, value interface{}) error { } // Get value in redis_cluster session -func (rs *SessionStore) Get(ctx context.Context, key interface{}) interface{} { +func (rs *SessionStore) Get(key interface{}) interface{} { rs.lock.RLock() defer rs.lock.RUnlock() if v, ok := rs.values[key]; ok { @@ -79,7 +74,7 @@ func (rs *SessionStore) Get(ctx context.Context, key interface{}) interface{} { } // Delete value in redis_cluster session -func (rs *SessionStore) Delete(ctx context.Context, key interface{}) error { +func (rs *SessionStore) Delete(key interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() delete(rs.values, key) @@ -87,7 +82,7 @@ func (rs *SessionStore) Delete(ctx context.Context, key interface{}) error { } // Flush clear all values in redis_cluster session -func (rs *SessionStore) Flush(context.Context) error { +func (rs *SessionStore) Flush() error { rs.lock.Lock() defer rs.lock.Unlock() rs.values = make(map[interface{}]interface{}) @@ -95,125 +90,73 @@ func (rs *SessionStore) Flush(context.Context) error { } // SessionID get redis_cluster session id -func (rs *SessionStore) SessionID(context.Context) string { +func (rs *SessionStore) SessionID() string { return rs.sid } // SessionRelease save session values to redis_cluster -func (rs *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { +func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { b, err := session.EncodeGob(rs.values) if err != nil { return } c := rs.p - c.Set(rs.sid, string(b), time.Duration(rs.maxlifetime)*time.Second) + c.Set(rs.sid, string(b), time.Duration(rs.maxlifetime) * time.Second) } // Provider redis_cluster session provider type Provider struct { maxlifetime int64 - SavePath string `json:"save_path"` - Poolsize int `json:"poolsize"` - Password string `json:"password"` - DbNum int `json:"db_num"` - - idleTimeout time.Duration - IdleTimeoutStr string `json:"idle_timeout"` - - idleCheckFrequency time.Duration - IdleCheckFrequencyStr string `json:"idle_check_frequency"` - MaxRetries int `json:"max_retries"` - poollist *rediss.ClusterClient + savePath string + poolsize int + password string + dbNum int + poollist *rediss.ClusterClient } // SessionInit init redis_cluster session -// cfgStr like redis server addr,pool size,password,dbnum +// savepath like redis server addr,pool size,password,dbnum // e.g. 127.0.0.1:6379;127.0.0.1:6380,100,test,0 -func (rp *Provider) SessionInit(ctx context.Context, maxlifetime int64, cfgStr string) error { +func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error { rp.maxlifetime = maxlifetime - cfgStr = strings.TrimSpace(cfgStr) - // we think cfgStr is v2.0, using json to init the session - if strings.HasPrefix(cfgStr, "{") { - err := json.Unmarshal([]byte(cfgStr), rp) - if err != nil { - return err - } - rp.idleTimeout, err = time.ParseDuration(rp.IdleTimeoutStr) - if err != nil { - return err - } - - rp.idleCheckFrequency, err = time.ParseDuration(rp.IdleCheckFrequencyStr) - if err != nil { - return err - } - - } else { - rp.initOldStyle(cfgStr) - } - - rp.poollist = rediss.NewClusterClient(&rediss.ClusterOptions{ - Addrs: strings.Split(rp.SavePath, ";"), - Password: rp.Password, - PoolSize: rp.Poolsize, - IdleTimeout: rp.idleTimeout, - IdleCheckFrequency: rp.idleCheckFrequency, - MaxRetries: rp.MaxRetries, - }) - return rp.poollist.Ping().Err() -} - -// for v1.x -func (rp *Provider) initOldStyle(savePath string) { configs := strings.Split(savePath, ",") if len(configs) > 0 { - rp.SavePath = configs[0] + rp.savePath = configs[0] } if len(configs) > 1 { poolsize, err := strconv.Atoi(configs[1]) if err != nil || poolsize < 0 { - rp.Poolsize = MaxPoolSize + rp.poolsize = MaxPoolSize } else { - rp.Poolsize = poolsize + rp.poolsize = poolsize } } else { - rp.Poolsize = MaxPoolSize + rp.poolsize = MaxPoolSize } if len(configs) > 2 { - rp.Password = configs[2] + rp.password = configs[2] } if len(configs) > 3 { dbnum, err := strconv.Atoi(configs[3]) if err != nil || dbnum < 0 { - rp.DbNum = 0 + rp.dbNum = 0 } else { - rp.DbNum = dbnum + rp.dbNum = dbnum } } else { - rp.DbNum = 0 - } - if len(configs) > 4 { - timeout, err := strconv.Atoi(configs[4]) - if err == nil && timeout > 0 { - rp.idleTimeout = time.Duration(timeout) * time.Second - } - } - if len(configs) > 5 { - checkFrequency, err := strconv.Atoi(configs[5]) - if err == nil && checkFrequency > 0 { - rp.idleCheckFrequency = time.Duration(checkFrequency) * time.Second - } - } - if len(configs) > 6 { - retries, err := strconv.Atoi(configs[6]) - if err == nil && retries > 0 { - rp.MaxRetries = retries - } + rp.dbNum = 0 } + + rp.poollist = rediss.NewClusterClient(&rediss.ClusterOptions{ + Addrs: strings.Split(rp.savePath, ";"), + Password: rp.password, + PoolSize: rp.poolsize, + }) + return rp.poollist.Ping().Err() } // SessionRead read redis_cluster session by sid -func (rp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { +func (rp *Provider) SessionRead(sid string) (session.Store, error) { var kv map[interface{}]interface{} kvs, err := rp.poollist.Get(sid).Result() if err != nil && err != rediss.Nil { @@ -232,43 +175,43 @@ func (rp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, } // SessionExist check redis_cluster session exist by sid -func (rp *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { +func (rp *Provider) SessionExist(sid string) bool { c := rp.poollist if existed, err := c.Exists(sid).Result(); err != nil || existed == 0 { - return false, err + return false } - return true, nil + return true } // SessionRegenerate generate new sid for redis_cluster session -func (rp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { +func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { c := rp.poollist - + if existed, err := c.Exists(oldsid).Result(); err != nil || existed == 0 { // oldsid doesn't exists, set the new sid directly // ignore error here, since if it return error // the existed value will be 0 - c.Set(sid, "", time.Duration(rp.maxlifetime)*time.Second) + c.Set(sid, "", time.Duration(rp.maxlifetime) * time.Second) } else { c.Rename(oldsid, sid) - c.Expire(sid, time.Duration(rp.maxlifetime)*time.Second) + c.Expire(sid, time.Duration(rp.maxlifetime) * time.Second) } - return rp.SessionRead(context.Background(), sid) + return rp.SessionRead(sid) } // SessionDestroy delete redis session by id -func (rp *Provider) SessionDestroy(ctx context.Context, sid string) error { +func (rp *Provider) SessionDestroy(sid string) error { c := rp.poollist c.Del(sid) return nil } // SessionGC Impelment method, no used. -func (rp *Provider) SessionGC(context.Context) { +func (rp *Provider) SessionGC() { } // SessionAll return all activeSession -func (rp *Provider) SessionAll(context.Context) int { +func (rp *Provider) SessionAll() int { return 0 } diff --git a/server/web/session/redis_sentinel/sess_redis_sentinel.go b/session/redis_sentinel/sess_redis_sentinel.go similarity index 57% rename from server/web/session/redis_sentinel/sess_redis_sentinel.go rename to session/redis_sentinel/sess_redis_sentinel.go index 89d73b86..6ecb2977 100644 --- a/server/web/session/redis_sentinel/sess_redis_sentinel.go +++ b/session/redis_sentinel/sess_redis_sentinel.go @@ -33,17 +33,13 @@ package redis_sentinel import ( - "context" - "encoding/json" + "github.com/astaxie/beego/session" + "github.com/go-redis/redis" "net/http" "strconv" "strings" "sync" "time" - - "github.com/go-redis/redis/v7" - - "github.com/astaxie/beego/server/web/session" ) var redispder = &Provider{} @@ -61,7 +57,7 @@ type SessionStore struct { } // Set value in redis_sentinel session -func (rs *SessionStore) Set(ctx context.Context, key, value interface{}) error { +func (rs *SessionStore) Set(key, value interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() rs.values[key] = value @@ -69,7 +65,7 @@ func (rs *SessionStore) Set(ctx context.Context, key, value interface{}) error { } // Get value in redis_sentinel session -func (rs *SessionStore) Get(ctx context.Context, key interface{}) interface{} { +func (rs *SessionStore) Get(key interface{}) interface{} { rs.lock.RLock() defer rs.lock.RUnlock() if v, ok := rs.values[key]; ok { @@ -79,7 +75,7 @@ func (rs *SessionStore) Get(ctx context.Context, key interface{}) interface{} { } // Delete value in redis_sentinel session -func (rs *SessionStore) Delete(ctx context.Context, key interface{}) error { +func (rs *SessionStore) Delete(key interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() delete(rs.values, key) @@ -87,7 +83,7 @@ func (rs *SessionStore) Delete(ctx context.Context, key interface{}) error { } // Flush clear all values in redis_sentinel session -func (rs *SessionStore) Flush(context.Context) error { +func (rs *SessionStore) Flush() error { rs.lock.Lock() defer rs.lock.Unlock() rs.values = make(map[interface{}]interface{}) @@ -95,12 +91,12 @@ func (rs *SessionStore) Flush(context.Context) error { } // SessionID get redis_sentinel session id -func (rs *SessionStore) SessionID(context.Context) string { +func (rs *SessionStore) SessionID() string { return rs.sid } // SessionRelease save session values to redis_sentinel -func (rs *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { +func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { b, err := session.EncodeGob(rs.values) if err != nil { return @@ -112,121 +108,69 @@ func (rs *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWrite // Provider redis_sentinel session provider type Provider struct { maxlifetime int64 - SavePath string `json:"save_path"` - Poolsize int `json:"poolsize"` - Password string `json:"password"` - DbNum int `json:"db_num"` - - idleTimeout time.Duration - IdleTimeoutStr string `json:"idle_timeout"` - - idleCheckFrequency time.Duration - IdleCheckFrequencyStr string `json:"idle_check_frequency"` - MaxRetries int `json:"max_retries"` - poollist *redis.Client - MasterName string `json:"master_name"` + savePath string + poolsize int + password string + dbNum int + poollist *redis.Client + masterName string } // SessionInit init redis_sentinel session -// cfgStr like redis sentinel addr,pool size,password,dbnum,masterName +// savepath like redis sentinel addr,pool size,password,dbnum,masterName // e.g. 127.0.0.1:26379;127.0.0.2:26379,100,1qaz2wsx,0,mymaster -func (rp *Provider) SessionInit(ctx context.Context, maxlifetime int64, cfgStr string) error { +func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error { rp.maxlifetime = maxlifetime - cfgStr = strings.TrimSpace(cfgStr) - // we think cfgStr is v2.0, using json to init the session - if strings.HasPrefix(cfgStr, "{") { - err := json.Unmarshal([]byte(cfgStr), rp) - if err != nil { - return err + configs := strings.Split(savePath, ",") + if len(configs) > 0 { + rp.savePath = configs[0] + } + if len(configs) > 1 { + poolsize, err := strconv.Atoi(configs[1]) + if err != nil || poolsize < 0 { + rp.poolsize = DefaultPoolSize + } else { + rp.poolsize = poolsize } - rp.idleTimeout, err = time.ParseDuration(rp.IdleTimeoutStr) - if err != nil { - return err - } - - rp.idleCheckFrequency, err = time.ParseDuration(rp.IdleCheckFrequencyStr) - if err != nil { - return err - } - } else { - rp.initOldStyle(cfgStr) + rp.poolsize = DefaultPoolSize + } + if len(configs) > 2 { + rp.password = configs[2] + } + if len(configs) > 3 { + dbnum, err := strconv.Atoi(configs[3]) + if err != nil || dbnum < 0 { + rp.dbNum = 0 + } else { + rp.dbNum = dbnum + } + } else { + rp.dbNum = 0 + } + if len(configs) > 4 { + if configs[4] != "" { + rp.masterName = configs[4] + } else { + rp.masterName = "mymaster" + } + } else { + rp.masterName = "mymaster" } rp.poollist = redis.NewFailoverClient(&redis.FailoverOptions{ - SentinelAddrs: strings.Split(rp.SavePath, ";"), - Password: rp.Password, - PoolSize: rp.Poolsize, - DB: rp.DbNum, - MasterName: rp.MasterName, - IdleTimeout: rp.idleTimeout, - IdleCheckFrequency: rp.idleCheckFrequency, - MaxRetries: rp.MaxRetries, + SentinelAddrs: strings.Split(rp.savePath, ";"), + Password: rp.password, + PoolSize: rp.poolsize, + DB: rp.dbNum, + MasterName: rp.masterName, }) return rp.poollist.Ping().Err() } -// for v1.x -func (rp *Provider) initOldStyle(savePath string) { - configs := strings.Split(savePath, ",") - if len(configs) > 0 { - rp.SavePath = configs[0] - } - if len(configs) > 1 { - poolsize, err := strconv.Atoi(configs[1]) - if err != nil || poolsize < 0 { - rp.Poolsize = DefaultPoolSize - } else { - rp.Poolsize = poolsize - } - } else { - rp.Poolsize = DefaultPoolSize - } - if len(configs) > 2 { - rp.Password = configs[2] - } - if len(configs) > 3 { - dbnum, err := strconv.Atoi(configs[3]) - if err != nil || dbnum < 0 { - rp.DbNum = 0 - } else { - rp.DbNum = dbnum - } - } else { - rp.DbNum = 0 - } - if len(configs) > 4 { - if configs[4] != "" { - rp.MasterName = configs[4] - } else { - rp.MasterName = "mymaster" - } - } else { - rp.MasterName = "mymaster" - } - if len(configs) > 5 { - timeout, err := strconv.Atoi(configs[4]) - if err == nil && timeout > 0 { - rp.idleTimeout = time.Duration(timeout) * time.Second - } - } - if len(configs) > 6 { - checkFrequency, err := strconv.Atoi(configs[5]) - if err == nil && checkFrequency > 0 { - rp.idleCheckFrequency = time.Duration(checkFrequency) * time.Second - } - } - if len(configs) > 7 { - retries, err := strconv.Atoi(configs[6]) - if err == nil && retries > 0 { - rp.MaxRetries = retries - } - } -} - // SessionRead read redis_sentinel session by sid -func (rp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { +func (rp *Provider) SessionRead(sid string) (session.Store, error) { var kv map[interface{}]interface{} kvs, err := rp.poollist.Get(sid).Result() if err != nil && err != redis.Nil { @@ -245,16 +189,16 @@ func (rp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, } // SessionExist check redis_sentinel session exist by sid -func (rp *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { +func (rp *Provider) SessionExist(sid string) bool { c := rp.poollist if existed, err := c.Exists(sid).Result(); err != nil || existed == 0 { - return false, err + return false } - return true, nil + return true } // SessionRegenerate generate new sid for redis_sentinel session -func (rp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { +func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { c := rp.poollist if existed, err := c.Exists(oldsid).Result(); err != nil || existed == 0 { @@ -266,22 +210,22 @@ func (rp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) ( c.Rename(oldsid, sid) c.Expire(sid, time.Duration(rp.maxlifetime)*time.Second) } - return rp.SessionRead(context.Background(), sid) + return rp.SessionRead(sid) } // SessionDestroy delete redis session by id -func (rp *Provider) SessionDestroy(ctx context.Context, sid string) error { +func (rp *Provider) SessionDestroy(sid string) error { c := rp.poollist c.Del(sid) return nil } // SessionGC Impelment method, no used. -func (rp *Provider) SessionGC(context.Context) { +func (rp *Provider) SessionGC() { } // SessionAll return all activeSession -func (rp *Provider) SessionAll(context.Context) int { +func (rp *Provider) SessionAll() int { return 0 } diff --git a/adapter/session/redis_sentinel/sess_redis_sentinel_test.go b/session/redis_sentinel/sess_redis_sentinel_test.go similarity index 96% rename from adapter/session/redis_sentinel/sess_redis_sentinel_test.go rename to session/redis_sentinel/sess_redis_sentinel_test.go index 407d32ab..fd4155c6 100644 --- a/adapter/session/redis_sentinel/sess_redis_sentinel_test.go +++ b/session/redis_sentinel/sess_redis_sentinel_test.go @@ -5,7 +5,7 @@ import ( "net/http/httptest" "testing" - "github.com/astaxie/beego/adapter/session" + "github.com/astaxie/beego/session" ) func TestRedisSentinel(t *testing.T) { @@ -23,7 +23,7 @@ func TestRedisSentinel(t *testing.T) { t.Log(e) return } - // todo test if e==nil + //todo test if e==nil go globalSessions.GC() r, _ := http.NewRequest("GET", "/", nil) diff --git a/server/web/session/sess_cookie.go b/session/sess_cookie.go similarity index 77% rename from server/web/session/sess_cookie.go rename to session/sess_cookie.go index 649f6510..6ad5debc 100644 --- a/server/web/session/sess_cookie.go +++ b/session/sess_cookie.go @@ -15,7 +15,6 @@ package session import ( - "context" "crypto/aes" "crypto/cipher" "encoding/json" @@ -35,7 +34,7 @@ type CookieSessionStore struct { // Set value to cookie session. // the value are encoded as gob with hash block string. -func (st *CookieSessionStore) Set(ctx context.Context, key, value interface{}) error { +func (st *CookieSessionStore) Set(key, value interface{}) error { st.lock.Lock() defer st.lock.Unlock() st.values[key] = value @@ -43,7 +42,7 @@ func (st *CookieSessionStore) Set(ctx context.Context, key, value interface{}) e } // Get value from cookie session -func (st *CookieSessionStore) Get(ctx context.Context, key interface{}) interface{} { +func (st *CookieSessionStore) Get(key interface{}) interface{} { st.lock.RLock() defer st.lock.RUnlock() if v, ok := st.values[key]; ok { @@ -53,7 +52,7 @@ func (st *CookieSessionStore) Get(ctx context.Context, key interface{}) interfac } // Delete value in cookie session -func (st *CookieSessionStore) Delete(ctx context.Context, key interface{}) error { +func (st *CookieSessionStore) Delete(key interface{}) error { st.lock.Lock() defer st.lock.Unlock() delete(st.values, key) @@ -61,7 +60,7 @@ func (st *CookieSessionStore) Delete(ctx context.Context, key interface{}) error } // Flush Clean all values in cookie session -func (st *CookieSessionStore) Flush(context.Context) error { +func (st *CookieSessionStore) Flush() error { st.lock.Lock() defer st.lock.Unlock() st.values = make(map[interface{}]interface{}) @@ -69,12 +68,12 @@ func (st *CookieSessionStore) Flush(context.Context) error { } // SessionID Return id of this cookie session -func (st *CookieSessionStore) SessionID(context.Context) string { +func (st *CookieSessionStore) SessionID() string { return st.sid } // SessionRelease Write cookie session to http response cookie -func (st *CookieSessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { +func (st *CookieSessionStore) SessionRelease(w http.ResponseWriter) { st.lock.Lock() encodedCookie, err := encodeCookie(cookiepder.block, cookiepder.config.SecurityKey, cookiepder.config.SecurityName, st.values) st.lock.Unlock() @@ -113,7 +112,7 @@ type CookieProvider struct { // securityName - recognized name in encoded cookie string // cookieName - cookie name // maxage - cookie max life time. -func (pder *CookieProvider) SessionInit(ctx context.Context, maxlifetime int64, config string) error { +func (pder *CookieProvider) SessionInit(maxlifetime int64, config string) error { pder.config = &cookieConfig{} err := json.Unmarshal([]byte(config), pder.config) if err != nil { @@ -135,7 +134,7 @@ func (pder *CookieProvider) SessionInit(ctx context.Context, maxlifetime int64, // SessionRead Get SessionStore in cooke. // decode cooke string to map and put into SessionStore with sid. -func (pder *CookieProvider) SessionRead(ctx context.Context, sid string) (Store, error) { +func (pder *CookieProvider) SessionRead(sid string) (Store, error) { maps, _ := decodeCookie(pder.block, pder.config.SecurityKey, pder.config.SecurityName, @@ -148,31 +147,31 @@ func (pder *CookieProvider) SessionRead(ctx context.Context, sid string) (Store, } // SessionExist Cookie session is always existed -func (pder *CookieProvider) SessionExist(ctx context.Context, sid string) (bool, error) { - return true, nil +func (pder *CookieProvider) SessionExist(sid string) bool { + return true } // SessionRegenerate Implement method, no used. -func (pder *CookieProvider) SessionRegenerate(ctx context.Context, oldsid, sid string) (Store, error) { +func (pder *CookieProvider) SessionRegenerate(oldsid, sid string) (Store, error) { return nil, nil } // SessionDestroy Implement method, no used. -func (pder *CookieProvider) SessionDestroy(ctx context.Context, sid string) error { +func (pder *CookieProvider) SessionDestroy(sid string) error { return nil } // SessionGC Implement method, no used. -func (pder *CookieProvider) SessionGC(context.Context) { +func (pder *CookieProvider) SessionGC() { } // SessionAll Implement method, return 0. -func (pder *CookieProvider) SessionAll(context.Context) int { +func (pder *CookieProvider) SessionAll() int { return 0 } // SessionUpdate Implement method, no used. -func (pder *CookieProvider) SessionUpdate(ctx context.Context, sid string) error { +func (pder *CookieProvider) SessionUpdate(sid string) error { return nil } diff --git a/adapter/session/sess_cookie_test.go b/session/sess_cookie_test.go similarity index 100% rename from adapter/session/sess_cookie_test.go rename to session/sess_cookie_test.go diff --git a/server/web/session/sess_file.go b/session/sess_file.go similarity index 85% rename from server/web/session/sess_file.go rename to session/sess_file.go index 90de9a79..c6dbf209 100644 --- a/server/web/session/sess_file.go +++ b/session/sess_file.go @@ -15,12 +15,11 @@ package session import ( - "context" - "errors" "fmt" "io/ioutil" "net/http" "os" + "errors" "path" "path/filepath" "strings" @@ -41,7 +40,7 @@ type FileSessionStore struct { } // Set value to file session -func (fs *FileSessionStore) Set(ctx context.Context, key, value interface{}) error { +func (fs *FileSessionStore) Set(key, value interface{}) error { fs.lock.Lock() defer fs.lock.Unlock() fs.values[key] = value @@ -49,7 +48,7 @@ func (fs *FileSessionStore) Set(ctx context.Context, key, value interface{}) err } // Get value from file session -func (fs *FileSessionStore) Get(ctx context.Context, key interface{}) interface{} { +func (fs *FileSessionStore) Get(key interface{}) interface{} { fs.lock.RLock() defer fs.lock.RUnlock() if v, ok := fs.values[key]; ok { @@ -59,7 +58,7 @@ func (fs *FileSessionStore) Get(ctx context.Context, key interface{}) interface{ } // Delete value in file session by given key -func (fs *FileSessionStore) Delete(ctx context.Context, key interface{}) error { +func (fs *FileSessionStore) Delete(key interface{}) error { fs.lock.Lock() defer fs.lock.Unlock() delete(fs.values, key) @@ -67,7 +66,7 @@ func (fs *FileSessionStore) Delete(ctx context.Context, key interface{}) error { } // Flush Clean all values in file session -func (fs *FileSessionStore) Flush(context.Context) error { +func (fs *FileSessionStore) Flush() error { fs.lock.Lock() defer fs.lock.Unlock() fs.values = make(map[interface{}]interface{}) @@ -75,12 +74,12 @@ func (fs *FileSessionStore) Flush(context.Context) error { } // SessionID Get file session store id -func (fs *FileSessionStore) SessionID(context.Context) string { +func (fs *FileSessionStore) SessionID() string { return fs.sid } // SessionRelease Write file session to local file with Gob string -func (fs *FileSessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { +func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) { filepder.lock.Lock() defer filepder.lock.Unlock() b, err := EncodeGob(fs.values) @@ -120,7 +119,7 @@ type FileProvider struct { // SessionInit Init file session provider. // savePath sets the session files path. -func (fp *FileProvider) SessionInit(ctx context.Context, maxlifetime int64, savePath string) error { +func (fp *FileProvider) SessionInit(maxlifetime int64, savePath string) error { fp.maxlifetime = maxlifetime fp.savePath = savePath return nil @@ -129,7 +128,7 @@ func (fp *FileProvider) SessionInit(ctx context.Context, maxlifetime int64, save // SessionRead Read file session by sid. // if file is not exist, create it. // the file path is generated from sid string. -func (fp *FileProvider) SessionRead(ctx context.Context, sid string) (Store, error) { +func (fp *FileProvider) SessionRead(sid string) (Store, error) { invalidChars := "./" if strings.ContainsAny(sid, invalidChars) { return nil, errors.New("the sid shouldn't have following characters: " + invalidChars) @@ -177,21 +176,16 @@ func (fp *FileProvider) SessionRead(ctx context.Context, sid string) (Store, err // SessionExist Check file session exist. // it checks the file named from sid exist or not. -func (fp *FileProvider) SessionExist(ctx context.Context, sid string) (bool, error) { +func (fp *FileProvider) SessionExist(sid string) bool { filepder.lock.Lock() defer filepder.lock.Unlock() - if len(sid) < 2 { - SLogger.Println("min length of session id is 2 but got length: ", sid) - return false, errors.New("min length of session id is 2") - } - _, err := os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid)) - return err == nil, nil + return err == nil } // SessionDestroy Remove all files in this save path -func (fp *FileProvider) SessionDestroy(ctx context.Context, sid string) error { +func (fp *FileProvider) SessionDestroy(sid string) error { filepder.lock.Lock() defer filepder.lock.Unlock() os.Remove(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid)) @@ -199,7 +193,7 @@ func (fp *FileProvider) SessionDestroy(ctx context.Context, sid string) error { } // SessionGC Recycle files in save path -func (fp *FileProvider) SessionGC(context.Context) { +func (fp *FileProvider) SessionGC() { filepder.lock.Lock() defer filepder.lock.Unlock() @@ -209,7 +203,7 @@ func (fp *FileProvider) SessionGC(context.Context) { // SessionAll Get active file session number. // it walks save path to count files. -func (fp *FileProvider) SessionAll(context.Context) int { +func (fp *FileProvider) SessionAll() int { a := &activeSession{} err := filepath.Walk(fp.savePath, func(path string, f os.FileInfo, err error) error { return a.visit(path, f, err) @@ -223,7 +217,7 @@ func (fp *FileProvider) SessionAll(context.Context) int { // SessionRegenerate Generate new sid for file session. // it delete old file and create new file named from new sid. -func (fp *FileProvider) SessionRegenerate(ctx context.Context, oldsid, sid string) (Store, error) { +func (fp *FileProvider) SessionRegenerate(oldsid, sid string) (Store, error) { filepder.lock.Lock() defer filepder.lock.Unlock() diff --git a/server/web/session/sess_mem.go b/session/sess_mem.go similarity index 78% rename from server/web/session/sess_mem.go rename to session/sess_mem.go index 27e24c73..64d8b056 100644 --- a/server/web/session/sess_mem.go +++ b/session/sess_mem.go @@ -16,7 +16,6 @@ package session import ( "container/list" - "context" "net/http" "sync" "time" @@ -34,7 +33,7 @@ type MemSessionStore struct { } // Set value to memory session -func (st *MemSessionStore) Set(ctx context.Context, key, value interface{}) error { +func (st *MemSessionStore) Set(key, value interface{}) error { st.lock.Lock() defer st.lock.Unlock() st.value[key] = value @@ -42,7 +41,7 @@ func (st *MemSessionStore) Set(ctx context.Context, key, value interface{}) erro } // Get value from memory session by key -func (st *MemSessionStore) Get(ctx context.Context, key interface{}) interface{} { +func (st *MemSessionStore) Get(key interface{}) interface{} { st.lock.RLock() defer st.lock.RUnlock() if v, ok := st.value[key]; ok { @@ -52,7 +51,7 @@ func (st *MemSessionStore) Get(ctx context.Context, key interface{}) interface{} } // Delete in memory session by key -func (st *MemSessionStore) Delete(ctx context.Context, key interface{}) error { +func (st *MemSessionStore) Delete(key interface{}) error { st.lock.Lock() defer st.lock.Unlock() delete(st.value, key) @@ -60,7 +59,7 @@ func (st *MemSessionStore) Delete(ctx context.Context, key interface{}) error { } // Flush clear all values in memory session -func (st *MemSessionStore) Flush(context.Context) error { +func (st *MemSessionStore) Flush() error { st.lock.Lock() defer st.lock.Unlock() st.value = make(map[interface{}]interface{}) @@ -68,12 +67,12 @@ func (st *MemSessionStore) Flush(context.Context) error { } // SessionID get this id of memory session store -func (st *MemSessionStore) SessionID(context.Context) string { +func (st *MemSessionStore) SessionID() string { return st.sid } // SessionRelease Implement method, no used. -func (st *MemSessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { +func (st *MemSessionStore) SessionRelease(w http.ResponseWriter) { } // MemProvider Implement the provider interface @@ -86,17 +85,17 @@ type MemProvider struct { } // SessionInit init memory session -func (pder *MemProvider) SessionInit(ctx context.Context, maxlifetime int64, savePath string) error { +func (pder *MemProvider) SessionInit(maxlifetime int64, savePath string) error { pder.maxlifetime = maxlifetime pder.savePath = savePath return nil } // SessionRead get memory session store by sid -func (pder *MemProvider) SessionRead(ctx context.Context, sid string) (Store, error) { +func (pder *MemProvider) SessionRead(sid string) (Store, error) { pder.lock.RLock() if element, ok := pder.sessions[sid]; ok { - go pder.SessionUpdate(nil, sid) + go pder.SessionUpdate(sid) pder.lock.RUnlock() return element.Value.(*MemSessionStore), nil } @@ -110,20 +109,20 @@ func (pder *MemProvider) SessionRead(ctx context.Context, sid string) (Store, er } // SessionExist check session store exist in memory session by sid -func (pder *MemProvider) SessionExist(ctx context.Context, sid string) (bool, error) { +func (pder *MemProvider) SessionExist(sid string) bool { pder.lock.RLock() defer pder.lock.RUnlock() if _, ok := pder.sessions[sid]; ok { - return true, nil + return true } - return false, nil + return false } // SessionRegenerate generate new sid for session store in memory session -func (pder *MemProvider) SessionRegenerate(ctx context.Context, oldsid, sid string) (Store, error) { +func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (Store, error) { pder.lock.RLock() if element, ok := pder.sessions[oldsid]; ok { - go pder.SessionUpdate(nil, oldsid) + go pder.SessionUpdate(oldsid) pder.lock.RUnlock() pder.lock.Lock() element.Value.(*MemSessionStore).sid = sid @@ -142,7 +141,7 @@ func (pder *MemProvider) SessionRegenerate(ctx context.Context, oldsid, sid stri } // SessionDestroy delete session store in memory session by id -func (pder *MemProvider) SessionDestroy(ctx context.Context, sid string) error { +func (pder *MemProvider) SessionDestroy(sid string) error { pder.lock.Lock() defer pder.lock.Unlock() if element, ok := pder.sessions[sid]; ok { @@ -154,7 +153,7 @@ func (pder *MemProvider) SessionDestroy(ctx context.Context, sid string) error { } // SessionGC clean expired session stores in memory session -func (pder *MemProvider) SessionGC(context.Context) { +func (pder *MemProvider) SessionGC() { pder.lock.RLock() for { element := pder.list.Back() @@ -176,12 +175,12 @@ func (pder *MemProvider) SessionGC(context.Context) { } // SessionAll get count number of memory session -func (pder *MemProvider) SessionAll(context.Context) int { +func (pder *MemProvider) SessionAll() int { return pder.list.Len() } // SessionUpdate expand time of session store by id in memory session -func (pder *MemProvider) SessionUpdate(ctx context.Context, sid string) error { +func (pder *MemProvider) SessionUpdate(sid string) error { pder.lock.Lock() defer pder.lock.Unlock() if element, ok := pder.sessions[sid]; ok { diff --git a/adapter/session/sess_mem_test.go b/session/sess_mem_test.go similarity index 100% rename from adapter/session/sess_mem_test.go rename to session/sess_mem_test.go diff --git a/server/web/session/sess_test.go b/session/sess_test.go similarity index 100% rename from server/web/session/sess_test.go rename to session/sess_test.go diff --git a/server/web/session/sess_utils.go b/session/sess_utils.go similarity index 99% rename from server/web/session/sess_utils.go rename to session/sess_utils.go index 8a031dd5..20915bb6 100644 --- a/server/web/session/sess_utils.go +++ b/session/sess_utils.go @@ -29,7 +29,7 @@ import ( "strconv" "time" - "github.com/astaxie/beego/core/utils" + "github.com/astaxie/beego/utils" ) func init() { diff --git a/server/web/session/session.go b/session/session.go similarity index 86% rename from server/web/session/session.go rename to session/session.go index bb7e5bd6..eb85360a 100644 --- a/server/web/session/session.go +++ b/session/session.go @@ -28,7 +28,6 @@ package session import ( - "context" "crypto/rand" "encoding/hex" "errors" @@ -44,24 +43,24 @@ import ( // Store contains all data for one session process with specific id. type Store interface { - Set(ctx context.Context, key, value interface{}) error //set session value - Get(ctx context.Context, key interface{}) interface{} //get session value - Delete(ctx context.Context, key interface{}) error //delete session value - SessionID(ctx context.Context) string //back current sessionID - SessionRelease(ctx context.Context, w http.ResponseWriter) // release the resource & save data to provider & return the data - Flush(ctx context.Context) error //delete all data + Set(key, value interface{}) error //set session value + Get(key interface{}) interface{} //get session value + Delete(key interface{}) error //delete session value + SessionID() string //back current sessionID + SessionRelease(w http.ResponseWriter) // release the resource & save data to provider & return the data + Flush() error //delete all data } // Provider contains global session methods and saved SessionStores. // it can operate a SessionStore by its id. type Provider interface { - SessionInit(ctx context.Context, gclifetime int64, config string) error - SessionRead(ctx context.Context, sid string) (Store, error) - SessionExist(ctx context.Context, sid string) (bool, error) - SessionRegenerate(ctx context.Context, oldsid, sid string) (Store, error) - SessionDestroy(ctx context.Context, sid string) error - SessionAll(ctx context.Context) int //get all active session - SessionGC(ctx context.Context) + SessionInit(gclifetime int64, config string) error + SessionRead(sid string) (Store, error) + SessionExist(sid string) bool + SessionRegenerate(oldsid, sid string) (Store, error) + SessionDestroy(sid string) error + SessionAll() int //get all active session + SessionGC() } var provides = make(map[string]Provider) @@ -149,7 +148,7 @@ func NewManager(provideName string, cf *ManagerConfig) (*Manager, error) { } } - err := provider.SessionInit(nil, cf.Maxlifetime, cf.ProviderConfig) + err := provider.SessionInit(cf.Maxlifetime, cf.ProviderConfig) if err != nil { return nil, err } @@ -212,14 +211,8 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se return nil, errs } - if sid != "" { - exists, err := manager.provider.SessionExist(nil, sid) - if err != nil { - return nil, err - } - if exists { - return manager.provider.SessionRead(nil, sid) - } + if sid != "" && manager.provider.SessionExist(sid) { + return manager.provider.SessionRead(sid) } // Generate a new session @@ -228,7 +221,7 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se return nil, errs } - session, err = manager.provider.SessionRead(nil, sid) + session, err = manager.provider.SessionRead(sid) if err != nil { return nil, err } @@ -270,7 +263,7 @@ func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) { } sid, _ := url.QueryUnescape(cookie.Value) - manager.provider.SessionDestroy(nil, sid) + manager.provider.SessionDestroy(sid) if manager.config.EnableSetCookie { expiration := time.Now() cookie = &http.Cookie{Name: manager.config.CookieName, @@ -286,14 +279,14 @@ func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) { // GetSessionStore Get SessionStore by its id. func (manager *Manager) GetSessionStore(sid string) (sessions Store, err error) { - sessions, err = manager.provider.SessionRead(nil, sid) + sessions, err = manager.provider.SessionRead(sid) return } // GC Start session gc process. // it can do gc in times after gc lifetime. func (manager *Manager) GC() { - manager.provider.SessionGC(nil) + manager.provider.SessionGC() time.AfterFunc(time.Duration(manager.config.Gclifetime)*time.Second, func() { manager.GC() }) } @@ -306,7 +299,7 @@ func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Reque cookie, err := r.Cookie(manager.config.CookieName) if err != nil || cookie.Value == "" { //delete old cookie - session, _ = manager.provider.SessionRead(nil, sid) + session, _ = manager.provider.SessionRead(sid) cookie = &http.Cookie{Name: manager.config.CookieName, Value: url.QueryEscape(sid), Path: "/", @@ -316,7 +309,7 @@ func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Reque } } else { oldsid, _ := url.QueryUnescape(cookie.Value) - session, _ = manager.provider.SessionRegenerate(nil, oldsid, sid) + session, _ = manager.provider.SessionRegenerate(oldsid, sid) cookie.Value = url.QueryEscape(sid) cookie.HttpOnly = true cookie.Path = "/" @@ -340,7 +333,7 @@ func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Reque // GetActiveSession Get all active sessions count number. func (manager *Manager) GetActiveSession() int { - return manager.provider.SessionAll(nil) + return manager.provider.SessionAll() } // SetSecure Set cookie with https. diff --git a/server/web/session/ssdb/sess_ssdb.go b/session/ssdb/sess_ssdb.go similarity index 66% rename from server/web/session/ssdb/sess_ssdb.go rename to session/ssdb/sess_ssdb.go index 0adc41bd..de0c6360 100644 --- a/server/web/session/ssdb/sess_ssdb.go +++ b/session/ssdb/sess_ssdb.go @@ -1,17 +1,14 @@ package ssdb import ( - "context" - "encoding/json" "errors" "net/http" "strconv" "strings" "sync" + "github.com/astaxie/beego/session" "github.com/ssdb/gossdb/ssdb" - - "github.com/astaxie/beego/server/web/session" ) var ssdbProvider = &Provider{} @@ -19,50 +16,35 @@ var ssdbProvider = &Provider{} // Provider holds ssdb client and configs type Provider struct { client *ssdb.Client - Host string `json:"host"` - Port int `json:"port"` + host string + port int maxLifetime int64 } func (p *Provider) connectInit() error { var err error - if p.Host == "" || p.Port == 0 { + if p.host == "" || p.port == 0 { return errors.New("SessionInit First") } - p.client, err = ssdb.Connect(p.Host, p.Port) + p.client, err = ssdb.Connect(p.host, p.port) return err } // SessionInit init the ssdb with the config -func (p *Provider) SessionInit(ctx context.Context, maxLifetime int64, cfg string) error { +func (p *Provider) SessionInit(maxLifetime int64, savePath string) error { p.maxLifetime = maxLifetime + address := strings.Split(savePath, ":") + p.host = address[0] - cfg = strings.TrimSpace(cfg) var err error - // we think this is v2.0, using json to init the session - if strings.HasPrefix(cfg, "{") { - err = json.Unmarshal([]byte(cfg), p) - } else { - err = p.initOldStyle(cfg) - } - if err != nil { + if p.port, err = strconv.Atoi(address[1]); err != nil { return err } return p.connectInit() } -// for v1.x -func (p *Provider) initOldStyle(savePath string) error { - address := strings.Split(savePath, ":") - p.Host = address[0] - - var err error - p.Port, err = strconv.Atoi(address[1]) - return err -} - // SessionRead return a ssdb client session Store -func (p *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { +func (p *Provider) SessionRead(sid string) (session.Store, error) { if p.client == nil { if err := p.connectInit(); err != nil { return nil, err @@ -86,10 +68,10 @@ func (p *Provider) SessionRead(ctx context.Context, sid string) (session.Store, } // SessionExist judged whether sid is exist in session -func (p *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { +func (p *Provider) SessionExist(sid string) bool { if p.client == nil { if err := p.connectInit(); err != nil { - return false, err + panic(err) } } value, err := p.client.Get(sid) @@ -97,14 +79,14 @@ func (p *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { panic(err) } if value == nil || len(value.(string)) == 0 { - return false, nil + return false } - return true, nil + return true } // SessionRegenerate regenerate session with new sid and delete oldsid -func (p *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { - // conn.Do("setx", key, v, ttl) +func (p *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { + //conn.Do("setx", key, v, ttl) if p.client == nil { if err := p.connectInit(); err != nil { return nil, err @@ -136,7 +118,7 @@ func (p *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (s } // SessionDestroy destroy the sid -func (p *Provider) SessionDestroy(ctx context.Context, sid string) error { +func (p *Provider) SessionDestroy(sid string) error { if p.client == nil { if err := p.connectInit(); err != nil { return err @@ -147,11 +129,11 @@ func (p *Provider) SessionDestroy(ctx context.Context, sid string) error { } // SessionGC not implemented -func (p *Provider) SessionGC(context.Context) { +func (p *Provider) SessionGC() { } // SessionAll not implemented -func (p *Provider) SessionAll(context.Context) int { +func (p *Provider) SessionAll() int { return 0 } @@ -165,7 +147,7 @@ type SessionStore struct { } // Set the key and value -func (s *SessionStore) Set(ctx context.Context, key, value interface{}) error { +func (s *SessionStore) Set(key, value interface{}) error { s.lock.Lock() defer s.lock.Unlock() s.values[key] = value @@ -173,7 +155,7 @@ func (s *SessionStore) Set(ctx context.Context, key, value interface{}) error { } // Get return the value by the key -func (s *SessionStore) Get(ctx context.Context, key interface{}) interface{} { +func (s *SessionStore) Get(key interface{}) interface{} { s.lock.Lock() defer s.lock.Unlock() if value, ok := s.values[key]; ok { @@ -183,7 +165,7 @@ func (s *SessionStore) Get(ctx context.Context, key interface{}) interface{} { } // Delete the key in session store -func (s *SessionStore) Delete(ctx context.Context, key interface{}) error { +func (s *SessionStore) Delete(key interface{}) error { s.lock.Lock() defer s.lock.Unlock() delete(s.values, key) @@ -191,7 +173,7 @@ func (s *SessionStore) Delete(ctx context.Context, key interface{}) error { } // Flush delete all keys and values -func (s *SessionStore) Flush(context.Context) error { +func (s *SessionStore) Flush() error { s.lock.Lock() defer s.lock.Unlock() s.values = make(map[interface{}]interface{}) @@ -199,12 +181,12 @@ func (s *SessionStore) Flush(context.Context) error { } // SessionID return the sessionID -func (s *SessionStore) SessionID(context.Context) string { +func (s *SessionStore) SessionID() string { return s.sid } // SessionRelease Store the keyvalues into ssdb -func (s *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { +func (s *SessionStore) SessionRelease(w http.ResponseWriter) { b, err := session.EncodeGob(s.values) if err != nil { return diff --git a/server/web/staticfile.go b/staticfile.go similarity index 96% rename from server/web/staticfile.go rename to staticfile.go index aa3f35d8..84e9aa7b 100644 --- a/server/web/staticfile.go +++ b/staticfile.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "bytes" @@ -26,10 +26,9 @@ import ( "sync" "time" - "github.com/astaxie/beego/core/logs" - lru "github.com/hashicorp/golang-lru" - - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/context" + "github.com/astaxie/beego/logs" + "github.com/hashicorp/golang-lru" ) var errNotStaticRequest = errors.New("request not a static file request") @@ -203,7 +202,7 @@ func searchFile(ctx *context.Context) (string, os.FileInfo, error) { if !strings.Contains(requestPath, prefix) { continue } - if prefix != "/" && len(requestPath) > len(prefix) && requestPath[len(prefix)] != '/' { + if prefix != "/" && len(requestPath) > len(prefix) && requestPath[len(prefix)] != '/' { continue } filePath := path.Join(staticDir, requestPath[len(prefix):]) diff --git a/server/web/staticfile_test.go b/staticfile_test.go similarity index 99% rename from server/web/staticfile_test.go rename to staticfile_test.go index 0725a2f8..e46c13ec 100644 --- a/server/web/staticfile_test.go +++ b/staticfile_test.go @@ -1,4 +1,4 @@ -package web +package beego import ( "bytes" diff --git a/server/web/swagger/swagger.go b/swagger/swagger.go similarity index 100% rename from server/web/swagger/swagger.go rename to swagger/swagger.go diff --git a/task/govenor_command.go b/task/govenor_command.go deleted file mode 100644 index 15e25e43..00000000 --- a/task/govenor_command.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 task - -import ( - "context" - "fmt" - "html/template" - - "github.com/pkg/errors" - - "github.com/astaxie/beego/core/governor" -) - -type listTaskCommand struct { -} - -func (l *listTaskCommand) Execute(params ...interface{}) *governor.Result { - resultList := make([][]string, 0, len(globalTaskManager.adminTaskList)) - for tname, tk := range globalTaskManager.adminTaskList { - result := []string{ - template.HTMLEscapeString(tname), - template.HTMLEscapeString(tk.GetSpec(nil)), - template.HTMLEscapeString(tk.GetStatus(nil)), - template.HTMLEscapeString(tk.GetPrev(context.Background()).String()), - } - resultList = append(resultList, result) - } - - return &governor.Result{ - Status: 200, - Content: resultList, - } -} - -type runTaskCommand struct { -} - -func (r *runTaskCommand) Execute(params ...interface{}) *governor.Result { - if len(params) == 0 { - return &governor.Result{ - Status: 400, - Error: errors.New("task name not passed"), - } - } - - tn, ok := params[0].(string) - - if !ok { - return &governor.Result{ - Status: 400, - Error: errors.New("parameter is invalid"), - } - } - - if t, ok := globalTaskManager.adminTaskList[tn]; ok { - err := t.Run(context.Background()) - if err != nil { - return &governor.Result{ - Status: 500, - Error: err, - } - } - return &governor.Result{ - Status: 200, - Content: t.GetStatus(context.Background()), - } - } else { - return &governor.Result{ - Status: 400, - Error: errors.New(fmt.Sprintf("task with name %s not found", tn)), - } - } - -} - -func registerCommands() { - governor.RegisterCommand("task", "list", &listTaskCommand{}) - governor.RegisterCommand("task", "run", &runTaskCommand{}) -} diff --git a/task/governor_command_test.go b/task/governor_command_test.go deleted file mode 100644 index 00ed37f2..00000000 --- a/task/governor_command_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2020 -// -// Licensed 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 task - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -type countTask struct { - cnt int - mockErr error -} - -func (c *countTask) GetSpec(ctx context.Context) string { - return "AAA" -} - -func (c *countTask) GetStatus(ctx context.Context) string { - return "SUCCESS" -} - -func (c *countTask) Run(ctx context.Context) error { - c.cnt++ - return c.mockErr -} - -func (c *countTask) SetNext(ctx context.Context, time time.Time) { -} - -func (c *countTask) GetNext(ctx context.Context) time.Time { - return time.Now() -} - -func (c *countTask) SetPrev(ctx context.Context, time time.Time) { -} - -func (c *countTask) GetPrev(ctx context.Context) time.Time { - return time.Now() -} - -func TestRunTaskCommand_Execute(t *testing.T) { - task := &countTask{} - AddTask("count", task) - - cmd := &runTaskCommand{} - - res := cmd.Execute() - assert.NotNil(t, res) - assert.NotNil(t, res.Error) - assert.Equal(t, "task name not passed", res.Error.Error()) - - res = cmd.Execute(10) - assert.NotNil(t, res) - assert.NotNil(t, res.Error) - assert.Equal(t, "parameter is invalid", res.Error.Error()) - - res = cmd.Execute("CCCC") - assert.NotNil(t, res) - assert.NotNil(t, res.Error) - assert.Equal(t, "task with name CCCC not found", res.Error.Error()) - - res = cmd.Execute("count") - assert.NotNil(t, res) - assert.True(t, res.IsSuccess()) - - task.mockErr = errors.New("mock error") - res = cmd.Execute("count") - assert.NotNil(t, res) - assert.NotNil(t, res.Error) - assert.Equal(t, "mock error", res.Error.Error()) -} - -func TestListTaskCommand_Execute(t *testing.T) { - task := &countTask{} - - cmd := &listTaskCommand{} - - res := cmd.Execute() - - assert.True(t, res.IsSuccess()) - - _, ok := res.Content.([][]string) - assert.True(t, ok) - - AddTask("count", task) - - res = cmd.Execute() - - assert.True(t, res.IsSuccess()) - - rl, ok := res.Content.([][]string) - assert.True(t, ok) - assert.Equal(t, 1, len(rl)) -} diff --git a/task/task_test.go b/task/task_test.go deleted file mode 100644 index 2cb807ce..00000000 --- a/task/task_test.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 task - -import ( - "context" - "errors" - "fmt" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestParse(t *testing.T) { - m := newTaskManager() - defer m.ClearTask() - tk := NewTask("taska", "0/30 * * * * *", func(ctx context.Context) error { - fmt.Println("hello world") - return nil - }) - err := tk.Run(nil) - if err != nil { - t.Fatal(err) - } - m.AddTask("taska", tk) - m.StartTask() - time.Sleep(3 * time.Second) - m.StopTask() -} - -func TestModifyTaskListAfterRunning(t *testing.T) { - m := newTaskManager() - defer m.ClearTask() - tk := NewTask("taskb", "0/30 * * * * *", func(ctx context.Context) error { - fmt.Println("hello world") - return nil - }) - err := tk.Run(nil) - if err != nil { - t.Fatal(err) - } - m.AddTask("taskb", tk) - m.StartTask() - go func() { - m.DeleteTask("taskb") - }() - go func() { - m.AddTask("taskb1", tk) - }() - - time.Sleep(3 * time.Second) - m.StopTask() -} - -func TestSpec(t *testing.T) { - m := newTaskManager() - defer m.ClearTask() - wg := &sync.WaitGroup{} - wg.Add(2) - tk1 := NewTask("tk1", "0 12 * * * *", func(ctx context.Context) error { fmt.Println("tk1"); return nil }) - tk2 := NewTask("tk2", "0,10,20 * * * * *", func(ctx context.Context) error { fmt.Println("tk2"); wg.Done(); return nil }) - tk3 := NewTask("tk3", "0 10 * * * *", func(ctx context.Context) error { fmt.Println("tk3"); wg.Done(); return nil }) - - m.AddTask("tk1", tk1) - m.AddTask("tk2", tk2) - m.AddTask("tk3", tk3) - m.StartTask() - defer m.StopTask() - - select { - case <-time.After(200 * time.Second): - t.FailNow() - case <-wait(wg): - } -} - -func TestTask_Run(t *testing.T) { - cnt := -1 - task := func(ctx context.Context) error { - cnt++ - fmt.Printf("Hello, world! %d \n", cnt) - return errors.New(fmt.Sprintf("Hello, world! %d", cnt)) - } - tk := NewTask("taska", "0/30 * * * * *", task) - for i := 0; i < 200; i++ { - e := tk.Run(nil) - assert.NotNil(t, e) - } - - l := tk.Errlist - assert.Equal(t, 100, len(l)) - assert.Equal(t, "Hello, world! 100", l[0].errinfo) - assert.Equal(t, "Hello, world! 101", l[1].errinfo) -} - -func wait(wg *sync.WaitGroup) chan bool { - ch := make(chan bool) - go func() { - wg.Wait() - ch <- true - }() - return ch -} diff --git a/server/web/template.go b/template.go similarity index 97% rename from server/web/template.go rename to template.go index d582dcda..59875be7 100644 --- a/server/web/template.go +++ b/template.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "errors" @@ -27,8 +27,8 @@ import ( "strings" "sync" - "github.com/astaxie/beego/core/logs" - "github.com/astaxie/beego/core/utils" + "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/utils" ) var ( @@ -368,14 +368,14 @@ func SetTemplateFSFunc(fnt templateFSFunc) { } // SetViewsPath sets view directory path in beego application. -func SetViewsPath(path string) *HttpServer { +func SetViewsPath(path string) *App { BConfig.WebConfig.ViewsPath = path return BeeApp } // SetStaticPath sets static directory path and proper url pattern in beego application. // if beego.SetStaticPath("static","public"), visit /static/* to load static file in folder "public". -func SetStaticPath(url string, path string) *HttpServer { +func SetStaticPath(url string, path string) *App { if !strings.HasPrefix(url, "/") { url = "/" + url } @@ -387,7 +387,7 @@ func SetStaticPath(url string, path string) *HttpServer { } // DelStaticPath removes the static folder setting in this url pattern in beego application. -func DelStaticPath(url string) *HttpServer { +func DelStaticPath(url string) *App { if !strings.HasPrefix(url, "/") { url = "/" + url } @@ -399,7 +399,7 @@ func DelStaticPath(url string) *HttpServer { } // AddTemplateEngine add a new templatePreProcessor which support extension -func AddTemplateEngine(extension string, fn templatePreProcessor) *HttpServer { +func AddTemplateEngine(extension string, fn templatePreProcessor) *App { AddTemplateExt(extension) beeTemplateEngines[extension] = fn return BeeApp diff --git a/server/web/template_test.go b/template_test.go similarity index 88% rename from server/web/template_test.go rename to template_test.go index b542494d..287faadc 100644 --- a/server/web/template_test.go +++ b/template_test.go @@ -12,19 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "bytes" + "github.com/astaxie/beego/testdata" + "github.com/elazarl/go-bindata-assetfs" "net/http" "os" "path/filepath" "testing" - - assetfs "github.com/elazarl/go-bindata-assetfs" - "github.com/stretchr/testify/assert" - - "github.com/astaxie/beego/test" ) var header = `{{define "header"}} @@ -49,9 +46,7 @@ var block = `{{define "block"}} {{end}}` func TestTemplate(t *testing.T) { - wkdir, err := os.Getwd() - assert.Nil(t, err) - dir := filepath.Join(wkdir, "_beeTmp", "TestTemplate") + dir := "_beeTmp" files := []string{ "header.tpl", "index.tpl", @@ -61,8 +56,7 @@ func TestTemplate(t *testing.T) { t.Fatal(err) } for k, name := range files { - dirErr := os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0777) - assert.Nil(t, dirErr) + os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0777) if f, err := os.Create(filepath.Join(dir, name)); err != nil { t.Fatal(err) } else { @@ -113,9 +107,7 @@ var user = ` ` func TestRelativeTemplate(t *testing.T) { - wkdir, err := os.Getwd() - assert.Nil(t, err) - dir := filepath.Join(wkdir, "_beeTmp") + dir := "_beeTmp" //Just add dir to known viewPaths if err := AddViewPath(dir); err != nil { @@ -226,10 +218,7 @@ var output = ` ` func TestTemplateLayout(t *testing.T) { - wkdir, err := os.Getwd() - assert.Nil(t, err) - - dir := filepath.Join(wkdir, "_beeTmp", "TestTemplateLayout") + dir := "_beeTmp" files := []string{ "add.tpl", "layout_blog.tpl", @@ -237,22 +226,17 @@ func TestTemplateLayout(t *testing.T) { if err := os.MkdirAll(dir, 0777); err != nil { t.Fatal(err) } - for k, name := range files { - dirErr := os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0777) - assert.Nil(t, dirErr) + os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0777) if f, err := os.Create(filepath.Join(dir, name)); err != nil { t.Fatal(err) } else { if k == 0 { - _, writeErr := f.WriteString(add) - assert.Nil(t, writeErr) + f.WriteString(add) } else if k == 1 { - _, writeErr := f.WriteString(layoutBlog) - assert.Nil(t, writeErr) + f.WriteString(layoutBlog) } - clErr := f.Close() - assert.Nil(t, clErr) + f.Close() } } if err := AddViewPath(dir); err != nil { @@ -263,7 +247,6 @@ func TestTemplateLayout(t *testing.T) { t.Fatalf("should be 2 but got %v", len(beeTemplates)) } out := bytes.NewBufferString("") - if err := beeTemplates["add.tpl"].ExecuteTemplate(out, "add.tpl", map[string]string{"Title": "Hello", "SomeVar": "val"}); err != nil { t.Fatal(err) } @@ -308,7 +291,7 @@ var outputBinData = ` func TestFsBinData(t *testing.T) { SetTemplateFSFunc(func() http.FileSystem { - return TestingFileSystem{&assetfs.AssetFS{Asset: test.Asset, AssetDir: test.AssetDir, AssetInfo: test.AssetInfo}} + return TestingFileSystem{&assetfs.AssetFS{Asset: testdata.Asset, AssetDir: testdata.AssetDir, AssetInfo: testdata.AssetInfo}} }) dir := "views" if err := AddViewPath("views"); err != nil { diff --git a/server/web/templatefunc.go b/templatefunc.go similarity index 98% rename from server/web/templatefunc.go rename to templatefunc.go index 53c99018..ba1ec5eb 100644 --- a/server/web/templatefunc.go +++ b/templatefunc.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "errors" @@ -58,11 +58,11 @@ func HTML2str(html string) string { re := regexp.MustCompile(`\<[\S\s]+?\>`) html = re.ReplaceAllStringFunc(html, strings.ToLower) - // remove STYLE + //remove STYLE re = regexp.MustCompile(`\`) html = re.ReplaceAllString(html, "") - // remove SCRIPT + //remove SCRIPT re = regexp.MustCompile(`\`) html = re.ReplaceAllString(html, "") @@ -85,7 +85,7 @@ func DateFormat(t time.Time, layout string) (datestring string) { var datePatterns = []string{ // year "Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003 - "y", "06", // A two digit representation of a year Examples: 99 or 03 + "y", "06", //A two digit representation of a year Examples: 99 or 03 // month "m", "01", // Numeric representation of a month, with leading zeros 01 through 12 @@ -160,7 +160,7 @@ func NotNil(a interface{}) (isNil bool) { func GetConfig(returnType, key string, defaultVal interface{}) (value interface{}, err error) { switch returnType { case "String": - value, err = AppConfig.String(key) + value = AppConfig.String(key) case "Bool": value, err = AppConfig.Bool(key) case "Int": @@ -201,7 +201,7 @@ func Str2html(raw string) template.HTML { // Htmlquote returns quoted html string. func Htmlquote(text string) string { - // HTML编码为实体符号 + //HTML编码为实体符号 /* Encodes `text` for raw use in HTML. >>> htmlquote("<'&\\">") @@ -220,7 +220,7 @@ func Htmlquote(text string) string { // Htmlunquote returns unquoted html string. func Htmlunquote(text string) string { - // 实体符号解释为HTML + //实体符号解释为HTML /* Decodes `text` that's HTML quoted. >>> htmlunquote('<'&">') @@ -362,7 +362,7 @@ func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) e value = value[:25] t, err = time.ParseInLocation(time.RFC3339, value, time.Local) } else if strings.HasSuffix(strings.ToUpper(value), "Z") { - t, err = time.ParseInLocation(time.RFC3339, value, time.Local) + t, err = time.ParseInLocation(time.RFC3339, value, time.Local) } else if len(value) >= 19 { if strings.Contains(value, "T") { value = value[:19] diff --git a/server/web/templatefunc_test.go b/templatefunc_test.go similarity index 99% rename from server/web/templatefunc_test.go rename to templatefunc_test.go index df5cfa40..b4c19c2e 100644 --- a/server/web/templatefunc_test.go +++ b/templatefunc_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "html/template" diff --git a/test/Makefile b/testdata/Makefile similarity index 100% rename from test/Makefile rename to testdata/Makefile diff --git a/test/bindata.go b/testdata/bindata.go similarity index 99% rename from test/bindata.go rename to testdata/bindata.go index 196ea95c..beade103 100644 --- a/test/bindata.go +++ b/testdata/bindata.go @@ -5,20 +5,19 @@ // views/index.tpl // DO NOT EDIT! -package test +package testdata import ( "bytes" "compress/gzip" "fmt" + "github.com/elazarl/go-bindata-assetfs" "io" "io/ioutil" "os" "path/filepath" "strings" "time" - - assetfs "github.com/elazarl/go-bindata-assetfs" ) func bindataRead(data []byte, name string) ([]byte, error) { diff --git a/test/views/blocks/block.tpl b/testdata/views/blocks/block.tpl similarity index 100% rename from test/views/blocks/block.tpl rename to testdata/views/blocks/block.tpl diff --git a/test/views/header.tpl b/testdata/views/header.tpl similarity index 100% rename from test/views/header.tpl rename to testdata/views/header.tpl diff --git a/test/views/index.tpl b/testdata/views/index.tpl similarity index 100% rename from test/views/index.tpl rename to testdata/views/index.tpl diff --git a/adapter/config/json.go b/testing/assertions.go similarity index 89% rename from adapter/config/json.go rename to testing/assertions.go index d77e6146..96c5d4dd 100644 --- a/adapter/config/json.go +++ b/testing/assertions.go @@ -12,8 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -package config - -import ( - _ "github.com/astaxie/beego/core/config/json" -) +package testing diff --git a/client/httplib/testing/client.go b/testing/client.go similarity index 88% rename from client/httplib/testing/client.go rename to testing/client.go index 34e49f2d..c3737e9c 100644 --- a/client/httplib/testing/client.go +++ b/testing/client.go @@ -15,7 +15,8 @@ package testing import ( - "github.com/astaxie/beego/client/httplib" + "github.com/astaxie/beego/config" + "github.com/astaxie/beego/httplib" ) var port = "" @@ -26,13 +27,13 @@ type TestHTTPRequest struct { httplib.BeegoHTTPRequest } -func SetTestingPort(p string) { - port = p -} - func getPort() string { if port == "" { - port = "8080" + config, err := config.NewConfig("ini", "../conf/app.conf") + if err != nil { + return "8080" + } + port = config.String("httpport") return port } return port diff --git a/core/governor/healthcheck.go b/toolbox/healthcheck.go similarity index 96% rename from core/governor/healthcheck.go rename to toolbox/healthcheck.go index a91f09fa..e3544b3a 100644 --- a/core/governor/healthcheck.go +++ b/toolbox/healthcheck.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package governor healthcheck +// Package toolbox healthcheck // // type DatabaseCheck struct { // } @@ -28,7 +28,7 @@ // AddHealthCheck("database",&DatabaseCheck{}) // // more docs: http://beego.me/docs/module/toolbox.md -package governor +package toolbox // AdminCheckList holds health checker map var AdminCheckList map[string]HealthChecker diff --git a/core/governor/profile.go b/toolbox/profile.go similarity index 82% rename from core/governor/profile.go rename to toolbox/profile.go index 17f1f375..06e40ede 100644 --- a/core/governor/profile.go +++ b/toolbox/profile.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package governor +package toolbox import ( "fmt" @@ -25,8 +25,6 @@ import ( "runtime/pprof" "strconv" "time" - - "github.com/astaxie/beego/core/utils" ) var startTime = time.Now() @@ -114,15 +112,15 @@ func printGC(memStats *runtime.MemStats, gcstats *debug.GCStats, w io.Writer) { fmt.Fprintf(w, "NumGC:%d Pause:%s Pause(Avg):%s Overhead:%3.2f%% Alloc:%s Sys:%s Alloc(Rate):%s/s Histogram:%s %s %s \n", gcstats.NumGC, - utils.ToShortTimeFormat(lastPause), - utils.ToShortTimeFormat(avg(gcstats.Pause)), + toS(lastPause), + toS(avg(gcstats.Pause)), overhead, toH(memStats.Alloc), toH(memStats.Sys), toH(uint64(allocatedRate)), - utils.ToShortTimeFormat(gcstats.PauseQuantiles[94]), - utils.ToShortTimeFormat(gcstats.PauseQuantiles[98]), - utils.ToShortTimeFormat(gcstats.PauseQuantiles[99])) + toS(gcstats.PauseQuantiles[94]), + toS(gcstats.PauseQuantiles[98]), + toS(gcstats.PauseQuantiles[99])) } else { // while GC has disabled elapsed := time.Now().Sub(startTime) @@ -156,3 +154,31 @@ func toH(bytes uint64) string { return fmt.Sprintf("%.2fG", float64(bytes)/1024/1024/1024) } } + +// short string format +func toS(d time.Duration) string { + + u := uint64(d) + if u < uint64(time.Second) { + switch { + case u == 0: + return "0" + case u < uint64(time.Microsecond): + return fmt.Sprintf("%.2fns", float64(u)) + case u < uint64(time.Millisecond): + return fmt.Sprintf("%.2fus", float64(u)/1000) + default: + return fmt.Sprintf("%.2fms", float64(u)/1000/1000) + } + } else { + switch { + case u < uint64(time.Minute): + return fmt.Sprintf("%.2fs", float64(u)/1000/1000/1000) + case u < uint64(time.Hour): + return fmt.Sprintf("%.2fm", float64(u)/1000/1000/1000/60) + default: + return fmt.Sprintf("%.2fh", float64(u)/1000/1000/1000/60/60) + } + } + +} diff --git a/adapter/toolbox/profile_test.go b/toolbox/profile_test.go similarity index 100% rename from adapter/toolbox/profile_test.go rename to toolbox/profile_test.go diff --git a/server/web/statistics.go b/toolbox/statistics.go similarity index 86% rename from server/web/statistics.go rename to toolbox/statistics.go index 98f85e96..fd73dfb3 100644 --- a/server/web/statistics.go +++ b/toolbox/statistics.go @@ -12,14 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package toolbox import ( "fmt" "sync" "time" - - "github.com/astaxie/beego/core/utils" ) // Statistics struct @@ -102,13 +100,13 @@ func (m *URLMap) GetMap() map[string]interface{} { fmt.Sprintf("% -10s", kk), fmt.Sprintf("% -16d", vv.RequestNum), fmt.Sprintf("%d", vv.TotalTime), - fmt.Sprintf("% -16s", utils.ToShortTimeFormat(vv.TotalTime)), + fmt.Sprintf("% -16s", toS(vv.TotalTime)), fmt.Sprintf("%d", vv.MaxTime), - fmt.Sprintf("% -16s", utils.ToShortTimeFormat(vv.MaxTime)), + fmt.Sprintf("% -16s", toS(vv.MaxTime)), fmt.Sprintf("%d", vv.MinTime), - fmt.Sprintf("% -16s", utils.ToShortTimeFormat(vv.MinTime)), + fmt.Sprintf("% -16s", toS(vv.MinTime)), fmt.Sprintf("%d", time.Duration(int64(vv.TotalTime)/vv.RequestNum)), - fmt.Sprintf("% -16s", utils.ToShortTimeFormat(time.Duration(int64(vv.TotalTime)/vv.RequestNum))), + fmt.Sprintf("% -16s", toS(time.Duration(int64(vv.TotalTime)/vv.RequestNum))), } resultLists = append(resultLists, result) } @@ -130,10 +128,10 @@ func (m *URLMap) GetMapData() []map[string]interface{} { "request_url": k, "method": kk, "times": vv.RequestNum, - "total_time": utils.ToShortTimeFormat(vv.TotalTime), - "max_time": utils.ToShortTimeFormat(vv.MaxTime), - "min_time": utils.ToShortTimeFormat(vv.MinTime), - "avg_time": utils.ToShortTimeFormat(time.Duration(int64(vv.TotalTime) / vv.RequestNum)), + "total_time": toS(vv.TotalTime), + "max_time": toS(vv.MaxTime), + "min_time": toS(vv.MinTime), + "avg_time": toS(time.Duration(int64(vv.TotalTime) / vv.RequestNum)), } resultLists = append(resultLists, result) } diff --git a/adapter/toolbox/statistics_test.go b/toolbox/statistics_test.go similarity index 100% rename from adapter/toolbox/statistics_test.go rename to toolbox/statistics_test.go diff --git a/task/task.go b/toolbox/task.go similarity index 74% rename from task/task.go rename to toolbox/task.go index 8f25a0f3..d2a94ba9 100644 --- a/task/task.go +++ b/toolbox/task.go @@ -12,10 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -package task +package toolbox import ( - "context" "log" "math" "sort" @@ -31,33 +30,18 @@ type bounds struct { names map[string]uint } -type taskManager struct { - adminTaskList map[string]Tasker +// The bounds for each field. +var ( + AdminTaskList map[string]Tasker taskLock sync.RWMutex stop chan bool changed chan bool - started bool -} - -func newTaskManager() *taskManager { - return &taskManager{ - adminTaskList: make(map[string]Tasker), - taskLock: sync.RWMutex{}, - stop: make(chan bool), - changed: make(chan bool), - started: false, - } -} - -// The bounds for each field. -var ( - globalTaskManager *taskManager - - seconds = bounds{0, 59, nil} - minutes = bounds{0, 59, nil} - hours = bounds{0, 23, nil} - days = bounds{1, 31, nil} - months = bounds{1, 12, map[string]uint{ + isstart bool + seconds = bounds{0, 59, nil} + minutes = bounds{0, 59, nil} + hours = bounds{0, 23, nil} + days = bounds{1, 31, nil} + months = bounds{1, 12, map[string]uint{ "jan": 1, "feb": 2, "mar": 3, @@ -98,17 +82,17 @@ type Schedule struct { } // TaskFunc task func type -type TaskFunc func(ctx context.Context) error +type TaskFunc func() error // Tasker task interface type Tasker interface { - GetSpec(ctx context.Context) string - GetStatus(ctx context.Context) string - Run(ctx context.Context) error - SetNext(context.Context, time.Time) - GetNext(ctx context.Context) time.Time - SetPrev(context.Context, time.Time) - GetPrev(ctx context.Context) time.Time + GetSpec() string + GetStatus() string + Run() error + SetNext(time.Time) + GetNext() time.Time + SetPrev(time.Time) + GetPrev() time.Time } // task error @@ -118,8 +102,6 @@ type taskerr struct { } // Task task struct -// It's not a thread-safe structure. -// Only nearest errors will be saved in ErrList type Task struct { Taskname string Spec *Schedule @@ -129,7 +111,6 @@ type Task struct { Next time.Time Errlist []*taskerr // like errtime:errinfo ErrLimit int // max length for the errlist, 0 stand for no limit - errCnt int // records the error count during the execution } // NewTask add new task with name, time and func @@ -138,23 +119,20 @@ func NewTask(tname string, spec string, f TaskFunc) *Task { task := &Task{ Taskname: tname, DoFunc: f, - // Make configurable ErrLimit: 100, SpecStr: spec, - // we only store the pointer, so it won't use too many space - Errlist: make([]*taskerr, 100, 100), } task.SetCron(spec) return task } // GetSpec get spec string -func (t *Task) GetSpec(context.Context) string { +func (t *Task) GetSpec() string { return t.SpecStr } // GetStatus get current task status -func (t *Task) GetStatus(context.Context) string { +func (t *Task) GetStatus() string { var str string for _, v := range t.Errlist { str += v.t.String() + ":" + v.errinfo + "
" @@ -163,33 +141,33 @@ func (t *Task) GetStatus(context.Context) string { } // Run run all tasks -func (t *Task) Run(ctx context.Context) error { - err := t.DoFunc(ctx) +func (t *Task) Run() error { + err := t.DoFunc() if err != nil { - index := t.errCnt % t.ErrLimit - t.Errlist[index] = &taskerr{t: t.Next, errinfo: err.Error()} - t.errCnt++ + if t.ErrLimit > 0 && t.ErrLimit > len(t.Errlist) { + t.Errlist = append(t.Errlist, &taskerr{t: t.Next, errinfo: err.Error()}) + } } return err } // SetNext set next time for this task -func (t *Task) SetNext(ctx context.Context, now time.Time) { +func (t *Task) SetNext(now time.Time) { t.Next = t.Spec.Next(now) } // GetNext get the next call time of this task -func (t *Task) GetNext(context.Context) time.Time { +func (t *Task) GetNext() time.Time { return t.Next } // SetPrev set prev time of this task -func (t *Task) SetPrev(ctx context.Context, now time.Time) { +func (t *Task) SetPrev(now time.Time) { t.Prev = now } // GetPrev get prev time of this task -func (t *Task) GetPrev(context.Context) time.Time { +func (t *Task) GetPrev() time.Time { return t.Prev } @@ -204,9 +182,9 @@ func (t *Task) GetPrev(context.Context) time.Time { // SetCron some signals: // *: any time // ,:  separate signal -//    -:duration +//   -:duration // /n : do as n times of time duration -// /////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// // 0/30 * * * * * every 30s // 0 43 21 * * * 21:43 // 0 15 05 * * *    05:15 @@ -413,153 +391,91 @@ func dayMatches(s *Schedule, t time.Time) bool { // StartTask start all tasks func StartTask() { - globalTaskManager.StartTask() -} - -// StopTask stop all tasks -func StopTask() { - globalTaskManager.StopTask() -} - -// AddTask add task with name -func AddTask(taskName string, t Tasker) { - globalTaskManager.AddTask(taskName, t) -} - -// DeleteTask delete task with name -func DeleteTask(taskName string) { - globalTaskManager.DeleteTask(taskName) -} - -// ClearTask clear all tasks -func ClearTask() { - globalTaskManager.ClearTask() -} - -// StartTask start all tasks -func (m *taskManager) StartTask() { - m.taskLock.Lock() - defer m.taskLock.Unlock() - if m.started { - // If already started, no need to start another goroutine. + taskLock.Lock() + defer taskLock.Unlock() + if isstart { + //If already started, no need to start another goroutine. return } - m.started = true - - registerCommands() - go m.run() + isstart = true + go run() } -func (m *taskManager) run() { +func run() { now := time.Now().Local() - for _, t := range m.adminTaskList { - t.SetNext(nil, now) + for _, t := range AdminTaskList { + t.SetNext(now) } for { // we only use RLock here because NewMapSorter copy the reference, do not change any thing - m.taskLock.RLock() - sortList := NewMapSorter(m.adminTaskList) - m.taskLock.RUnlock() + taskLock.RLock() + sortList := NewMapSorter(AdminTaskList) + taskLock.RUnlock() sortList.Sort() var effective time.Time - if len(m.adminTaskList) == 0 || sortList.Vals[0].GetNext(context.Background()).IsZero() { + if len(AdminTaskList) == 0 || sortList.Vals[0].GetNext().IsZero() { // If there are no entries yet, just sleep - it still handles new entries // and stop requests. effective = now.AddDate(10, 0, 0) } else { - effective = sortList.Vals[0].GetNext(context.Background()) + effective = sortList.Vals[0].GetNext() } select { case now = <-time.After(effective.Sub(now)): // Run every entry whose next time was this effective time. for _, e := range sortList.Vals { - if e.GetNext(context.Background()) != effective { + if e.GetNext() != effective { break } - go e.Run(nil) - e.SetPrev(context.Background(), e.GetNext(context.Background())) - e.SetNext(nil, effective) + go e.Run() + e.SetPrev(e.GetNext()) + e.SetNext(effective) } continue - case <-m.changed: + case <-changed: now = time.Now().Local() - m.taskLock.Lock() - for _, t := range m.adminTaskList { - t.SetNext(nil, now) + taskLock.Lock() + for _, t := range AdminTaskList { + t.SetNext(now) } - m.taskLock.Unlock() + taskLock.Unlock() continue - case <-m.stop: - m.taskLock.Lock() - if m.started { - m.started = false - } - m.taskLock.Unlock() + case <-stop: return } } } // StopTask stop all tasks -func (m *taskManager) StopTask() { - go func() { - m.stop <- true - }() +func StopTask() { + taskLock.Lock() + defer taskLock.Unlock() + if isstart { + isstart = false + stop <- true + } + } // AddTask add task with name -func (m *taskManager) AddTask(taskname string, t Tasker) { - isChanged := false - m.taskLock.Lock() - t.SetNext(nil, time.Now().Local()) - m.adminTaskList[taskname] = t - if m.started { - isChanged = true +func AddTask(taskname string, t Tasker) { + taskLock.Lock() + defer taskLock.Unlock() + t.SetNext(time.Now().Local()) + AdminTaskList[taskname] = t + if isstart { + changed <- true } - m.taskLock.Unlock() - - if isChanged { - go func() { - m.changed <- true - }() - } - } // DeleteTask delete task with name -func (m *taskManager) DeleteTask(taskname string) { - isChanged := false - - m.taskLock.Lock() - delete(m.adminTaskList, taskname) - if m.started { - isChanged = true - } - m.taskLock.Unlock() - - if isChanged { - go func() { - m.changed <- true - }() - } -} - -// ClearTask clear all tasks -func (m *taskManager) ClearTask() { - isChanged := false - - m.taskLock.Lock() - m.adminTaskList = make(map[string]Tasker) - if m.started { - isChanged = true - } - m.taskLock.Unlock() - - if isChanged { - go func() { - m.changed <- true - }() +func DeleteTask(taskname string) { + taskLock.Lock() + defer taskLock.Unlock() + delete(AdminTaskList, taskname) + if isstart { + changed <- true } } @@ -589,13 +505,13 @@ func (ms *MapSorter) Sort() { func (ms *MapSorter) Len() int { return len(ms.Keys) } func (ms *MapSorter) Less(i, j int) bool { - if ms.Vals[i].GetNext(context.Background()).IsZero() { + if ms.Vals[i].GetNext().IsZero() { return false } - if ms.Vals[j].GetNext(context.Background()).IsZero() { + if ms.Vals[j].GetNext().IsZero() { return true } - return ms.Vals[i].GetNext(context.Background()).Before(ms.Vals[j].GetNext(context.Background())) + return ms.Vals[i].GetNext().Before(ms.Vals[j].GetNext()) } func (ms *MapSorter) Swap(i, j int) { ms.Vals[i], ms.Vals[j] = ms.Vals[j], ms.Vals[i] @@ -712,5 +628,7 @@ func all(r bounds) uint64 { } func init() { - globalTaskManager = newTaskManager() + AdminTaskList = make(map[string]Tasker) + stop = make(chan bool) + changed = make(chan bool) } diff --git a/adapter/toolbox/task_test.go b/toolbox/task_test.go similarity index 97% rename from adapter/toolbox/task_test.go rename to toolbox/task_test.go index 994c4976..596bc9c5 100644 --- a/adapter/toolbox/task_test.go +++ b/toolbox/task_test.go @@ -22,8 +22,6 @@ import ( ) func TestParse(t *testing.T) { - defer ClearTask() - tk := NewTask("taska", "0/30 * * * * *", func() error { fmt.Println("hello world"); return nil }) err := tk.Run() if err != nil { @@ -36,8 +34,6 @@ func TestParse(t *testing.T) { } func TestSpec(t *testing.T) { - defer ClearTask() - wg := &sync.WaitGroup{} wg.Add(2) tk1 := NewTask("tk1", "0 12 * * * *", func() error { fmt.Println("tk1"); return nil }) diff --git a/server/web/tree.go b/tree.go similarity index 95% rename from server/web/tree.go rename to tree.go index fc5a11a2..9e53003b 100644 --- a/server/web/tree.go +++ b/tree.go @@ -12,16 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "path" "regexp" "strings" - "github.com/astaxie/beego/core/utils" - - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/context" + "github.com/astaxie/beego/utils" ) var ( @@ -33,13 +32,13 @@ var ( // wildcard stores params // leaves store the endpoint information type Tree struct { - // prefix set for static router + //prefix set for static router prefix string - // search fix route first + //search fix route first fixrouters []*Tree - // if set, failure to match fixrouters search then search wildcard + //if set, failure to match fixrouters search then search wildcard wildcard *Tree - // if set, failure to match wildcard search + //if set, failure to match wildcard search leaves []*leafInfo } @@ -69,13 +68,13 @@ func (t *Tree) addtree(segments []string, tree *Tree, wildcards []string, reg st filterTreeWithPrefix(tree, wildcards, reg) } } - // Rule: /login/*/access match /login/2009/11/access - // if already has *, and when loop the access, should as a regexpStr + //Rule: /login/*/access match /login/2009/11/access + //if already has *, and when loop the access, should as a regexpStr if !iswild && utils.InSlice(":splat", wildcards) { iswild = true regexpStr = seg } - // Rule: /user/:id/* + //Rule: /user/:id/* if seg == "*" && len(wildcards) > 0 && reg == "" { regexpStr = "(.+)" } @@ -222,13 +221,13 @@ func (t *Tree) addseg(segments []string, route interface{}, wildcards []string, t.addseg(segments[1:], route, wildcards, reg) params = params[1:] } - // Rule: /login/*/access match /login/2009/11/access - // if already has *, and when loop the access, should as a regexpStr + //Rule: /login/*/access match /login/2009/11/access + //if already has *, and when loop the access, should as a regexpStr if !iswild && utils.InSlice(":splat", wildcards) { iswild = true regexpStr = seg } - // Rule: /user/:id/* + //Rule: /user/:id/* if seg == "*" && len(wildcards) > 0 && reg == "" { regexpStr = "(.+)" } @@ -393,7 +392,7 @@ type leafInfo struct { } func (leaf *leafInfo) match(treePattern string, wildcardValues []string, ctx *context.Context) (ok bool) { - // fmt.Println("Leaf:", wildcardValues, leaf.wildcards, leaf.regexps) + //fmt.Println("Leaf:", wildcardValues, leaf.wildcards, leaf.regexps) if leaf.regexps == nil { if len(wildcardValues) == 0 && len(leaf.wildcards) == 0 { // static path return true @@ -500,7 +499,7 @@ func splitSegment(key string) (bool, []string, string) { continue } if start { - // :id:int and :name:string + //:id:int and :name:string if v == ':' { if len(key) >= i+4 { if key[i+1:i+4] == "int" { diff --git a/server/web/tree_test.go b/tree_test.go similarity index 99% rename from server/web/tree_test.go rename to tree_test.go index e72bc1f9..d412a348 100644 --- a/server/web/tree_test.go +++ b/tree_test.go @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "strings" "testing" - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/context" ) type testinfo struct { diff --git a/server/web/unregroute_test.go b/unregroute_test.go similarity index 99% rename from server/web/unregroute_test.go rename to unregroute_test.go index c675ae7d..08b1b77b 100644 --- a/server/web/unregroute_test.go +++ b/unregroute_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package web +package beego import ( "net/http" diff --git a/core/utils/caller.go b/utils/caller.go similarity index 100% rename from core/utils/caller.go rename to utils/caller.go diff --git a/adapter/utils/caller_test.go b/utils/caller_test.go similarity index 100% rename from adapter/utils/caller_test.go rename to utils/caller_test.go diff --git a/adapter/utils/captcha/LICENSE b/utils/captcha/LICENSE similarity index 100% rename from adapter/utils/captcha/LICENSE rename to utils/captcha/LICENSE diff --git a/adapter/utils/captcha/README.md b/utils/captcha/README.md similarity index 100% rename from adapter/utils/captcha/README.md rename to utils/captcha/README.md diff --git a/server/web/captcha/captcha.go b/utils/captcha/captcha.go similarity index 82% rename from server/web/captcha/captcha.go rename to utils/captcha/captcha.go index 8ce832f7..42ac70d3 100644 --- a/server/web/captcha/captcha.go +++ b/utils/captcha/captcha.go @@ -59,7 +59,6 @@ package captcha import ( - context2 "context" "fmt" "html/template" "net/http" @@ -67,11 +66,11 @@ import ( "strings" "time" - "github.com/astaxie/beego/core/logs" - - "github.com/astaxie/beego/core/utils" - "github.com/astaxie/beego/server/web" - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego" + "github.com/astaxie/beego/cache" + "github.com/astaxie/beego/context" + "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/utils" ) var ( @@ -91,7 +90,7 @@ const ( // Captcha struct type Captcha struct { // beego cache store - store Storage + store cache.Cache // url prefix for captcha image URLPrefix string @@ -138,15 +137,14 @@ func (c *Captcha) Handler(ctx *context.Context) { if len(ctx.Input.Query("reload")) > 0 { chars = c.genRandChars() - if err := c.store.Put(context2.Background(), key, chars, c.Expiration); err != nil { + if err := c.store.Put(key, chars, c.Expiration); err != nil { ctx.Output.SetStatus(500) ctx.WriteString("captcha reload error") logs.Error("Reload Create Captcha Error:", err) return } } else { - val, _ := c.store.Get(context2.Background(), key) - if v, ok := val.([]byte); ok { + if v, ok := c.store.Get(key).([]byte); ok { chars = v } else { ctx.Output.SetStatus(404) @@ -185,7 +183,7 @@ func (c *Captcha) CreateCaptcha() (string, error) { chars := c.genRandChars() // save to store - if err := c.store.Put(context2.Background(), c.key(id), chars, c.Expiration); err != nil { + if err := c.store.Put(c.key(id), chars, c.Expiration); err != nil { return "", err } @@ -207,8 +205,8 @@ func (c *Captcha) Verify(id string, challenge string) (success bool) { var chars []byte key := c.key(id) - val, _ := c.store.Get(context2.Background(), key) - if v, ok := val.([]byte); ok { + + if v, ok := c.store.Get(key).([]byte); ok { chars = v } else { return @@ -216,7 +214,7 @@ func (c *Captcha) Verify(id string, challenge string) (success bool) { defer func() { // finally remove it - c.store.Delete(context2.Background(), key) + c.store.Delete(key) }() if len(chars) != len(challenge) { @@ -233,7 +231,7 @@ func (c *Captcha) Verify(id string, challenge string) (success bool) { } // NewCaptcha create a new captcha.Captcha -func NewCaptcha(urlPrefix string, store Storage) *Captcha { +func NewCaptcha(urlPrefix string, store cache.Cache) *Captcha { cpt := &Captcha{} cpt.store = store cpt.FieldIDName = fieldIDName @@ -259,23 +257,14 @@ func NewCaptcha(urlPrefix string, store Storage) *Captcha { // NewWithFilter create a new captcha.Captcha and auto AddFilter for serve captacha image // and add a template func for output html -func NewWithFilter(urlPrefix string, store Storage) *Captcha { +func NewWithFilter(urlPrefix string, store cache.Cache) *Captcha { cpt := NewCaptcha(urlPrefix, store) // create filter for serve captcha image - web.InsertFilter(cpt.URLPrefix+"*", web.BeforeRouter, cpt.Handler) + beego.InsertFilter(cpt.URLPrefix+"*", beego.BeforeRouter, cpt.Handler) // add to template func map - web.AddFuncMap("create_captcha", cpt.CreateCaptchaHTML) + beego.AddFuncMap("create_captcha", cpt.CreateCaptchaHTML) return cpt } - -type Storage interface { - // Get a cached value by key. - Get(ctx context2.Context, key string) (interface{}, error) - // Set a cached value with key and expire time. - Put(ctx context2.Context, key string, val interface{}, timeout time.Duration) error - // Delete cached value by key. - Delete(ctx context2.Context, key string) error -} diff --git a/server/web/captcha/image.go b/utils/captcha/image.go similarity index 100% rename from server/web/captcha/image.go rename to utils/captcha/image.go diff --git a/server/web/captcha/image_test.go b/utils/captcha/image_test.go similarity index 97% rename from server/web/captcha/image_test.go rename to utils/captcha/image_test.go index 4b4518a1..5e35b7f7 100644 --- a/server/web/captcha/image_test.go +++ b/utils/captcha/image_test.go @@ -17,7 +17,7 @@ package captcha import ( "testing" - "github.com/astaxie/beego/core/utils" + "github.com/astaxie/beego/utils" ) type byteCounter struct { diff --git a/server/web/captcha/siprng.go b/utils/captcha/siprng.go similarity index 100% rename from server/web/captcha/siprng.go rename to utils/captcha/siprng.go diff --git a/server/web/captcha/siprng_test.go b/utils/captcha/siprng_test.go similarity index 100% rename from server/web/captcha/siprng_test.go rename to utils/captcha/siprng_test.go diff --git a/core/utils/debug.go b/utils/debug.go similarity index 100% rename from core/utils/debug.go rename to utils/debug.go diff --git a/adapter/utils/debug_test.go b/utils/debug_test.go similarity index 100% rename from adapter/utils/debug_test.go rename to utils/debug_test.go diff --git a/core/utils/file.go b/utils/file.go similarity index 100% rename from core/utils/file.go rename to utils/file.go diff --git a/core/utils/file_test.go b/utils/file_test.go similarity index 100% rename from core/utils/file_test.go rename to utils/file_test.go diff --git a/core/utils/mail.go b/utils/mail.go similarity index 100% rename from core/utils/mail.go rename to utils/mail.go diff --git a/adapter/utils/mail_test.go b/utils/mail_test.go similarity index 100% rename from adapter/utils/mail_test.go rename to utils/mail_test.go diff --git a/server/web/pagination/controller.go b/utils/pagination/controller.go similarity index 81% rename from server/web/pagination/controller.go rename to utils/pagination/controller.go index f6b2f73d..2f022d0c 100644 --- a/server/web/pagination/controller.go +++ b/utils/pagination/controller.go @@ -15,13 +15,12 @@ package pagination import ( - "github.com/astaxie/beego/core/utils/pagination" - "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/context" ) // SetPaginator Instantiates a Paginator and assigns it to context.Input.Data("paginator"). -func SetPaginator(context *context.Context, per int, nums int64) (paginator *pagination.Paginator) { - paginator = pagination.NewPaginator(context.Request, per, nums) +func SetPaginator(context *context.Context, per int, nums int64) (paginator *Paginator) { + paginator = NewPaginator(context.Request, per, nums) context.Input.SetData("paginator", &paginator) return } diff --git a/adapter/utils/pagination/doc.go b/utils/pagination/doc.go similarity index 100% rename from adapter/utils/pagination/doc.go rename to utils/pagination/doc.go diff --git a/core/utils/pagination/paginator.go b/utils/pagination/paginator.go similarity index 100% rename from core/utils/pagination/paginator.go rename to utils/pagination/paginator.go diff --git a/core/utils/pagination/utils.go b/utils/pagination/utils.go similarity index 100% rename from core/utils/pagination/utils.go rename to utils/pagination/utils.go diff --git a/core/utils/rand.go b/utils/rand.go similarity index 100% rename from core/utils/rand.go rename to utils/rand.go diff --git a/adapter/utils/rand_test.go b/utils/rand_test.go similarity index 100% rename from adapter/utils/rand_test.go rename to utils/rand_test.go diff --git a/core/utils/safemap.go b/utils/safemap.go similarity index 100% rename from core/utils/safemap.go rename to utils/safemap.go diff --git a/adapter/utils/safemap_test.go b/utils/safemap_test.go similarity index 100% rename from adapter/utils/safemap_test.go rename to utils/safemap_test.go diff --git a/core/utils/slice.go b/utils/slice.go similarity index 100% rename from core/utils/slice.go rename to utils/slice.go diff --git a/adapter/utils/slice_test.go b/utils/slice_test.go similarity index 100% rename from adapter/utils/slice_test.go rename to utils/slice_test.go diff --git a/core/utils/testdata/grepe.test b/utils/testdata/grepe.test similarity index 100% rename from core/utils/testdata/grepe.test rename to utils/testdata/grepe.test diff --git a/core/utils/utils.go b/utils/utils.go similarity index 100% rename from core/utils/utils.go rename to utils/utils.go diff --git a/core/utils/utils_test.go b/utils/utils_test.go similarity index 100% rename from core/utils/utils_test.go rename to utils/utils_test.go diff --git a/core/validation/README.md b/validation/README.md similarity index 100% rename from core/validation/README.md rename to validation/README.md diff --git a/core/validation/util.go b/validation/util.go similarity index 99% rename from core/validation/util.go rename to validation/util.go index 918b206c..82206f4f 100644 --- a/core/validation/util.go +++ b/validation/util.go @@ -213,7 +213,7 @@ func parseFunc(vfunc, key string, label string) (v ValidFunc, err error) { return } - tParams, err := trim(name, key+"."+name+"."+label, params) + tParams, err := trim(name, key+"."+ name + "." + label, params) if err != nil { return } diff --git a/core/validation/util_test.go b/validation/util_test.go similarity index 100% rename from core/validation/util_test.go rename to validation/util_test.go diff --git a/core/validation/validation.go b/validation/validation.go similarity index 99% rename from core/validation/validation.go rename to validation/validation.go index 134e750e..190e0f0e 100644 --- a/core/validation/validation.go +++ b/validation/validation.go @@ -269,11 +269,6 @@ func (v *Validation) apply(chk Validator, obj interface{}) *Result { Field := "" Label := "" parts := strings.Split(key, ".") - if len(parts) == 2 { - Field = parts[0] - Name = parts[1] - Label = Field - } if len(parts) == 3 { Field = parts[0] Name = parts[1] diff --git a/adapter/validation/validation_test.go b/validation/validation_test.go similarity index 100% rename from adapter/validation/validation_test.go rename to validation/validation_test.go diff --git a/core/validation/validators.go b/validation/validators.go similarity index 99% rename from core/validation/validators.go rename to validation/validators.go index ec422d86..38b6f1aa 100644 --- a/core/validation/validators.go +++ b/validation/validators.go @@ -16,14 +16,13 @@ package validation import ( "fmt" + "github.com/astaxie/beego/logs" "reflect" "regexp" "strings" "sync" "time" "unicode/utf8" - - "github.com/astaxie/beego/core/logs" ) // CanSkipFuncs will skip valid if RequiredFirst is true and the struct field's value is empty From debd68cbe4f8e8ccf8743bccc25fe7ca1fa41bc7 Mon Sep 17 00:00:00 2001 From: Ming Deng Date: Sat, 12 Dec 2020 21:28:58 +0800 Subject: [PATCH 2/2] Revert "Merge pull request #4325 from flycash/revert1" This reverts commit fad897346f286303a6c4a2cb432ea44058e470cd, reversing changes made to e284b0ddae072311617a6fdd8393eb04aba873d2. --- .github/workflows/stale.yml | 19 + .gitignore | 6 + .travis.yml | 62 +- CONTRIBUTING.md | 45 +- README.md | 218 ++++- adapter/admin.go | 45 ++ adapter/app.go | 262 ++++++ adapter/beego.go | 77 ++ adapter/build_info.go | 27 + {cache => adapter/cache}/cache.go | 0 adapter/cache/cache_adapter.go | 117 +++ {cache => adapter/cache}/cache_test.go | 0 adapter/cache/conv.go | 44 + {cache => adapter/cache}/conv_test.go | 0 adapter/cache/file.go | 30 + adapter/cache/memcache/memcache.go | 44 + .../cache}/memcache/memcache_test.go | 18 +- adapter/cache/memory.go | 28 + adapter/cache/redis/redis.go | 49 ++ {cache => adapter/cache}/redis/redis_test.go | 27 +- adapter/cache/ssdb/ssdb.go | 15 + {cache => adapter/cache}/ssdb/ssdb_test.go | 11 +- adapter/config.go | 177 +++++ adapter/config/adapter.go | 191 +++++ adapter/config/config.go | 151 ++++ {config => adapter/config}/config_test.go | 0 adapter/config/env/env.go | 50 ++ {config => adapter/config}/env/env_test.go | 0 adapter/config/fake.go | 25 + {config => adapter/config}/ini_test.go | 0 .../assertions.go => adapter/config/json.go | 6 +- {config => adapter/config}/json_test.go | 0 adapter/config/xml/xml.go | 34 + {config => adapter/config}/xml/xml_test.go | 2 +- adapter/config/yaml/yaml.go | 34 + {config => adapter/config}/yaml/yaml_test.go | 2 +- adapter/context/acceptencoder.go | 45 ++ adapter/context/context.go | 146 ++++ adapter/context/input.go | 282 +++++++ adapter/context/output.go | 154 ++++ adapter/context/renderer.go | 8 + {context => adapter/context}/response.go | 7 +- adapter/controller.go | 399 ++++++++++ adapter/doc.go | 16 + adapter/error.go | 202 +++++ filter.go => adapter/filter.go | 24 +- adapter/flash.go | 63 ++ adapter/fs.go | 35 + adapter/grace/grace.go | 94 +++ adapter/grace/server.go | 48 ++ adapter/httplib/httplib.go | 300 +++++++ {httplib => adapter/httplib}/httplib_test.go | 33 +- log.go => adapter/log.go | 22 +- adapter/logs/accesslog.go | 27 + adapter/logs/alils/alils.go | 5 + adapter/logs/es/es.go | 5 + adapter/logs/log.go | 347 ++++++++ adapter/logs/log_adapter.go | 69 ++ adapter/logs/logger.go | 38 + adapter/logs/logger_test.go | 24 + {metric => adapter/metric}/prometheus.go | 17 +- {metric => adapter/metric}/prometheus_test.go | 2 +- adapter/migration/ddl.go | 198 +++++ {migration => adapter/migration}/doc.go | 0 adapter/migration/migration.go | 111 +++ adapter/namespace.go | 378 +++++++++ adapter/orm/cmd.go | 28 + adapter/orm/db.go | 24 + adapter/orm/db_alias.go | 121 +++ adapter/orm/models.go | 25 + adapter/orm/models_boot.go | 40 + adapter/orm/models_fields.go | 625 +++++++++++++++ adapter/orm/orm.go | 314 ++++++++ adapter/orm/orm_conds.go | 83 ++ adapter/orm/orm_log.go | 32 + adapter/orm/orm_queryset.go | 32 + adapter/orm/qb.go | 27 + adapter/orm/qb_mysql.go | 150 ++++ {orm => adapter/orm}/qb_tidb.go | 89 +-- adapter/orm/query_setter_adapter.go | 34 + adapter/orm/types.go | 150 ++++ adapter/orm/utils.go | 286 +++++++ {orm => adapter/orm}/utils_test.go | 0 adapter/plugins/apiauth/apiauth.go | 94 +++ .../plugins}/apiauth/apiauth_test.go | 0 adapter/plugins/auth/basic.go | 81 ++ adapter/plugins/authz/authz.go | 80 ++ .../plugins}/authz/authz_model.conf | 0 .../plugins}/authz/authz_policy.csv | 0 .../plugins}/authz/authz_test.go | 9 +- adapter/plugins/cors/cors.go | 71 ++ adapter/policy.go | 57 ++ adapter/router.go | 282 +++++++ adapter/session/couchbase/sess_couchbase.go | 118 +++ adapter/session/ledis/ledis_session.go | 86 ++ adapter/session/memcache/sess_memcache.go | 118 +++ adapter/session/mysql/sess_mysql.go | 135 ++++ adapter/session/postgres/sess_postgresql.go | 139 ++++ adapter/session/provider_adapter.go | 104 +++ adapter/session/redis/sess_redis.go | 121 +++ .../session/redis_cluster/redis_cluster.go | 120 +++ .../redis_sentinel/sess_redis_sentinel.go | 121 +++ .../sess_redis_sentinel_test.go | 4 +- adapter/session/sess_cookie.go | 114 +++ .../session}/sess_cookie_test.go | 0 adapter/session/sess_file.go | 106 +++ adapter/session/sess_file_test.go | 336 ++++++++ adapter/session/sess_mem.go | 106 +++ {session => adapter/session}/sess_mem_test.go | 0 adapter/session/sess_test.go | 51 ++ adapter/session/sess_utils.go | 29 + adapter/session/session.go | 166 ++++ adapter/session/ssdb/sess_ssdb.go | 84 ++ adapter/session/store_adapter.go | 84 ++ adapter/swagger/swagger.go | 68 ++ adapter/template.go | 108 +++ adapter/templatefunc.go | 151 ++++ adapter/templatefunc_test.go | 304 +++++++ adapter/testing/client.go | 50 ++ adapter/toolbox/healthcheck.go | 52 ++ adapter/toolbox/profile.go | 50 ++ {toolbox => adapter/toolbox}/profile_test.go | 0 adapter/toolbox/statistics.go | 50 ++ .../toolbox}/statistics_test.go | 0 adapter/toolbox/task.go | 291 +++++++ {toolbox => adapter/toolbox}/task_test.go | 4 + adapter/tree.go | 49 ++ adapter/tree_test.go | 249 ++++++ logs/conn_test.go => adapter/utils/caller.go | 11 +- {utils => adapter/utils}/caller_test.go | 0 {utils => adapter/utils}/captcha/LICENSE | 0 {utils => adapter/utils}/captcha/README.md | 0 adapter/utils/captcha/captcha.go | 124 +++ adapter/utils/captcha/image.go | 35 + adapter/utils/captcha/image_test.go | 58 ++ adapter/utils/debug.go | 34 + {utils => adapter/utils}/debug_test.go | 0 adapter/utils/file.go | 47 ++ adapter/utils/mail.go | 63 ++ {utils => adapter/utils}/mail_test.go | 0 adapter/utils/pagination/controller.go | 26 + {utils => adapter/utils}/pagination/doc.go | 0 adapter/utils/pagination/paginator.go | 112 +++ adapter/utils/rand.go | 24 + {utils => adapter/utils}/rand_test.go | 0 adapter/utils/safemap.go | 58 ++ {utils => adapter/utils}/safemap_test.go | 0 adapter/utils/slice.go | 101 +++ {utils => adapter/utils}/slice_test.go | 0 adapter/utils/utils.go | 10 + adapter/validation/util.go | 62 ++ adapter/validation/validation.go | 274 +++++++ .../validation}/validation_test.go | 0 adapter/validation/validators.go | 512 ++++++++++++ admin.go | 420 ---------- admin_test.go | 77 -- app.go | 496 ------------ build_info.go | 13 +- {cache => client/cache}/README.md | 0 client/cache/cache.go | 104 +++ client/cache/cache_test.go | 193 +++++ {cache => client/cache}/conv.go | 10 +- client/cache/conv_test.go | 143 ++++ {cache => client/cache}/file.go | 101 ++- {cache => client/cache}/memcache/memcache.go | 65 +- client/cache/memcache/memcache_test.go | 121 +++ {cache => client/cache}/memory.go | 88 +- {cache => client/cache}/redis/redis.go | 69 +- client/cache/redis/redis_test.go | 163 ++++ {cache => client/cache}/ssdb/ssdb.go | 71 +- client/cache/ssdb/ssdb_test.go | 118 +++ {httplib => client/httplib}/README.md | 0 client/httplib/filter.go | 24 + client/httplib/filter/opentracing/filter.go | 71 ++ .../httplib/filter/opentracing/filter_test.go | 42 + client/httplib/filter/prometheus/filter.go | 77 ++ .../httplib/filter/prometheus/filter_test.go | 41 + {httplib => client/httplib}/httplib.go | 107 ++- client/httplib/httplib_test.go | 302 +++++++ {testing => client/httplib/testing}/client.go | 13 +- {orm => client/orm}/README.md | 0 {orm => client/orm}/cmd.go | 44 +- client/orm/cmd_utils.go | 171 ++++ {orm => client/orm}/db.go | 99 ++- {orm => client/orm}/db_alias.go | 301 ++++--- client/orm/db_alias_test.go | 86 ++ {orm => client/orm}/db_mysql.go | 55 +- {orm => client/orm}/db_oracle.go | 69 +- {orm => client/orm}/db_postgres.go | 47 +- {orm => client/orm}/db_sqlite.go | 58 +- {orm => client/orm}/db_tables.go | 9 + {orm => client/orm}/db_tidb.go | 0 {orm => client/orm}/db_utils.go | 0 client/orm/do_nothing_orm.go | 180 +++++ client/orm/do_nothing_orm_test.go | 134 ++++ client/orm/filter.go | 40 + .../orm/filter/bean/default_value_filter.go | 137 ++++ .../filter/bean/default_value_filter_test.go | 72 ++ client/orm/filter/opentracing/filter.go | 71 ++ client/orm/filter/opentracing/filter_test.go | 44 + client/orm/filter/prometheus/filter.go | 85 ++ client/orm/filter/prometheus/filter_test.go | 62 ++ client/orm/filter_orm_decorator.go | 514 ++++++++++++ client/orm/filter_orm_decorator_test.go | 434 ++++++++++ client/orm/filter_test.go | 32 + client/orm/hints/db_hints.go | 103 +++ client/orm/hints/db_hints_test.go | 127 +++ client/orm/invocation.go | 58 ++ {migration => client/orm/migration}/ddl.go | 54 +- client/orm/migration/doc.go | 32 + .../orm/migration}/migration.go | 4 +- client/orm/model_utils_test.go | 62 ++ client/orm/models.go | 569 +++++++++++++ client/orm/models_boot.go | 40 + {orm => client/orm}/models_fields.go | 0 {orm => client/orm}/models_info_f.go | 15 +- {orm => client/orm}/models_info_m.go | 2 +- {orm => client/orm}/models_test.go | 184 +++-- {orm => client/orm}/models_utils.go | 13 + client/orm/models_utils_test.go | 35 + {orm => client/orm}/orm.go | 364 +++++---- {orm => client/orm}/orm_conds.go | 0 {orm => client/orm}/orm_log.go | 27 +- {orm => client/orm}/orm_object.go | 4 +- {orm => client/orm}/orm_querym2m.go | 2 +- {orm => client/orm}/orm_queryset.go | 33 +- {orm => client/orm}/orm_raw.go | 72 +- {orm => client/orm}/orm_test.go | 316 ++++++-- {orm => client/orm}/qb.go | 2 +- {orm => client/orm}/qb_mysql.go | 58 +- client/orm/qb_postgres.go | 221 ++++++ client/orm/qb_tidb.go | 21 + {orm => client/orm}/types.go | 252 ++++-- {orm => client/orm}/utils.go | 0 client/orm/utils_test.go | 70 ++ core/bean/context.go | 20 + core/bean/doc.go | 17 + core/bean/factory.go | 25 + core/bean/metadata.go | 28 + core/bean/tag_auto_wire_bean_factory.go | 231 ++++++ core/bean/tag_auto_wire_bean_factory_test.go | 75 ++ core/bean/time_type_adapter.go | 35 + core/bean/time_type_adapter_test.go | 29 + core/bean/type_adapter.go | 26 + core/config/base_config_test.go | 72 ++ {config => core/config}/config.go | 146 +++- core/config/config_test.go | 55 ++ {config => core/config}/env/env.go | 4 +- core/config/env/env_test.go | 75 ++ core/config/error.go | 25 + core/config/etcd/config.go | 195 +++++ core/config/etcd/config_test.go | 117 +++ {config => core/config}/fake.go | 56 +- core/config/global.go | 103 +++ core/config/global_test.go | 104 +++ {config => core/config}/ini.go | 90 ++- core/config/ini_test.go | 191 +++++ {config => core/config/json}/json.go | 101 ++- core/config/json/json_test.go | 251 ++++++ core/config/toml/toml.go | 357 +++++++++ core/config/toml/toml_test.go | 379 +++++++++ {config => core/config}/xml/xml.go | 113 ++- core/config/xml/xml_test.go | 157 ++++ {config => core/config}/yaml/yaml.go | 128 ++- core/config/yaml/yaml_test.go | 151 ++++ core/governor/command.go | 87 ++ {toolbox => core/governor}/healthcheck.go | 4 +- {toolbox => core/governor}/profile.go | 42 +- core/governor/profile_test.go | 28 + {logs => core/logs}/README.md | 0 logs/accesslog.go => core/logs/access_log.go | 18 +- core/logs/access_log_test.go | 38 + {logs => core/logs}/alils/alils.go | 64 +- {logs => core/logs}/alils/config.go | 4 +- {logs => core/logs}/alils/log.pb.go | 53 +- {logs => core/logs}/alils/log_config.go | 6 +- {logs => core/logs}/alils/log_project.go | 2 +- {logs => core/logs}/alils/log_store.go | 6 +- {logs => core/logs}/alils/machine_group.go | 10 +- {logs => core/logs}/alils/request.go | 0 {logs => core/logs}/alils/signature.go | 0 {logs => core/logs}/conn.go | 51 +- core/logs/conn_test.go | 97 +++ {logs => core/logs}/console.go | 66 +- {logs => core/logs}/console_test.go | 18 + {logs => core/logs}/es/es.go | 66 +- core/logs/es/index.go | 39 + core/logs/es/index_test.go | 34 + {logs => core/logs}/file.go | 100 ++- {logs => core/logs}/file_test.go | 57 +- core/logs/formatter.go | 89 +++ core/logs/formatter_test.go | 95 +++ {logs => core/logs}/jianliao.go | 45 +- core/logs/jianliao_test.go | 36 + {logs => core/logs}/log.go | 242 ++++-- core/logs/log_msg.go | 55 ++ core/logs/log_msg_test.go | 44 + core/logs/log_test.go | 27 + {logs => core/logs}/logger.go | 7 +- {logs => core/logs}/logger_test.go | 0 {logs => core/logs}/multifile.go | 34 +- {logs => core/logs}/multifile_test.go | 0 core/logs/slack.go | 82 ++ {logs => core/logs}/smtp.go | 43 +- {logs => core/logs}/smtp_test.go | 0 {utils => core/utils}/caller.go | 0 core/utils/caller_test.go | 28 + {utils => core/utils}/debug.go | 0 core/utils/debug_test.go | 46 ++ {utils => core/utils}/file.go | 0 {utils => core/utils}/file_test.go | 0 core/utils/kv.go | 87 ++ core/utils/kv_test.go | 38 + {utils => core/utils}/mail.go | 0 core/utils/mail_test.go | 41 + core/utils/pagination/doc.go | 58 ++ {utils => core/utils}/pagination/paginator.go | 0 {utils => core/utils}/pagination/utils.go | 0 {utils => core/utils}/rand.go | 0 core/utils/rand_test.go | 33 + {utils => core/utils}/safemap.go | 0 core/utils/safemap_test.go | 89 +++ {utils => core/utils}/slice.go | 0 core/utils/slice_test.go | 29 + {utils => core/utils}/testdata/grepe.test | 0 core/utils/time.go | 48 ++ {utils => core/utils}/utils.go | 0 {utils => core/utils}/utils_test.go | 0 {validation => core/validation}/README.md | 0 {validation => core/validation}/util.go | 2 +- {validation => core/validation}/util_test.go | 0 {validation => core/validation}/validation.go | 5 + core/validation/validation_test.go | 634 +++++++++++++++ {validation => core/validation}/validators.go | 3 +- doc.go | 28 +- go.mod | 27 +- go.sum | 144 +++- logs/slack.go | 60 -- orm/cmd_utils.go | 320 -------- orm/models.go | 99 --- orm/models_boot.go | 347 -------- scripts/gobuild.sh | 112 --- scripts/report_build_info.sh | 52 -- server/web/LICENSE | 13 + server/web/admin.go | 126 +++ server/web/admin_controller.go | 297 +++++++ server/web/admin_test.go | 249 ++++++ adminui.go => server/web/adminui.go | 4 +- beego.go => server/web/beego.go | 72 +- server/web/captcha/LICENSE | 19 + server/web/captcha/README.md | 45 ++ {utils => server/web}/captcha/captcha.go | 43 +- {utils => server/web}/captcha/image.go | 0 {utils => server/web}/captcha/image_test.go | 2 +- {utils => server/web}/captcha/siprng.go | 0 {utils => server/web}/captcha/siprng_test.go | 0 config.go => server/web/config.go | 149 ++-- config_test.go => server/web/config_test.go | 10 +- .../web/context}/acceptencoder.go | 30 +- .../web/context}/acceptencoder_test.go | 0 {context => server/web/context}/context.go | 51 +- .../web/context}/context_test.go | 0 {context => server/web/context}/input.go | 60 +- {context => server/web/context}/input_test.go | 10 + {context => server/web/context}/output.go | 52 +- {context => server/web/context}/param/conv.go | 4 +- .../web/context}/param/methodparams.go | 4 +- .../web/context}/param/options.go | 0 .../web/context}/param/parsers.go | 0 .../web/context}/param/parsers_test.go | 8 +- {context => server/web/context}/renderer.go | 2 +- server/web/context/response.go | 26 + controller.go => server/web/controller.go | 19 +- .../web/controller_test.go | 16 +- server/web/doc.go | 17 + error.go => server/web/error.go | 32 +- error_test.go => server/web/error_test.go | 2 +- server/web/filter.go | 134 ++++ .../web/filter}/apiauth/apiauth.go | 37 +- server/web/filter/apiauth/apiauth_test.go | 20 + {plugins => server/web/filter}/auth/basic.go | 8 +- {plugins => server/web/filter}/authz/authz.go | 10 +- server/web/filter/authz/authz_model.conf | 14 + server/web/filter/authz/authz_policy.csv | 7 + server/web/filter/authz/authz_test.go | 109 +++ {plugins => server/web/filter}/cors/cors.go | 6 +- .../web/filter}/cors/cors_test.go | 38 +- server/web/filter/opentracing/filter.go | 86 ++ server/web/filter/opentracing/filter_test.go | 47 ++ server/web/filter/prometheus/filter.go | 87 ++ server/web/filter/prometheus/filter_test.go | 40 + server/web/filter_chain_test.go | 48 ++ filter_test.go => server/web/filter_test.go | 4 +- flash.go => server/web/flash.go | 2 +- flash_test.go => server/web/flash_test.go | 2 +- fs.go => server/web/fs.go | 2 +- {grace => server/web/grace}/grace.go | 0 {grace => server/web/grace}/server.go | 4 +- hooks.go => server/web/hooks.go | 30 +- mime.go => server/web/mime.go | 2 +- namespace.go => server/web/namespace.go | 6 +- .../web/namespace_test.go | 4 +- .../web}/pagination/controller.go | 7 +- parser.go => server/web/parser.go | 53 +- policy.go => server/web/policy.go | 4 +- router.go => server/web/router.go | 332 ++++---- router_test.go => server/web/router_test.go | 85 +- server/web/server.go | 751 ++++++++++++++++++ server/web/server_test.go | 30 + {session => server/web/session}/README.md | 2 +- .../web/session}/couchbase/sess_couchbase.go | 69 +- .../session/couchbase/sess_couchbase_test.go | 43 + .../web/session}/ledis/ledis_session.go | 78 +- .../web/session/ledis/ledis_session_test.go | 41 + .../web/session}/memcache/sess_memcache.go | 35 +- .../web/session}/mysql/sess_mysql.go | 37 +- .../web/session}/postgres/sess_postgresql.go | 37 +- .../web/session}/redis/sess_redis.go | 187 +++-- server/web/session/redis/sess_redis_test.go | 112 +++ .../session}/redis_cluster/redis_cluster.go | 167 ++-- .../redis_cluster/redis_cluster_test.go | 35 + .../redis_sentinel/sess_redis_sentinel.go | 178 +++-- .../sess_redis_sentinel_test.go | 106 +++ .../web/session}/sess_cookie.go | 31 +- server/web/session/sess_cookie_test.go | 105 +++ {session => server/web/session}/sess_file.go | 36 +- server/web/session/sess_file_test.go | 427 ++++++++++ {session => server/web/session}/sess_mem.go | 37 +- server/web/session/sess_mem_test.go | 58 ++ {session => server/web/session}/sess_test.go | 0 {session => server/web/session}/sess_utils.go | 2 +- {session => server/web/session}/session.go | 53 +- .../web/session}/ssdb/sess_ssdb.go | 68 +- server/web/session/ssdb/sess_ssdb_test.go | 41 + staticfile.go => server/web/staticfile.go | 11 +- .../web/staticfile_test.go | 2 +- {toolbox => server/web}/statistics.go | 20 +- server/web/statistics_test.go | 40 + {swagger => server/web/swagger}/swagger.go | 0 template.go => server/web/template.go | 14 +- .../web/template_test.go | 41 +- templatefunc.go => server/web/templatefunc.go | 16 +- .../web/templatefunc_test.go | 2 +- tree.go => server/web/tree.go | 31 +- tree_test.go => server/web/tree_test.go | 4 +- .../web/unregroute_test.go | 2 +- task/govenor_command.go | 92 +++ task/governor_command_test.go | 111 +++ {toolbox => task}/task.go | 250 ++++-- task/task_test.go | 117 +++ {testdata => test}/Makefile | 0 {testdata => test}/bindata.go | 5 +- {testdata => test}/views/blocks/block.tpl | 0 {testdata => test}/views/header.tpl | 0 {testdata => test}/views/index.tpl | 0 455 files changed, 30003 insertions(+), 4667 deletions(-) create mode 100644 .github/workflows/stale.yml create mode 100644 adapter/admin.go create mode 100644 adapter/app.go create mode 100644 adapter/beego.go create mode 100644 adapter/build_info.go rename {cache => adapter/cache}/cache.go (100%) create mode 100644 adapter/cache/cache_adapter.go rename {cache => adapter/cache}/cache_test.go (100%) create mode 100644 adapter/cache/conv.go rename {cache => adapter/cache}/conv_test.go (100%) create mode 100644 adapter/cache/file.go create mode 100644 adapter/cache/memcache/memcache.go rename {cache => adapter/cache}/memcache/memcache_test.go (90%) create mode 100644 adapter/cache/memory.go create mode 100644 adapter/cache/redis/redis.go rename {cache => adapter/cache}/redis/redis_test.go (86%) create mode 100644 adapter/cache/ssdb/ssdb.go rename {cache => adapter/cache}/ssdb/ssdb_test.go (90%) create mode 100644 adapter/config.go create mode 100644 adapter/config/adapter.go create mode 100644 adapter/config/config.go rename {config => adapter/config}/config_test.go (100%) create mode 100644 adapter/config/env/env.go rename {config => adapter/config}/env/env_test.go (100%) create mode 100644 adapter/config/fake.go rename {config => adapter/config}/ini_test.go (100%) rename testing/assertions.go => adapter/config/json.go (89%) rename {config => adapter/config}/json_test.go (100%) create mode 100644 adapter/config/xml/xml.go rename {config => adapter/config}/xml/xml_test.go (98%) create mode 100644 adapter/config/yaml/yaml.go rename {config => adapter/config}/yaml/yaml_test.go (98%) create mode 100644 adapter/context/acceptencoder.go create mode 100644 adapter/context/context.go create mode 100644 adapter/context/input.go create mode 100644 adapter/context/output.go create mode 100644 adapter/context/renderer.go rename {context => adapter/context}/response.go (83%) create mode 100644 adapter/controller.go create mode 100644 adapter/doc.go create mode 100644 adapter/error.go rename filter.go => adapter/filter.go (79%) create mode 100644 adapter/flash.go create mode 100644 adapter/fs.go create mode 100644 adapter/grace/grace.go create mode 100644 adapter/grace/server.go create mode 100644 adapter/httplib/httplib.go rename {httplib => adapter/httplib}/httplib_test.go (86%) rename log.go => adapter/log.go (88%) create mode 100644 adapter/logs/accesslog.go create mode 100644 adapter/logs/alils/alils.go create mode 100644 adapter/logs/es/es.go create mode 100644 adapter/logs/log.go create mode 100644 adapter/logs/log_adapter.go create mode 100644 adapter/logs/logger.go create mode 100644 adapter/logs/logger_test.go rename {metric => adapter/metric}/prometheus.go (87%) rename {metric => adapter/metric}/prometheus_test.go (96%) create mode 100644 adapter/migration/ddl.go rename {migration => adapter/migration}/doc.go (100%) create mode 100644 adapter/migration/migration.go create mode 100644 adapter/namespace.go create mode 100644 adapter/orm/cmd.go create mode 100644 adapter/orm/db.go create mode 100644 adapter/orm/db_alias.go create mode 100644 adapter/orm/models.go create mode 100644 adapter/orm/models_boot.go create mode 100644 adapter/orm/models_fields.go create mode 100644 adapter/orm/orm.go create mode 100644 adapter/orm/orm_conds.go create mode 100644 adapter/orm/orm_log.go create mode 100644 adapter/orm/orm_queryset.go create mode 100644 adapter/orm/qb.go create mode 100644 adapter/orm/qb_mysql.go rename {orm => adapter/orm}/qb_tidb.go (60%) create mode 100644 adapter/orm/query_setter_adapter.go create mode 100644 adapter/orm/types.go create mode 100644 adapter/orm/utils.go rename {orm => adapter/orm}/utils_test.go (100%) create mode 100644 adapter/plugins/apiauth/apiauth.go rename {plugins => adapter/plugins}/apiauth/apiauth_test.go (100%) create mode 100644 adapter/plugins/auth/basic.go create mode 100644 adapter/plugins/authz/authz.go rename {plugins => adapter/plugins}/authz/authz_model.conf (100%) rename {plugins => adapter/plugins}/authz/authz_policy.csv (100%) rename {plugins => adapter/plugins}/authz/authz_test.go (96%) create mode 100644 adapter/plugins/cors/cors.go create mode 100644 adapter/policy.go create mode 100644 adapter/router.go create mode 100644 adapter/session/couchbase/sess_couchbase.go create mode 100644 adapter/session/ledis/ledis_session.go create mode 100644 adapter/session/memcache/sess_memcache.go create mode 100644 adapter/session/mysql/sess_mysql.go create mode 100644 adapter/session/postgres/sess_postgresql.go create mode 100644 adapter/session/provider_adapter.go create mode 100644 adapter/session/redis/sess_redis.go create mode 100644 adapter/session/redis_cluster/redis_cluster.go create mode 100644 adapter/session/redis_sentinel/sess_redis_sentinel.go rename {session => adapter/session}/redis_sentinel/sess_redis_sentinel_test.go (96%) create mode 100644 adapter/session/sess_cookie.go rename {session => adapter/session}/sess_cookie_test.go (100%) create mode 100644 adapter/session/sess_file.go create mode 100644 adapter/session/sess_file_test.go create mode 100644 adapter/session/sess_mem.go rename {session => adapter/session}/sess_mem_test.go (100%) create mode 100644 adapter/session/sess_test.go create mode 100644 adapter/session/sess_utils.go create mode 100644 adapter/session/session.go create mode 100644 adapter/session/ssdb/sess_ssdb.go create mode 100644 adapter/session/store_adapter.go create mode 100644 adapter/swagger/swagger.go create mode 100644 adapter/template.go create mode 100644 adapter/templatefunc.go create mode 100644 adapter/templatefunc_test.go create mode 100644 adapter/testing/client.go create mode 100644 adapter/toolbox/healthcheck.go create mode 100644 adapter/toolbox/profile.go rename {toolbox => adapter/toolbox}/profile_test.go (100%) create mode 100644 adapter/toolbox/statistics.go rename {toolbox => adapter/toolbox}/statistics_test.go (100%) create mode 100644 adapter/toolbox/task.go rename {toolbox => adapter/toolbox}/task_test.go (97%) create mode 100644 adapter/tree.go create mode 100644 adapter/tree_test.go rename logs/conn_test.go => adapter/utils/caller.go (78%) rename {utils => adapter/utils}/caller_test.go (100%) rename {utils => adapter/utils}/captcha/LICENSE (100%) rename {utils => adapter/utils}/captcha/README.md (100%) create mode 100644 adapter/utils/captcha/captcha.go create mode 100644 adapter/utils/captcha/image.go create mode 100644 adapter/utils/captcha/image_test.go create mode 100644 adapter/utils/debug.go rename {utils => adapter/utils}/debug_test.go (100%) create mode 100644 adapter/utils/file.go create mode 100644 adapter/utils/mail.go rename {utils => adapter/utils}/mail_test.go (100%) create mode 100644 adapter/utils/pagination/controller.go rename {utils => adapter/utils}/pagination/doc.go (100%) create mode 100644 adapter/utils/pagination/paginator.go create mode 100644 adapter/utils/rand.go rename {utils => adapter/utils}/rand_test.go (100%) create mode 100644 adapter/utils/safemap.go rename {utils => adapter/utils}/safemap_test.go (100%) create mode 100644 adapter/utils/slice.go rename {utils => adapter/utils}/slice_test.go (100%) create mode 100644 adapter/utils/utils.go create mode 100644 adapter/validation/util.go create mode 100644 adapter/validation/validation.go rename {validation => adapter/validation}/validation_test.go (100%) create mode 100644 adapter/validation/validators.go delete mode 100644 admin.go delete mode 100644 admin_test.go delete mode 100644 app.go rename {cache => client/cache}/README.md (100%) create mode 100644 client/cache/cache.go create mode 100644 client/cache/cache_test.go rename {cache => client/cache}/conv.go (90%) create mode 100644 client/cache/conv_test.go rename {cache => client/cache}/file.go (68%) rename {cache => client/cache}/memcache/memcache.go (71%) create mode 100644 client/cache/memcache/memcache_test.go rename {cache => client/cache}/memory.go (65%) rename {cache => client/cache}/redis/redis.go (75%) create mode 100644 client/cache/redis/redis_test.go rename {cache => client/cache}/ssdb/ssdb.go (70%) create mode 100644 client/cache/ssdb/ssdb_test.go rename {httplib => client/httplib}/README.md (100%) create mode 100644 client/httplib/filter.go create mode 100644 client/httplib/filter/opentracing/filter.go create mode 100644 client/httplib/filter/opentracing/filter_test.go create mode 100644 client/httplib/filter/prometheus/filter.go create mode 100644 client/httplib/filter/prometheus/filter_test.go rename {httplib => client/httplib}/httplib.go (85%) create mode 100644 client/httplib/httplib_test.go rename {testing => client/httplib/testing}/client.go (88%) rename {orm => client/orm}/README.md (100%) rename {orm => client/orm}/cmd.go (85%) create mode 100644 client/orm/cmd_utils.go rename {orm => client/orm}/db.go (94%) rename {orm => client/orm}/db_alias.go (62%) create mode 100644 client/orm/db_alias_test.go rename {orm => client/orm}/db_mysql.go (79%) rename {orm => client/orm}/db_oracle.go (67%) rename {orm => client/orm}/db_postgres.go (78%) rename {orm => client/orm}/db_sqlite.go (73%) rename {orm => client/orm}/db_tables.go (97%) rename {orm => client/orm}/db_tidb.go (100%) rename {orm => client/orm}/db_utils.go (100%) create mode 100644 client/orm/do_nothing_orm.go create mode 100644 client/orm/do_nothing_orm_test.go create mode 100644 client/orm/filter.go create mode 100644 client/orm/filter/bean/default_value_filter.go create mode 100644 client/orm/filter/bean/default_value_filter_test.go create mode 100644 client/orm/filter/opentracing/filter.go create mode 100644 client/orm/filter/opentracing/filter_test.go create mode 100644 client/orm/filter/prometheus/filter.go create mode 100644 client/orm/filter/prometheus/filter_test.go create mode 100644 client/orm/filter_orm_decorator.go create mode 100644 client/orm/filter_orm_decorator_test.go create mode 100644 client/orm/filter_test.go create mode 100644 client/orm/hints/db_hints.go create mode 100644 client/orm/hints/db_hints_test.go create mode 100644 client/orm/invocation.go rename {migration => client/orm/migration}/ddl.go (85%) create mode 100644 client/orm/migration/doc.go rename {migration => client/orm/migration}/migration.go (99%) create mode 100644 client/orm/model_utils_test.go create mode 100644 client/orm/models.go create mode 100644 client/orm/models_boot.go rename {orm => client/orm}/models_fields.go (100%) rename {orm => client/orm}/models_info_f.go (97%) rename {orm => client/orm}/models_info_m.go (98%) rename {orm => client/orm}/models_test.go (66%) rename {orm => client/orm}/models_utils.go (93%) create mode 100644 client/orm/models_utils_test.go rename {orm => client/orm}/orm.go (57%) rename {orm => client/orm}/orm_conds.go (100%) rename {orm => client/orm}/orm_log.go (88%) rename {orm => client/orm}/orm_object.go (96%) rename {orm => client/orm}/orm_querym2m.go (97%) rename {orm => client/orm}/orm_queryset.go (91%) rename {orm => client/orm}/orm_raw.go (91%) rename {orm => client/orm}/orm_test.go (89%) rename {orm => client/orm}/qb.go (96%) rename {orm => client/orm}/qb_mysql.go (71%) create mode 100644 client/orm/qb_postgres.go create mode 100644 client/orm/qb_tidb.go rename {orm => client/orm}/types.go (77%) rename {orm => client/orm}/utils.go (100%) create mode 100644 client/orm/utils_test.go create mode 100644 core/bean/context.go create mode 100644 core/bean/doc.go create mode 100644 core/bean/factory.go create mode 100644 core/bean/metadata.go create mode 100644 core/bean/tag_auto_wire_bean_factory.go create mode 100644 core/bean/tag_auto_wire_bean_factory_test.go create mode 100644 core/bean/time_type_adapter.go create mode 100644 core/bean/time_type_adapter_test.go create mode 100644 core/bean/type_adapter.go create mode 100644 core/config/base_config_test.go rename {config => core/config}/config.go (65%) create mode 100644 core/config/config_test.go rename {config => core/config}/env/env.go (96%) create mode 100644 core/config/env/env_test.go create mode 100644 core/config/error.go create mode 100644 core/config/etcd/config.go create mode 100644 core/config/etcd/config_test.go rename {config => core/config}/fake.go (71%) create mode 100644 core/config/global.go create mode 100644 core/config/global_test.go rename {config => core/config}/ini.go (85%) create mode 100644 core/config/ini_test.go rename {config => core/config/json}/json.go (71%) create mode 100644 core/config/json/json_test.go create mode 100644 core/config/toml/toml.go create mode 100644 core/config/toml/toml_test.go rename {config => core/config}/xml/xml.go (66%) create mode 100644 core/config/xml/xml_test.go rename {config => core/config}/yaml/yaml.go (71%) create mode 100644 core/config/yaml/yaml_test.go create mode 100644 core/governor/command.go rename {toolbox => core/governor}/healthcheck.go (96%) rename {toolbox => core/governor}/profile.go (82%) create mode 100644 core/governor/profile_test.go rename {logs => core/logs}/README.md (100%) rename logs/accesslog.go => core/logs/access_log.go (89%) create mode 100644 core/logs/access_log_test.go rename {logs => core/logs}/alils/alils.go (69%) rename {logs => core/logs}/alils/config.go (61%) rename {logs => core/logs}/alils/log.pb.go (95%) rename {logs => core/logs}/alils/log_config.go (91%) rename {logs => core/logs}/alils/log_project.go (99%) rename {logs => core/logs}/alils/log_store.go (98%) rename {logs => core/logs}/alils/machine_group.go (88%) rename {logs => core/logs}/alils/request.go (100%) rename {logs => core/logs}/alils/signature.go (100%) rename {logs => core/logs}/conn.go (65%) create mode 100644 core/logs/conn_test.go rename {logs => core/logs}/console.go (57%) rename {logs => core/logs}/console_test.go (78%) rename {logs => core/logs}/es/es.go (56%) create mode 100644 core/logs/es/index.go create mode 100644 core/logs/es/index_test.go rename {logs => core/logs}/file.go (80%) rename {logs => core/logs}/file_test.go (89%) create mode 100644 core/logs/formatter.go create mode 100644 core/logs/formatter_test.go rename {logs => core/logs}/jianliao.go (56%) create mode 100644 core/logs/jianliao_test.go rename {logs => core/logs}/log.go (78%) create mode 100644 core/logs/log_msg.go create mode 100644 core/logs/log_msg_test.go create mode 100644 core/logs/log_test.go rename {logs => core/logs}/logger.go (96%) rename {logs => core/logs}/logger_test.go (100%) rename {logs => core/logs}/multifile.go (83%) rename {logs => core/logs}/multifile_test.go (100%) create mode 100644 core/logs/slack.go rename {logs => core/logs}/smtp.go (77%) rename {logs => core/logs}/smtp_test.go (100%) rename {utils => core/utils}/caller.go (100%) create mode 100644 core/utils/caller_test.go rename {utils => core/utils}/debug.go (100%) create mode 100644 core/utils/debug_test.go rename {utils => core/utils}/file.go (100%) rename {utils => core/utils}/file_test.go (100%) create mode 100644 core/utils/kv.go create mode 100644 core/utils/kv_test.go rename {utils => core/utils}/mail.go (100%) create mode 100644 core/utils/mail_test.go create mode 100644 core/utils/pagination/doc.go rename {utils => core/utils}/pagination/paginator.go (100%) rename {utils => core/utils}/pagination/utils.go (100%) rename {utils => core/utils}/rand.go (100%) create mode 100644 core/utils/rand_test.go rename {utils => core/utils}/safemap.go (100%) create mode 100644 core/utils/safemap_test.go rename {utils => core/utils}/slice.go (100%) create mode 100644 core/utils/slice_test.go rename {utils => core/utils}/testdata/grepe.test (100%) create mode 100644 core/utils/time.go rename {utils => core/utils}/utils.go (100%) rename {utils => core/utils}/utils_test.go (100%) rename {validation => core/validation}/README.md (100%) rename {validation => core/validation}/util.go (99%) rename {validation => core/validation}/util_test.go (100%) rename {validation => core/validation}/validation.go (99%) create mode 100644 core/validation/validation_test.go rename {validation => core/validation}/validators.go (99%) delete mode 100644 logs/slack.go delete mode 100644 orm/cmd_utils.go delete mode 100644 orm/models.go delete mode 100644 orm/models_boot.go delete mode 100755 scripts/gobuild.sh delete mode 100755 scripts/report_build_info.sh create mode 100644 server/web/LICENSE create mode 100644 server/web/admin.go create mode 100644 server/web/admin_controller.go create mode 100644 server/web/admin_test.go rename adminui.go => server/web/adminui.go (99%) rename beego.go => server/web/beego.go (65%) create mode 100644 server/web/captcha/LICENSE create mode 100644 server/web/captcha/README.md rename {utils => server/web}/captcha/captcha.go (82%) rename {utils => server/web}/captcha/image.go (100%) rename {utils => server/web}/captcha/image_test.go (97%) rename {utils => server/web}/captcha/siprng.go (100%) rename {utils => server/web}/captcha/siprng_test.go (100%) rename config.go => server/web/config.go (78%) rename config_test.go => server/web/config_test.go (95%) rename {context => server/web/context}/acceptencoder.go (86%) rename {context => server/web/context}/acceptencoder_test.go (100%) rename {context => server/web/context}/context.go (80%) rename {context => server/web/context}/context_test.go (100%) rename {context => server/web/context}/input.go (92%) rename {context => server/web/context}/input_test.go (95%) rename {context => server/web/context}/output.go (88%) rename {context => server/web/context}/param/conv.go (95%) rename {context => server/web/context}/param/methodparams.go (91%) rename {context => server/web/context}/param/options.go (100%) rename {context => server/web/context}/param/parsers.go (100%) rename {context => server/web/context}/param/parsers_test.go (98%) rename {context => server/web/context}/renderer.go (77%) create mode 100644 server/web/context/response.go rename controller.go => server/web/controller.go (98%) rename controller_test.go => server/web/controller_test.go (94%) create mode 100644 server/web/doc.go rename error.go => server/web/error.go (94%) rename error_test.go => server/web/error_test.go (99%) create mode 100644 server/web/filter.go rename {plugins => server/web/filter}/apiauth/apiauth.go (77%) create mode 100644 server/web/filter/apiauth/apiauth_test.go rename {plugins => server/web/filter}/auth/basic.go (94%) rename {plugins => server/web/filter}/authz/authz.go (94%) create mode 100644 server/web/filter/authz/authz_model.conf create mode 100644 server/web/filter/authz/authz_policy.csv create mode 100644 server/web/filter/authz/authz_test.go rename {plugins => server/web/filter}/cors/cors.go (98%) rename {plugins => server/web/filter}/cors/cors_test.go (88%) create mode 100644 server/web/filter/opentracing/filter.go create mode 100644 server/web/filter/opentracing/filter_test.go create mode 100644 server/web/filter/prometheus/filter.go create mode 100644 server/web/filter/prometheus/filter_test.go create mode 100644 server/web/filter_chain_test.go rename filter_test.go => server/web/filter_test.go (97%) rename flash.go => server/web/flash.go (99%) rename flash_test.go => server/web/flash_test.go (99%) rename fs.go => server/web/fs.go (99%) rename {grace => server/web/grace}/grace.go (100%) rename {grace => server/web/grace}/server.go (98%) rename hooks.go => server/web/hooks.go (84%) rename mime.go => server/web/mime.go (99%) rename namespace.go => server/web/namespace.go (98%) rename namespace_test.go => server/web/namespace_test.go (98%) rename {utils => server/web}/pagination/controller.go (81%) rename parser.go => server/web/parser.go (93%) rename policy.go => server/web/policy.go (97%) rename router.go => server/web/router.go (79%) rename router_test.go => server/web/router_test.go (88%) create mode 100644 server/web/server.go create mode 100644 server/web/server_test.go rename {session => server/web/session}/README.md (98%) rename {session => server/web/session}/couchbase/sess_couchbase.go (69%) create mode 100644 server/web/session/couchbase/sess_couchbase_test.go rename {session => server/web/session}/ledis/ledis_session.go (59%) create mode 100644 server/web/session/ledis/ledis_session_test.go rename {session => server/web/session}/memcache/sess_memcache.go (81%) rename {session => server/web/session}/mysql/sess_mysql.go (82%) rename {session => server/web/session}/postgres/sess_postgresql.go (83%) rename {session => server/web/session}/redis/sess_redis.go (50%) create mode 100644 server/web/session/redis/sess_redis_test.go rename {session => server/web/session}/redis_cluster/redis_cluster.go (55%) create mode 100644 server/web/session/redis_cluster/redis_cluster_test.go rename {session => server/web/session}/redis_sentinel/sess_redis_sentinel.go (57%) create mode 100644 server/web/session/redis_sentinel/sess_redis_sentinel_test.go rename {session => server/web/session}/sess_cookie.go (77%) create mode 100644 server/web/session/sess_cookie_test.go rename {session => server/web/session}/sess_file.go (85%) create mode 100644 server/web/session/sess_file_test.go rename {session => server/web/session}/sess_mem.go (78%) create mode 100644 server/web/session/sess_mem_test.go rename {session => server/web/session}/sess_test.go (100%) rename {session => server/web/session}/sess_utils.go (99%) rename {session => server/web/session}/session.go (86%) rename {session => server/web/session}/ssdb/sess_ssdb.go (66%) create mode 100644 server/web/session/ssdb/sess_ssdb_test.go rename staticfile.go => server/web/staticfile.go (96%) rename staticfile_test.go => server/web/staticfile_test.go (99%) rename {toolbox => server/web}/statistics.go (86%) create mode 100644 server/web/statistics_test.go rename {swagger => server/web/swagger}/swagger.go (100%) rename template.go => server/web/template.go (97%) rename template_test.go => server/web/template_test.go (88%) rename templatefunc.go => server/web/templatefunc.go (98%) rename templatefunc_test.go => server/web/templatefunc_test.go (99%) rename tree.go => server/web/tree.go (95%) rename tree_test.go => server/web/tree_test.go (99%) rename unregroute_test.go => server/web/unregroute_test.go (99%) create mode 100644 task/govenor_command.go create mode 100644 task/governor_command_test.go rename {toolbox => task}/task.go (74%) create mode 100644 task/task_test.go rename {testdata => test}/Makefile (100%) rename {testdata => test}/bindata.go (99%) rename {testdata => test}/views/blocks/block.tpl (100%) rename {testdata => test}/views/header.tpl (100%) rename {testdata => test}/views/index.tpl (100%) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..3a4d2e9a --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,19 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "30 1 * * *" + +jobs: + stale: + + runs-on: ubuntu-latest + + steps: + - uses: actions/stale@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'This issue is inactive for a long time.' + stale-pr-message: 'This PR is inactive for a long time' + stale-issue-label: 'inactive-issue' + stale-pr-label: 'inactive-pr' \ No newline at end of file diff --git a/.gitignore b/.gitignore index e1b65291..304c4b73 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,9 @@ *.swp *.swo beego.iml + +_beeTmp/ +_beeTmp2/ +pkg/_beeTmp/ +pkg/_beeTmp2/ +test/tmp/ diff --git a/.travis.yml b/.travis.yml index c019c999..973b40ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,30 +1,59 @@ language: go go: - - "1.13.x" + - "1.14.x" services: - redis-server - mysql - postgresql - memcached + - docker env: global: - GO_REPO_FULLNAME="github.com/astaxie/beego" matrix: - ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db - ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable" + - ORM_DRIVER=mysql export ORM_SOURCE="root:@/orm_test?charset=utf8" before_install: - # link the local repo with ${GOPATH}/src// - - GO_REPO_NAMESPACE=${GO_REPO_FULLNAME%/*} - # relies on GOPATH to contain only one directory... - - mkdir -p ${GOPATH}/src/${GO_REPO_NAMESPACE} - - ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH}/src/${GO_REPO_FULLNAME} - - cd ${GOPATH}/src/${GO_REPO_FULLNAME} - # get and build ssdb - - git clone git://github.com/ideawu/ssdb.git - - cd ssdb - - make - - cd .. + # link the local repo with ${GOPATH}/src// + - GO_REPO_NAMESPACE=${GO_REPO_FULLNAME%/*} + # relies on GOPATH to contain only one directory... + - mkdir -p ${GOPATH}/src/${GO_REPO_NAMESPACE} + - ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH}/src/${GO_REPO_FULLNAME} + - cd ${GOPATH}/src/${GO_REPO_FULLNAME} + # get and build ssdb + - git clone git://github.com/ideawu/ssdb.git + - cd ssdb + - make + - cd .. + # - prepare etcd + # - prepare for etcd unit tests + - rm -rf /tmp/etcd-data.tmp + - mkdir -p /tmp/etcd-data.tmp + - docker rmi gcr.io/etcd-development/etcd:v3.3.25 || true && + docker run -d + -p 2379:2379 + -p 2380:2380 + --mount type=bind,source=/tmp/etcd-data.tmp,destination=/etcd-data + --name etcd-gcr-v3.3.25 + gcr.io/etcd-development/etcd:v3.3.25 + /usr/local/bin/etcd + --name s1 + --data-dir /etcd-data + --listen-client-urls http://0.0.0.0:2379 + --advertise-client-urls http://0.0.0.0:2379 + --listen-peer-urls http://0.0.0.0:2380 + --initial-advertise-peer-urls http://0.0.0.0:2380 + --initial-cluster s1=http://0.0.0.0:2380 + --initial-cluster-token tkn + --initial-cluster-state new + - docker exec etcd-gcr-v3.3.25 /bin/sh -c "ETCDCTL_API=3 /usr/local/bin/etcdctl put current.float 1.23" + - docker exec etcd-gcr-v3.3.25 /bin/sh -c "ETCDCTL_API=3 /usr/local/bin/etcdctl put current.bool true" + - docker exec etcd-gcr-v3.3.25 /bin/sh -c "ETCDCTL_API=3 /usr/local/bin/etcdctl put current.int 11" + - docker exec etcd-gcr-v3.3.25 /bin/sh -c "ETCDCTL_API=3 /usr/local/bin/etcdctl put current.string hello" + - docker exec etcd-gcr-v3.3.25 /bin/sh -c "ETCDCTL_API=3 /usr/local/bin/etcdctl put current.serialize.name test" + - docker exec etcd-gcr-v3.3.25 /bin/sh -c "ETCDCTL_API=3 /usr/local/bin/etcdctl put sub.sub.key1 sub.sub.key" install: - go get github.com/lib/pq - go get github.com/go-sql-driver/mysql @@ -51,7 +80,10 @@ install: - go get -u golang.org/x/lint/golint - go get -u github.com/go-redis/redis before_script: + + # - - psql --version + # - prepare for orm unit tests - sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi" - sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi" - sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi" @@ -63,11 +95,11 @@ after_script: - killall -w ssdb-server - rm -rf ./res/var/* script: - - go test -v ./... - - staticcheck -show-ignored -checks "-ST1017,-U1000,-ST1005,-S1034,-S1012,-SA4006,-SA6005,-SA1019,-SA1024" + - go test ./... + - staticcheck -show-ignored -checks "-ST1017,-U1000,-ST1005,-S1034,-S1012,-SA4006,-SA6005,-SA1019,-SA1024" ./ - unconvert $(go list ./... | grep -v /vendor/) - ineffassign . - find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s - golint ./... addons: - postgresql: "9.6" + postgresql: "9.6" \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9d511616..cb279cbb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,17 +7,58 @@ It is the work of hundreds of contributors. We appreciate your help! Here are instructions to get you started. They are probably not perfect, please let us know if anything feels wrong or incomplete. +## Prepare environment + +Firstly, install some tools. Execute those commands **outside** the project. Or those command will modify go.mod file. + +```shell script +go get -u golang.org/x/tools/cmd/goimports + +go get -u github.com/gordonklaus/ineffassign +``` + +Put those lines into your pre-commit githook script: +```shell script +goimports -w -format-only ./ + +ineffassign . + +staticcheck -show-ignored -checks "-ST1017,-U1000,-ST1005,-S1034,-S1012,-SA4006,-SA6005,-SA1019,-SA1024" ./ +``` + +## Prepare middleware + +Beego uses many middlewares, including MySQL, Redis, SSDB and so on. + +We provide docker compose file to start all middlewares. + +You can run: +```shell script +docker-compose -f scripts/test_docker_compose.yml up -d +``` +Unit tests read addresses from environment, here is an example: +```shell script +export ORM_DRIVER=mysql +export ORM_SOURCE="beego:test@tcp(192.168.0.105:13306)/orm_test?charset=utf8" +export MEMCACHE_ADDR="192.168.0.105:11211" +export REDIS_ADDR="192.168.0.105:6379" +export SSDB_ADDR="192.168.0.105:8888" +``` + + ## Contribution guidelines ### Pull requests First of all. beego follow the gitflow. So please send you pull request -to **develop** branch. We will close the pull request to master branch. +to **develop-2** branch. We will close the pull request to master branch. We are always happy to receive pull requests, and do our best to review them as fast as possible. Not sure if that typo is worth a pull request? Do it! We will appreciate it. +Don't forget to rebase your commits! + If your pull request is not accepted on the first try, don't be discouraged! Sometimes we can make a mistake, please do more explaining for us. We will appreciate it. @@ -48,5 +89,5 @@ documenting your bug report or improvement proposal. If it does, it never hurts to add a quick "+1" or "I have this problem too". This will help prioritize the most common problems and requests. -Also if you don't know how to use it. please make sure you have read though +Also, if you don't know how to use it. please make sure you have read through the docs in http://beego.me/docs \ No newline at end of file diff --git a/README.md b/README.md index 3b414c6f..934fc429 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,21 @@ It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific feature ## Quick Start +###### Please see [Documentation](http://beego.me/docs) for more. + +###### [beego-example](https://github.com/beego-dev/beego-example) + +### Web Application + +#### Create `hello` directory, cd `hello` directory + + mkdir hello + cd hello + +#### Init module + + go mod init + #### Download and install go get github.com/astaxie/beego @@ -16,10 +31,10 @@ It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific feature ```go package main -import "github.com/astaxie/beego" +import "github.com/astaxie/beego/server/web" func main(){ - beego.Run() + web.Run() } ``` #### Build and run @@ -31,9 +46,204 @@ func main(){ Congratulations! You've just built your first **beego** app. -###### Please see [Documentation](http://beego.me/docs) for more. +### Using ORM module + +```go + +package main + +import ( + "github.com/astaxie/beego/client/orm" + "github.com/astaxie/beego/core/logs" + _ "github.com/go-sql-driver/mysql" +) + +// User - +type User struct { + ID int `orm:"column(id)"` + Name string `orm:"column(name)"` +} + +func init() { + // need to register models in init + orm.RegisterModel(new(User)) + + // need to register db driver + orm.RegisterDriver("mysql", orm.DRMySQL) + + // need to register default database + orm.RegisterDataBase("default", "mysql", "beego:test@tcp(192.168.0.105:13306)/orm_test?charset=utf8") +} + +func main() { + // automatically build table + orm.RunSyncdb("default", false, true) + + // create orm object, and it will use `default` database + o := orm.NewOrm() + + // data + user := new(User) + user.Name = "mike" + + // insert data + id, err := o.Insert(user) + if err != nil { + logs.Info(err) + } + + // ... +} +``` + +### Using httplib as http client +```go +package main + +import ( + "github.com/astaxie/beego/client/httplib" + "github.com/astaxie/beego/core/logs" +) + +func main() { + // Get, more methods please read docs + req := httplib.Get("http://beego.me/") + str, err := req.String() + if err != nil { + logs.Error(err) + } + logs.Info(str) +} + +``` + +### Using config module + +```go +package main + +import ( + "context" + + "github.com/astaxie/beego/core/config" + "github.com/astaxie/beego/core/logs" +) + +var ( + ConfigFile = "./app.conf" +) + +func main() { + cfg, err := config.NewConfig("ini", ConfigFile) + if err != nil { + logs.Critical("An error occurred:", err) + panic(err) + } + res, _ := cfg.String(context.Background(), "name") + logs.Info("load config name is", res) +} +``` +### Using logs module +```go +package main + +import ( + "github.com/astaxie/beego/core/logs" +) + +func main() { + err := logs.SetLogger(logs.AdapterFile, `{"filename":"project.log","level":7,"maxlines":0,"maxsize":0,"daily":true,"maxdays":10,"color":true}`) + if err != nil { + panic(err) + } + logs.Info("hello beego") +} +``` +### Using timed task + +```go +package main + +import ( + "context" + "time" + + "github.com/astaxie/beego/core/logs" + "github.com/astaxie/beego/task" +) + +func main() { + // create a task + tk1 := task.NewTask("tk1", "0/3 * * * * *", func(ctx context.Context) error { logs.Info("tk1"); return nil }) + + // check task + err := tk1.Run(context.Background()) + if err != nil { + logs.Error(err) + } + + // add task to global todolist + task.AddTask("tk1", tk1) + + // start tasks + task.StartTask() + + // wait 12 second + time.Sleep(12 * time.Second) + defer task.StopTask() +} +``` + +### Using cache module + +```go +package main + +import ( + "context" + "time" + + "github.com/astaxie/beego/client/cache" + + // don't forget this + _ "github.com/astaxie/beego/client/cache/redis" + + "github.com/astaxie/beego/core/logs" +) + +func main() { + // create cache + bm, err := cache.NewCache("redis", `{"key":"default", "conn":":6379", "password":"123456", "dbNum":"0"}`) + if err != nil { + logs.Error(err) + } + + // put + isPut := bm.Put(context.Background(), "astaxie", 1, time.Second*10) + logs.Info(isPut) + + isPut = bm.Put(context.Background(), "hello", "world", time.Second*10) + logs.Info(isPut) + + // get + result, _ := bm.Get(context.Background(),"astaxie") + logs.Info(string(result.([]byte))) + + multiResult, _ := bm.GetMulti(context.Background(), []string{"astaxie", "hello"}) + for i := range multiResult { + logs.Info(string(multiResult[i].([]byte))) + } + + // isExist + isExist, _ := bm.IsExist(context.Background(), "astaxie") + logs.Info(isExist) + + // delete + isDelete := bm.Delete(context.Background(), "astaxie") + logs.Info(isDelete) +} +``` -###### [beego-example](https://github.com/beego-dev/beego-example) ## Features diff --git a/adapter/admin.go b/adapter/admin.go new file mode 100644 index 00000000..e555f59e --- /dev/null +++ b/adapter/admin.go @@ -0,0 +1,45 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 adapter + +import ( + "time" + + _ "github.com/astaxie/beego/core/governor" + "github.com/astaxie/beego/server/web" +) + +// FilterMonitorFunc is default monitor filter when admin module is enable. +// if this func returns, admin module records qps for this request by condition of this function logic. +// usage: +// func MyFilterMonitor(method, requestPath string, t time.Duration, pattern string, statusCode int) bool { +// if method == "POST" { +// return false +// } +// if t.Nanoseconds() < 100 { +// return false +// } +// if strings.HasPrefix(requestPath, "/astaxie") { +// return false +// } +// return true +// } +// beego.FilterMonitorFunc = MyFilterMonitor. +var FilterMonitorFunc func(string, string, time.Duration, string, int) bool + +// PrintTree prints all registered routers. +func PrintTree() M { + return (M)(web.BeeApp.PrintTree()) +} diff --git a/adapter/app.go b/adapter/app.go new file mode 100644 index 00000000..e20cd9d2 --- /dev/null +++ b/adapter/app.go @@ -0,0 +1,262 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 adapter + +import ( + "net/http" + + context2 "github.com/astaxie/beego/adapter/context" + "github.com/astaxie/beego/server/web" + "github.com/astaxie/beego/server/web/context" +) + +var ( + // BeeApp is an application instance + BeeApp *App +) + +func init() { + // create beego application + BeeApp = (*App)(web.BeeApp) +} + +// App defines beego application with a new PatternServeMux. +type App web.HttpServer + +// NewApp returns a new beego application. +func NewApp() *App { + return (*App)(web.NewHttpSever()) +} + +// MiddleWare function for http.Handler +type MiddleWare web.MiddleWare + +// Run beego application. +func (app *App) Run(mws ...MiddleWare) { + newMws := oldMiddlewareToNew(mws) + (*web.HttpServer)(app).Run("", newMws...) +} + +func oldMiddlewareToNew(mws []MiddleWare) []web.MiddleWare { + newMws := make([]web.MiddleWare, 0, len(mws)) + for _, old := range mws { + newMws = append(newMws, (web.MiddleWare)(old)) + } + return newMws +} + +// Router adds a patterned controller handler to BeeApp. +// it's an alias method of HttpServer.Router. +// usage: +// simple router +// beego.Router("/admin", &admin.UserController{}) +// beego.Router("/admin/index", &admin.ArticleController{}) +// +// regex router +// +// beego.Router("/api/:id([0-9]+)", &controllers.RController{}) +// +// custom rules +// beego.Router("/api/list",&RestController{},"*:ListFood") +// beego.Router("/api/create",&RestController{},"post:CreateFood") +// beego.Router("/api/update",&RestController{},"put:UpdateFood") +// beego.Router("/api/delete",&RestController{},"delete:DeleteFood") +func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App { + return (*App)(web.Router(rootpath, c, mappingMethods...)) +} + +// UnregisterFixedRoute unregisters the route with the specified fixedRoute. It is particularly useful +// in web applications that inherit most routes from a base webapp via the underscore +// import, and aim to overwrite only certain paths. +// The method parameter can be empty or "*" for all HTTP methods, or a particular +// method type (e.g. "GET" or "POST") for selective removal. +// +// Usage (replace "GET" with "*" for all methods): +// beego.UnregisterFixedRoute("/yourpreviouspath", "GET") +// beego.Router("/yourpreviouspath", yourControllerAddress, "get:GetNewPage") +func UnregisterFixedRoute(fixedRoute string, method string) *App { + return (*App)(web.UnregisterFixedRoute(fixedRoute, method)) +} + +// Include will generate router file in the router/xxx.go from the controller's comments +// usage: +// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) +// type BankAccount struct{ +// beego.Controller +// } +// +// register the function +// func (b *BankAccount)Mapping(){ +// b.Mapping("ShowAccount" , b.ShowAccount) +// b.Mapping("ModifyAccount", b.ModifyAccount) +// } +// +// //@router /account/:id [get] +// func (b *BankAccount) ShowAccount(){ +// //logic +// } +// +// +// //@router /account/:id [post] +// func (b *BankAccount) ModifyAccount(){ +// //logic +// } +// +// the comments @router url methodlist +// url support all the function Router's pattern +// methodlist [get post head put delete options *] +func Include(cList ...ControllerInterface) *App { + newList := oldToNewCtrlIntfs(cList) + return (*App)(web.Include(newList...)) +} + +func oldToNewCtrlIntfs(cList []ControllerInterface) []web.ControllerInterface { + newList := make([]web.ControllerInterface, 0, len(cList)) + for _, c := range cList { + newList = append(newList, c) + } + return newList +} + +// RESTRouter adds a restful controller handler to BeeApp. +// its' controller implements beego.ControllerInterface and +// defines a param "pattern/:objectId" to visit each resource. +func RESTRouter(rootpath string, c ControllerInterface) *App { + return (*App)(web.RESTRouter(rootpath, c)) +} + +// AutoRouter adds defined controller handler to BeeApp. +// it's same to HttpServer.AutoRouter. +// if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page, +// visit the url /main/list to exec List function or /main/page to exec Page function. +func AutoRouter(c ControllerInterface) *App { + return (*App)(web.AutoRouter(c)) +} + +// AutoPrefix adds controller handler to BeeApp with prefix. +// it's same to HttpServer.AutoRouterWithPrefix. +// if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page, +// visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function. +func AutoPrefix(prefix string, c ControllerInterface) *App { + return (*App)(web.AutoPrefix(prefix, c)) +} + +// Get used to register router for Get method +// usage: +// beego.Get("/", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Get(rootpath string, f FilterFunc) *App { + return (*App)(web.Get(rootpath, func(ctx *context.Context) { + f((*context2.Context)(ctx)) + })) +} + +// Post used to register router for Post method +// usage: +// beego.Post("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Post(rootpath string, f FilterFunc) *App { + return (*App)(web.Post(rootpath, func(ctx *context.Context) { + f((*context2.Context)(ctx)) + })) +} + +// Delete used to register router for Delete method +// usage: +// beego.Delete("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Delete(rootpath string, f FilterFunc) *App { + return (*App)(web.Delete(rootpath, func(ctx *context.Context) { + f((*context2.Context)(ctx)) + })) +} + +// Put used to register router for Put method +// usage: +// beego.Put("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Put(rootpath string, f FilterFunc) *App { + return (*App)(web.Put(rootpath, func(ctx *context.Context) { + f((*context2.Context)(ctx)) + })) +} + +// Head used to register router for Head method +// usage: +// beego.Head("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Head(rootpath string, f FilterFunc) *App { + return (*App)(web.Head(rootpath, func(ctx *context.Context) { + f((*context2.Context)(ctx)) + })) +} + +// Options used to register router for Options method +// usage: +// beego.Options("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Options(rootpath string, f FilterFunc) *App { + return (*App)(web.Options(rootpath, func(ctx *context.Context) { + f((*context2.Context)(ctx)) + })) +} + +// Patch used to register router for Patch method +// usage: +// beego.Patch("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Patch(rootpath string, f FilterFunc) *App { + return (*App)(web.Patch(rootpath, func(ctx *context.Context) { + f((*context2.Context)(ctx)) + })) +} + +// Any used to register router for all methods +// usage: +// beego.Any("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func Any(rootpath string, f FilterFunc) *App { + return (*App)(web.Any(rootpath, func(ctx *context.Context) { + f((*context2.Context)(ctx)) + })) +} + +// Handler used to register a Handler router +// usage: +// beego.Handler("/api", http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { +// fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) +// })) +func Handler(rootpath string, h http.Handler, options ...interface{}) *App { + return (*App)(web.Handler(rootpath, h, options)) +} + +// InsertFilter adds a FilterFunc with pattern condition and action constant. +// The pos means action constant including +// beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter. +// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute) +func InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) *App { + opts := oldToNewFilterOpts(params) + return (*App)(web.InsertFilter(pattern, pos, func(ctx *context.Context) { + filter((*context2.Context)(ctx)) + }, opts...)) +} diff --git a/adapter/beego.go b/adapter/beego.go new file mode 100644 index 00000000..bbe37db8 --- /dev/null +++ b/adapter/beego.go @@ -0,0 +1,77 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 adapter + +import ( + "github.com/astaxie/beego" + "github.com/astaxie/beego/server/web" +) + +const ( + + // VERSION represent beego web framework version. + VERSION = beego.VERSION + + // DEV is for develop + DEV = web.DEV + // PROD is for production + PROD = web.PROD +) + +// M is Map shortcut +type M web.M + +// Hook function to run +type hookfunc func() error + +var ( + hooks = make([]hookfunc, 0) // hook function slice to store the hookfunc +) + +// AddAPPStartHook is used to register the hookfunc +// The hookfuncs will run in beego.Run() +// such as initiating session , starting middleware , building template, starting admin control and so on. +func AddAPPStartHook(hf ...hookfunc) { + for _, f := range hf { + web.AddAPPStartHook(func() error { + return f() + }) + } +} + +// Run beego application. +// beego.Run() default run on HttpPort +// beego.Run("localhost") +// beego.Run(":8089") +// beego.Run("127.0.0.1:8089") +func Run(params ...string) { + web.Run(params...) +} + +// RunWithMiddleWares Run beego application with middlewares. +func RunWithMiddleWares(addr string, mws ...MiddleWare) { + newMws := oldMiddlewareToNew(mws) + web.RunWithMiddleWares(addr, newMws...) +} + +// TestBeegoInit is for test package init +func TestBeegoInit(ap string) { + web.TestBeegoInit(ap) +} + +// InitBeegoBeforeTest is for test package init +func InitBeegoBeforeTest(appConfigPath string) { + web.InitBeegoBeforeTest(appConfigPath) +} diff --git a/adapter/build_info.go b/adapter/build_info.go new file mode 100644 index 00000000..1e8dacf0 --- /dev/null +++ b/adapter/build_info.go @@ -0,0 +1,27 @@ +// Copyright 2020 astaxie +// +// Licensed 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 adapter + +var ( + BuildVersion string + BuildGitRevision string + BuildStatus string + BuildTag string + BuildTime string + + GoVersion string + + GitBranch string +) diff --git a/cache/cache.go b/adapter/cache/cache.go similarity index 100% rename from cache/cache.go rename to adapter/cache/cache.go diff --git a/adapter/cache/cache_adapter.go b/adapter/cache/cache_adapter.go new file mode 100644 index 00000000..3bfd0bf8 --- /dev/null +++ b/adapter/cache/cache_adapter.go @@ -0,0 +1,117 @@ +// Copyright 2020 +// +// Licensed 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 cache + +import ( + "context" + "time" + + "github.com/astaxie/beego/client/cache" +) + +type newToOldCacheAdapter struct { + delegate cache.Cache +} + +func (c *newToOldCacheAdapter) Get(key string) interface{} { + res, _ := c.delegate.Get(context.Background(), key) + return res +} + +func (c *newToOldCacheAdapter) GetMulti(keys []string) []interface{} { + res, _ := c.delegate.GetMulti(context.Background(), keys) + return res +} + +func (c *newToOldCacheAdapter) Put(key string, val interface{}, timeout time.Duration) error { + return c.delegate.Put(context.Background(), key, val, timeout) +} + +func (c *newToOldCacheAdapter) Delete(key string) error { + return c.delegate.Delete(context.Background(), key) +} + +func (c *newToOldCacheAdapter) Incr(key string) error { + return c.delegate.Incr(context.Background(), key) +} + +func (c *newToOldCacheAdapter) Decr(key string) error { + return c.delegate.Decr(context.Background(), key) +} + +func (c *newToOldCacheAdapter) IsExist(key string) bool { + res, err := c.delegate.IsExist(context.Background(), key) + return res && err == nil +} + +func (c *newToOldCacheAdapter) ClearAll() error { + return c.delegate.ClearAll(context.Background()) +} + +func (c *newToOldCacheAdapter) StartAndGC(config string) error { + return c.delegate.StartAndGC(config) +} + +func CreateNewToOldCacheAdapter(delegate cache.Cache) Cache { + return &newToOldCacheAdapter{ + delegate: delegate, + } +} + +type oldToNewCacheAdapter struct { + old Cache +} + +func (o *oldToNewCacheAdapter) Get(ctx context.Context, key string) (interface{}, error) { + return o.old.Get(key), nil +} + +func (o *oldToNewCacheAdapter) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { + return o.old.GetMulti(keys), nil +} + +func (o *oldToNewCacheAdapter) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { + return o.old.Put(key, val, timeout) +} + +func (o *oldToNewCacheAdapter) Delete(ctx context.Context, key string) error { + return o.old.Delete(key) +} + +func (o *oldToNewCacheAdapter) Incr(ctx context.Context, key string) error { + return o.old.Incr(key) +} + +func (o *oldToNewCacheAdapter) Decr(ctx context.Context, key string) error { + return o.old.Decr(key) +} + +func (o *oldToNewCacheAdapter) IsExist(ctx context.Context, key string) (bool, error) { + return o.old.IsExist(key), nil +} + +func (o *oldToNewCacheAdapter) ClearAll(ctx context.Context) error { + return o.old.ClearAll() +} + +func (o *oldToNewCacheAdapter) StartAndGC(config string) error { + return o.old.StartAndGC(config) +} + +func CreateOldToNewAdapter(old Cache) cache.Cache { + return &oldToNewCacheAdapter{ + old: old, + } +} diff --git a/cache/cache_test.go b/adapter/cache/cache_test.go similarity index 100% rename from cache/cache_test.go rename to adapter/cache/cache_test.go diff --git a/adapter/cache/conv.go b/adapter/cache/conv.go new file mode 100644 index 00000000..18b8a255 --- /dev/null +++ b/adapter/cache/conv.go @@ -0,0 +1,44 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 cache + +import ( + "github.com/astaxie/beego/client/cache" +) + +// GetString convert interface to string. +func GetString(v interface{}) string { + return cache.GetString(v) +} + +// GetInt convert interface to int. +func GetInt(v interface{}) int { + return cache.GetInt(v) +} + +// GetInt64 convert interface to int64. +func GetInt64(v interface{}) int64 { + return cache.GetInt64(v) +} + +// GetFloat64 convert interface to float64. +func GetFloat64(v interface{}) float64 { + return cache.GetFloat64(v) +} + +// GetBool convert interface to bool. +func GetBool(v interface{}) bool { + return cache.GetBool(v) +} diff --git a/cache/conv_test.go b/adapter/cache/conv_test.go similarity index 100% rename from cache/conv_test.go rename to adapter/cache/conv_test.go diff --git a/adapter/cache/file.go b/adapter/cache/file.go new file mode 100644 index 00000000..74eb980a --- /dev/null +++ b/adapter/cache/file.go @@ -0,0 +1,30 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 cache + +import ( + "github.com/astaxie/beego/client/cache" +) + +// NewFileCache Create new file cache with no config. +// the level and expiry need set in method StartAndGC as config string. +func NewFileCache() Cache { + // return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix} + return CreateNewToOldCacheAdapter(cache.NewFileCache()) +} + +func init() { + Register("file", NewFileCache) +} diff --git a/adapter/cache/memcache/memcache.go b/adapter/cache/memcache/memcache.go new file mode 100644 index 00000000..b4da1bfe --- /dev/null +++ b/adapter/cache/memcache/memcache.go @@ -0,0 +1,44 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 memcache for cache provider +// +// depend on github.com/bradfitz/gomemcache/memcache +// +// go install github.com/bradfitz/gomemcache/memcache +// +// Usage: +// import( +// _ "github.com/astaxie/beego/cache/memcache" +// "github.com/astaxie/beego/cache" +// ) +// +// bm, err := cache.NewCache("memcache", `{"conn":"127.0.0.1:11211"}`) +// +// more docs http://beego.me/docs/module/cache.md +package memcache + +import ( + "github.com/astaxie/beego/adapter/cache" + "github.com/astaxie/beego/client/cache/memcache" +) + +// NewMemCache create new memcache adapter. +func NewMemCache() cache.Cache { + return cache.CreateNewToOldCacheAdapter(memcache.NewMemCache()) +} + +func init() { + cache.Register("memcache", NewMemCache) +} diff --git a/cache/memcache/memcache_test.go b/adapter/cache/memcache/memcache_test.go similarity index 90% rename from cache/memcache/memcache_test.go rename to adapter/cache/memcache/memcache_test.go index d9129b69..b9b6dc6b 100644 --- a/cache/memcache/memcache_test.go +++ b/adapter/cache/memcache/memcache_test.go @@ -15,17 +15,23 @@ package memcache import ( - _ "github.com/bradfitz/gomemcache/memcache" - + "fmt" + "os" "strconv" "testing" "time" - "github.com/astaxie/beego/cache" + "github.com/astaxie/beego/adapter/cache" ) func TestMemcacheCache(t *testing.T) { - bm, err := cache.NewCache("memcache", `{"conn": "127.0.0.1:11211"}`) + + addr := os.Getenv("MEMCACHE_ADDR") + if addr == "" { + addr = "127.0.0.1:11211" + } + + bm, err := cache.NewCache("memcache", fmt.Sprintf(`{"conn": "%s"}`, addr)) if err != nil { t.Error("init err") } @@ -70,7 +76,7 @@ func TestMemcacheCache(t *testing.T) { t.Error("delete err") } - //test string + // test string if err = bm.Put("astaxie", "author", timeoutDuration); err != nil { t.Error("set Error", err) } @@ -82,7 +88,7 @@ func TestMemcacheCache(t *testing.T) { t.Error("get err") } - //test GetMulti + // test GetMulti if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil { t.Error("set Error", err) } diff --git a/adapter/cache/memory.go b/adapter/cache/memory.go new file mode 100644 index 00000000..cf6e3992 --- /dev/null +++ b/adapter/cache/memory.go @@ -0,0 +1,28 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 cache + +import ( + "github.com/astaxie/beego/client/cache" +) + +// NewMemoryCache returns a new MemoryCache. +func NewMemoryCache() Cache { + return CreateNewToOldCacheAdapter(cache.NewMemoryCache()) +} + +func init() { + Register("memory", NewMemoryCache) +} diff --git a/adapter/cache/redis/redis.go b/adapter/cache/redis/redis.go new file mode 100644 index 00000000..3562057d --- /dev/null +++ b/adapter/cache/redis/redis.go @@ -0,0 +1,49 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 redis for cache provider +// +// depend on github.com/gomodule/redigo/redis +// +// go install github.com/gomodule/redigo/redis +// +// Usage: +// import( +// _ "github.com/astaxie/beego/cache/redis" +// "github.com/astaxie/beego/cache" +// ) +// +// bm, err := cache.NewCache("redis", `{"conn":"127.0.0.1:11211"}`) +// +// more docs http://beego.me/docs/module/cache.md +package redis + +import ( + "github.com/astaxie/beego/adapter/cache" + redis2 "github.com/astaxie/beego/client/cache/redis" +) + +var ( + // DefaultKey the collection name of redis for cache adapter. + DefaultKey = "beecacheRedis" +) + +// NewRedisCache create new redis cache with default collection name. +func NewRedisCache() cache.Cache { + return cache.CreateNewToOldCacheAdapter(redis2.NewRedisCache()) +} + +func init() { + cache.Register("redis", NewRedisCache) +} diff --git a/cache/redis/redis_test.go b/adapter/cache/redis/redis_test.go similarity index 86% rename from cache/redis/redis_test.go rename to adapter/cache/redis/redis_test.go index 7ac88f87..7ae12197 100644 --- a/cache/redis/redis_test.go +++ b/adapter/cache/redis/redis_test.go @@ -16,15 +16,22 @@ package redis import ( "fmt" + "os" "testing" "time" - "github.com/astaxie/beego/cache" "github.com/gomodule/redigo/redis" + + "github.com/astaxie/beego/adapter/cache" ) func TestRedisCache(t *testing.T) { - bm, err := cache.NewCache("redis", `{"conn": "127.0.0.1:6379"}`) + redisAddr := os.Getenv("REDIS_ADDR") + if redisAddr == "" { + redisAddr = "127.0.0.1:6379" + } + + bm, err := cache.NewCache("redis", fmt.Sprintf(`{"conn": "%s"}`, redisAddr)) if err != nil { t.Error("init err") } @@ -119,26 +126,10 @@ func TestCache_Scan(t *testing.T) { t.Error("set Error", err) } } - // scan all for the first time - keys, err := bm.(*Cache).Scan(DefaultKey + ":*") - if err != nil { - t.Error("scan Error", err) - } - if len(keys) != 10000 { - t.Error("scan all err") - } // clear all if err = bm.ClearAll(); err != nil { t.Error("clear all err") } - // scan all for the second time - keys, err = bm.(*Cache).Scan(DefaultKey + ":*") - if err != nil { - t.Error("scan Error", err) - } - if len(keys) != 0 { - t.Error("scan all err") - } } diff --git a/adapter/cache/ssdb/ssdb.go b/adapter/cache/ssdb/ssdb.go new file mode 100644 index 00000000..df552043 --- /dev/null +++ b/adapter/cache/ssdb/ssdb.go @@ -0,0 +1,15 @@ +package ssdb + +import ( + "github.com/astaxie/beego/adapter/cache" + ssdb2 "github.com/astaxie/beego/client/cache/ssdb" +) + +// NewSsdbCache create new ssdb adapter. +func NewSsdbCache() cache.Cache { + return cache.CreateNewToOldCacheAdapter(ssdb2.NewSsdbCache()) +} + +func init() { + cache.Register("ssdb", NewSsdbCache) +} diff --git a/cache/ssdb/ssdb_test.go b/adapter/cache/ssdb/ssdb_test.go similarity index 90% rename from cache/ssdb/ssdb_test.go rename to adapter/cache/ssdb/ssdb_test.go index dd474960..080167cd 100644 --- a/cache/ssdb/ssdb_test.go +++ b/adapter/cache/ssdb/ssdb_test.go @@ -1,15 +1,22 @@ package ssdb import ( + "fmt" + "os" "strconv" "testing" "time" - "github.com/astaxie/beego/cache" + "github.com/astaxie/beego/adapter/cache" ) func TestSsdbcacheCache(t *testing.T) { - ssdb, err := cache.NewCache("ssdb", `{"conn": "127.0.0.1:8888"}`) + ssdbAddr := os.Getenv("SSDB_ADDR") + if ssdbAddr == "" { + ssdbAddr = "127.0.0.1:8888" + } + + ssdb, err := cache.NewCache("ssdb", fmt.Sprintf(`{"conn": "%s"}`, ssdbAddr)) if err != nil { t.Error("init err") } diff --git a/adapter/config.go b/adapter/config.go new file mode 100644 index 00000000..6280b8f8 --- /dev/null +++ b/adapter/config.go @@ -0,0 +1,177 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 adapter + +import ( + "github.com/astaxie/beego/adapter/session" + newCfg "github.com/astaxie/beego/core/config" + "github.com/astaxie/beego/server/web" +) + +// Config is the main struct for BConfig +type Config web.Config + +// Listen holds for http and https related config +type Listen web.Listen + +// WebConfig holds web related config +type WebConfig web.WebConfig + +// SessionConfig holds session related config +type SessionConfig web.SessionConfig + +// LogConfig holds Log related config +type LogConfig web.LogConfig + +var ( + // BConfig is the default config for Application + BConfig *Config + // AppConfig is the instance of Config, store the config information from file + AppConfig *beegoAppConfig + // AppPath is the absolute path to the app + AppPath string + // GlobalSessions is the instance for the session manager + GlobalSessions *session.Manager + + // appConfigPath is the path to the config files + appConfigPath string + // appConfigProvider is the provider for the config, default is ini + appConfigProvider = "ini" + // WorkPath is the absolute path to project root directory + WorkPath string +) + +func init() { + BConfig = (*Config)(web.BConfig) + AppPath = web.AppPath + + WorkPath = web.WorkPath + + AppConfig = &beegoAppConfig{innerConfig: (newCfg.Configer)(web.AppConfig)} +} + +// LoadAppConfig allow developer to apply a config file +func LoadAppConfig(adapterName, configPath string) error { + return web.LoadAppConfig(adapterName, configPath) +} + +type beegoAppConfig struct { + innerConfig newCfg.Configer +} + +func (b *beegoAppConfig) Set(key, val string) error { + if err := b.innerConfig.Set(BConfig.RunMode+"::"+key, val); err != nil { + return b.innerConfig.Set(key, val) + } + return nil +} + +func (b *beegoAppConfig) String(key string) string { + if v, err := b.innerConfig.String(BConfig.RunMode + "::" + key); v != "" && err != nil { + return v + } + res, _ := b.innerConfig.String(key) + return res +} + +func (b *beegoAppConfig) Strings(key string) []string { + if v, err := b.innerConfig.Strings(BConfig.RunMode + "::" + key); len(v) > 0 && err != nil { + return v + } + res, _ := b.innerConfig.Strings(key) + return res +} + +func (b *beegoAppConfig) Int(key string) (int, error) { + if v, err := b.innerConfig.Int(BConfig.RunMode + "::" + key); err == nil { + return v, nil + } + return b.innerConfig.Int(key) +} + +func (b *beegoAppConfig) Int64(key string) (int64, error) { + if v, err := b.innerConfig.Int64(BConfig.RunMode + "::" + key); err == nil { + return v, nil + } + return b.innerConfig.Int64(key) +} + +func (b *beegoAppConfig) Bool(key string) (bool, error) { + if v, err := b.innerConfig.Bool(BConfig.RunMode + "::" + key); err == nil { + return v, nil + } + return b.innerConfig.Bool(key) +} + +func (b *beegoAppConfig) Float(key string) (float64, error) { + if v, err := b.innerConfig.Float(BConfig.RunMode + "::" + key); err == nil { + return v, nil + } + return b.innerConfig.Float(key) +} + +func (b *beegoAppConfig) DefaultString(key string, defaultVal string) string { + if v := b.String(key); v != "" { + return v + } + return defaultVal +} + +func (b *beegoAppConfig) DefaultStrings(key string, defaultVal []string) []string { + if v := b.Strings(key); len(v) != 0 { + return v + } + return defaultVal +} + +func (b *beegoAppConfig) DefaultInt(key string, defaultVal int) int { + if v, err := b.Int(key); err == nil { + return v + } + return defaultVal +} + +func (b *beegoAppConfig) DefaultInt64(key string, defaultVal int64) int64 { + if v, err := b.Int64(key); err == nil { + return v + } + return defaultVal +} + +func (b *beegoAppConfig) DefaultBool(key string, defaultVal bool) bool { + if v, err := b.Bool(key); err == nil { + return v + } + return defaultVal +} + +func (b *beegoAppConfig) DefaultFloat(key string, defaultVal float64) float64 { + if v, err := b.Float(key); err == nil { + return v + } + return defaultVal +} + +func (b *beegoAppConfig) DIY(key string) (interface{}, error) { + return b.innerConfig.DIY(key) +} + +func (b *beegoAppConfig) GetSection(section string) (map[string]string, error) { + return b.innerConfig.GetSection(section) +} + +func (b *beegoAppConfig) SaveConfigFile(filename string) error { + return b.innerConfig.SaveConfigFile(filename) +} diff --git a/adapter/config/adapter.go b/adapter/config/adapter.go new file mode 100644 index 00000000..0a9e1d0c --- /dev/null +++ b/adapter/config/adapter.go @@ -0,0 +1,191 @@ +// Copyright 2020 +// +// Licensed 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 config + +import ( + "github.com/pkg/errors" + + "github.com/astaxie/beego/core/config" +) + +type newToOldConfigerAdapter struct { + delegate config.Configer +} + +func (c *newToOldConfigerAdapter) Set(key, val string) error { + return c.delegate.Set(key, val) +} + +func (c *newToOldConfigerAdapter) String(key string) string { + res, _ := c.delegate.String(key) + return res +} + +func (c *newToOldConfigerAdapter) Strings(key string) []string { + res, _ := c.delegate.Strings(key) + return res +} + +func (c *newToOldConfigerAdapter) Int(key string) (int, error) { + return c.delegate.Int(key) +} + +func (c *newToOldConfigerAdapter) Int64(key string) (int64, error) { + return c.delegate.Int64(key) +} + +func (c *newToOldConfigerAdapter) Bool(key string) (bool, error) { + return c.delegate.Bool(key) +} + +func (c *newToOldConfigerAdapter) Float(key string) (float64, error) { + return c.delegate.Float(key) +} + +func (c *newToOldConfigerAdapter) DefaultString(key string, defaultVal string) string { + return c.delegate.DefaultString(key, defaultVal) +} + +func (c *newToOldConfigerAdapter) DefaultStrings(key string, defaultVal []string) []string { + return c.delegate.DefaultStrings(key, defaultVal) +} + +func (c *newToOldConfigerAdapter) DefaultInt(key string, defaultVal int) int { + return c.delegate.DefaultInt(key, defaultVal) +} + +func (c *newToOldConfigerAdapter) DefaultInt64(key string, defaultVal int64) int64 { + return c.delegate.DefaultInt64(key, defaultVal) +} + +func (c *newToOldConfigerAdapter) DefaultBool(key string, defaultVal bool) bool { + return c.delegate.DefaultBool(key, defaultVal) +} + +func (c *newToOldConfigerAdapter) DefaultFloat(key string, defaultVal float64) float64 { + return c.delegate.DefaultFloat(key, defaultVal) +} + +func (c *newToOldConfigerAdapter) DIY(key string) (interface{}, error) { + return c.delegate.DIY(key) +} + +func (c *newToOldConfigerAdapter) GetSection(section string) (map[string]string, error) { + return c.delegate.GetSection(section) +} + +func (c *newToOldConfigerAdapter) SaveConfigFile(filename string) error { + return c.delegate.SaveConfigFile(filename) +} + +type oldToNewConfigerAdapter struct { + delegate Configer +} + +func (o *oldToNewConfigerAdapter) Set(key, val string) error { + return o.delegate.Set(key, val) +} + +func (o *oldToNewConfigerAdapter) String(key string) (string, error) { + return o.delegate.String(key), nil +} + +func (o *oldToNewConfigerAdapter) Strings(key string) ([]string, error) { + return o.delegate.Strings(key), nil +} + +func (o *oldToNewConfigerAdapter) Int(key string) (int, error) { + return o.delegate.Int(key) +} + +func (o *oldToNewConfigerAdapter) Int64(key string) (int64, error) { + return o.delegate.Int64(key) +} + +func (o *oldToNewConfigerAdapter) Bool(key string) (bool, error) { + return o.delegate.Bool(key) +} + +func (o *oldToNewConfigerAdapter) Float(key string) (float64, error) { + return o.delegate.Float(key) +} + +func (o *oldToNewConfigerAdapter) DefaultString(key string, defaultVal string) string { + return o.delegate.DefaultString(key, defaultVal) +} + +func (o *oldToNewConfigerAdapter) DefaultStrings(key string, defaultVal []string) []string { + return o.delegate.DefaultStrings(key, defaultVal) +} + +func (o *oldToNewConfigerAdapter) DefaultInt(key string, defaultVal int) int { + return o.delegate.DefaultInt(key, defaultVal) +} + +func (o *oldToNewConfigerAdapter) DefaultInt64(key string, defaultVal int64) int64 { + return o.delegate.DefaultInt64(key, defaultVal) +} + +func (o *oldToNewConfigerAdapter) DefaultBool(key string, defaultVal bool) bool { + return o.delegate.DefaultBool(key, defaultVal) +} + +func (o *oldToNewConfigerAdapter) DefaultFloat(key string, defaultVal float64) float64 { + return o.delegate.DefaultFloat(key, defaultVal) +} + +func (o *oldToNewConfigerAdapter) DIY(key string) (interface{}, error) { + return o.delegate.DIY(key) +} + +func (o *oldToNewConfigerAdapter) GetSection(section string) (map[string]string, error) { + return o.delegate.GetSection(section) +} + +func (o *oldToNewConfigerAdapter) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error { + return errors.New("unsupported operation, please use actual config.Configer") +} + +func (o *oldToNewConfigerAdapter) Sub(key string) (config.Configer, error) { + return nil, errors.New("unsupported operation, please use actual config.Configer") +} + +func (o *oldToNewConfigerAdapter) OnChange(key string, fn func(value string)) { + // do nothing +} + +func (o *oldToNewConfigerAdapter) SaveConfigFile(filename string) error { + return o.delegate.SaveConfigFile(filename) +} + +type oldToNewConfigAdapter struct { + delegate Config +} + +func (o *oldToNewConfigAdapter) Parse(key string) (config.Configer, error) { + old, err := o.delegate.Parse(key) + if err != nil { + return nil, err + } + return &oldToNewConfigerAdapter{delegate: old}, nil +} + +func (o *oldToNewConfigAdapter) ParseData(data []byte) (config.Configer, error) { + old, err := o.delegate.ParseData(data) + if err != nil { + return nil, err + } + return &oldToNewConfigerAdapter{delegate: old}, nil +} diff --git a/adapter/config/config.go b/adapter/config/config.go new file mode 100644 index 00000000..703555cd --- /dev/null +++ b/adapter/config/config.go @@ -0,0 +1,151 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 config is used to parse config. +// Usage: +// import "github.com/astaxie/beego/config" +// Examples. +// +// cnf, err := config.NewConfig("ini", "config.conf") +// +// cnf APIS: +// +// cnf.Set(key, val string) error +// cnf.String(key string) string +// cnf.Strings(key string) []string +// cnf.Int(key string) (int, error) +// cnf.Int64(key string) (int64, error) +// cnf.Bool(key string) (bool, error) +// cnf.Float(key string) (float64, error) +// cnf.DefaultString(key string, defaultVal string) string +// cnf.DefaultStrings(key string, defaultVal []string) []string +// cnf.DefaultInt(key string, defaultVal int) int +// cnf.DefaultInt64(key string, defaultVal int64) int64 +// cnf.DefaultBool(key string, defaultVal bool) bool +// cnf.DefaultFloat(key string, defaultVal float64) float64 +// cnf.DIY(key string) (interface{}, error) +// cnf.GetSection(section string) (map[string]string, error) +// cnf.SaveConfigFile(filename string) error +// More docs http://beego.me/docs/module/config.md +package config + +import ( + "github.com/astaxie/beego/core/config" +) + +// Configer defines how to get and set value from configuration raw data. +type Configer interface { + Set(key, val string) error // support section::key type in given key when using ini type. + String(key string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. + Strings(key string) []string // get string slice + Int(key string) (int, error) + Int64(key string) (int64, error) + Bool(key string) (bool, error) + Float(key string) (float64, error) + DefaultString(key string, defaultVal string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. + DefaultStrings(key string, defaultVal []string) []string // get string slice + DefaultInt(key string, defaultVal int) int + DefaultInt64(key string, defaultVal int64) int64 + DefaultBool(key string, defaultVal bool) bool + DefaultFloat(key string, defaultVal float64) float64 + DIY(key string) (interface{}, error) + GetSection(section string) (map[string]string, error) + SaveConfigFile(filename string) error +} + +// Config is the adapter interface for parsing config file to get raw data to Configer. +type Config interface { + Parse(key string) (Configer, error) + ParseData(data []byte) (Configer, error) +} + +var adapters = make(map[string]Config) + +// Register makes a config adapter available by the adapter name. +// If Register is called twice with the same name or if driver is nil, +// it panics. +func Register(name string, adapter Config) { + config.Register(name, &oldToNewConfigAdapter{delegate: adapter}) +} + +// NewConfig adapterName is ini/json/xml/yaml. +// filename is the config file path. +func NewConfig(adapterName, filename string) (Configer, error) { + cfg, err := config.NewConfig(adapterName, filename) + if err != nil { + return nil, err + } + + // it was registered by using Register method + res, ok := cfg.(*oldToNewConfigerAdapter) + if ok { + return res.delegate, nil + } + + return &newToOldConfigerAdapter{ + delegate: cfg, + }, nil +} + +// NewConfigData adapterName is ini/json/xml/yaml. +// data is the config data. +func NewConfigData(adapterName string, data []byte) (Configer, error) { + cfg, err := config.NewConfigData(adapterName, data) + if err != nil { + return nil, err + } + + // it was registered by using Register method + res, ok := cfg.(*oldToNewConfigerAdapter) + if ok { + return res.delegate, nil + } + + return &newToOldConfigerAdapter{ + delegate: cfg, + }, nil +} + +// ExpandValueEnvForMap convert all string value with environment variable. +func ExpandValueEnvForMap(m map[string]interface{}) map[string]interface{} { + return config.ExpandValueEnvForMap(m) +} + +// ExpandValueEnv returns value of convert with environment variable. +// +// Return environment variable if value start with "${" and end with "}". +// Return default value if environment variable is empty or not exist. +// +// It accept value formats "${env}" , "${env||}}" , "${env||defaultValue}" , "defaultvalue". +// Examples: +// v1 := config.ExpandValueEnv("${GOPATH}") // return the GOPATH environment variable. +// v2 := config.ExpandValueEnv("${GOAsta||/usr/local/go}") // return the default value "/usr/local/go/". +// v3 := config.ExpandValueEnv("Astaxie") // return the value "Astaxie". +func ExpandValueEnv(value string) string { + return config.ExpandValueEnv(value) +} + +// ParseBool returns the boolean value represented by the string. +// +// It accepts 1, 1.0, t, T, TRUE, true, True, YES, yes, Yes,Y, y, ON, on, On, +// 0, 0.0, f, F, FALSE, false, False, NO, no, No, N,n, OFF, off, Off. +// Any other value returns an error. +func ParseBool(val interface{}) (value bool, err error) { + return config.ParseBool(val) +} + +// ToString converts values of any type to string. +func ToString(x interface{}) string { + return config.ToString(x) +} diff --git a/config/config_test.go b/adapter/config/config_test.go similarity index 100% rename from config/config_test.go rename to adapter/config/config_test.go diff --git a/adapter/config/env/env.go b/adapter/config/env/env.go new file mode 100644 index 00000000..839c60c1 --- /dev/null +++ b/adapter/config/env/env.go @@ -0,0 +1,50 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// Copyright 2017 Faissal Elamraoui. All Rights Reserved. +// +// Licensed 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 env is used to parse environment. +package env + +import ( + "github.com/astaxie/beego/core/config/env" +) + +// Get returns a value by key. +// If the key does not exist, the default value will be returned. +func Get(key string, defVal string) string { + return env.Get(key, defVal) +} + +// MustGet returns a value by key. +// If the key does not exist, it will return an error. +func MustGet(key string) (string, error) { + return env.MustGet(key) +} + +// Set sets a value in the ENV copy. +// This does not affect the child process environment. +func Set(key string, value string) { + env.Set(key, value) +} + +// MustSet sets a value in the ENV copy and the child process environment. +// It returns an error in case the set operation failed. +func MustSet(key string, value string) error { + return env.MustSet(key, value) +} + +// GetAll returns all keys/values in the current child process environment. +func GetAll() map[string]string { + return env.GetAll() +} diff --git a/config/env/env_test.go b/adapter/config/env/env_test.go similarity index 100% rename from config/env/env_test.go rename to adapter/config/env/env_test.go diff --git a/adapter/config/fake.go b/adapter/config/fake.go new file mode 100644 index 00000000..050f0252 --- /dev/null +++ b/adapter/config/fake.go @@ -0,0 +1,25 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 config + +import ( + "github.com/astaxie/beego/core/config" +) + +// NewFakeConfig return a fake Configer +func NewFakeConfig() Configer { + new := config.NewFakeConfig() + return &newToOldConfigerAdapter{delegate: new} +} diff --git a/config/ini_test.go b/adapter/config/ini_test.go similarity index 100% rename from config/ini_test.go rename to adapter/config/ini_test.go diff --git a/testing/assertions.go b/adapter/config/json.go similarity index 89% rename from testing/assertions.go rename to adapter/config/json.go index 96c5d4dd..d77e6146 100644 --- a/testing/assertions.go +++ b/adapter/config/json.go @@ -12,4 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -package testing +package config + +import ( + _ "github.com/astaxie/beego/core/config/json" +) diff --git a/config/json_test.go b/adapter/config/json_test.go similarity index 100% rename from config/json_test.go rename to adapter/config/json_test.go diff --git a/adapter/config/xml/xml.go b/adapter/config/xml/xml.go new file mode 100644 index 00000000..28d5f44e --- /dev/null +++ b/adapter/config/xml/xml.go @@ -0,0 +1,34 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 xml for config provider. +// +// depend on github.com/beego/x2j. +// +// go install github.com/beego/x2j. +// +// Usage: +// import( +// _ "github.com/astaxie/beego/config/xml" +// "github.com/astaxie/beego/config" +// ) +// +// cnf, err := config.NewConfig("xml", "config.xml") +// +// More docs http://beego.me/docs/module/config.md +package xml + +import ( + _ "github.com/astaxie/beego/core/config/xml" +) diff --git a/config/xml/xml_test.go b/adapter/config/xml/xml_test.go similarity index 98% rename from config/xml/xml_test.go rename to adapter/config/xml/xml_test.go index 346c866e..ae9b209e 100644 --- a/config/xml/xml_test.go +++ b/adapter/config/xml/xml_test.go @@ -19,7 +19,7 @@ import ( "os" "testing" - "github.com/astaxie/beego/config" + "github.com/astaxie/beego/adapter/config" ) func TestXML(t *testing.T) { diff --git a/adapter/config/yaml/yaml.go b/adapter/config/yaml/yaml.go new file mode 100644 index 00000000..196c9725 --- /dev/null +++ b/adapter/config/yaml/yaml.go @@ -0,0 +1,34 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 yaml for config provider +// +// depend on github.com/beego/goyaml2 +// +// go install github.com/beego/goyaml2 +// +// Usage: +// import( +// _ "github.com/astaxie/beego/config/yaml" +// "github.com/astaxie/beego/config" +// ) +// +// cnf, err := config.NewConfig("yaml", "config.yaml") +// +// More docs http://beego.me/docs/module/config.md +package yaml + +import ( + _ "github.com/astaxie/beego/core/config/yaml" +) diff --git a/config/yaml/yaml_test.go b/adapter/config/yaml/yaml_test.go similarity index 98% rename from config/yaml/yaml_test.go rename to adapter/config/yaml/yaml_test.go index 49cc1d1e..a72e435e 100644 --- a/config/yaml/yaml_test.go +++ b/adapter/config/yaml/yaml_test.go @@ -19,7 +19,7 @@ import ( "os" "testing" - "github.com/astaxie/beego/config" + "github.com/astaxie/beego/adapter/config" ) func TestYaml(t *testing.T) { diff --git a/adapter/context/acceptencoder.go b/adapter/context/acceptencoder.go new file mode 100644 index 00000000..4bfef95e --- /dev/null +++ b/adapter/context/acceptencoder.go @@ -0,0 +1,45 @@ +// Copyright 2015 beego Author. All Rights Reserved. +// +// Licensed 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 context + +import ( + "io" + "net/http" + "os" + + "github.com/astaxie/beego/server/web/context" +) + +// InitGzip init the gzipcompress +func InitGzip(minLength, compressLevel int, methods []string) { + context.InitGzip(minLength, compressLevel, methods) +} + +// WriteFile reads from file and writes to writer by the specific encoding(gzip/deflate) +func WriteFile(encoding string, writer io.Writer, file *os.File) (bool, string, error) { + return context.WriteFile(encoding, writer, file) +} + +// WriteBody reads writes content to writer by the specific encoding(gzip/deflate) +func WriteBody(encoding string, writer io.Writer, content []byte) (bool, string, error) { + return context.WriteBody(encoding, writer, content) +} + +// ParseEncoding will extract the right encoding for response +// the Accept-Encoding's sec is here: +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3 +func ParseEncoding(r *http.Request) string { + return context.ParseEncoding(r) +} diff --git a/adapter/context/context.go b/adapter/context/context.go new file mode 100644 index 00000000..123fdb2c --- /dev/null +++ b/adapter/context/context.go @@ -0,0 +1,146 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 context provide the context utils +// Usage: +// +// import "github.com/astaxie/beego/context" +// +// ctx := context.Context{Request:req,ResponseWriter:rw} +// +// more docs http://beego.me/docs/module/context.md +package context + +import ( + "bufio" + "net" + "net/http" + + "github.com/astaxie/beego/server/web/context" +) + +// commonly used mime-types +const ( + ApplicationJSON = context.ApplicationJSON + ApplicationXML = context.ApplicationXML + ApplicationYAML = context.ApplicationYAML + TextXML = context.TextXML +) + +// NewContext return the Context with Input and Output +func NewContext() *Context { + return (*Context)(context.NewContext()) +} + +// Context Http request context struct including BeegoInput, BeegoOutput, http.Request and http.ResponseWriter. +// BeegoInput and BeegoOutput provides some api to operate request and response more easily. +type Context context.Context + +// Reset init Context, BeegoInput and BeegoOutput +func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) { + (*context.Context)(ctx).Reset(rw, r) +} + +// Redirect does redirection to localurl with http header status code. +func (ctx *Context) Redirect(status int, localurl string) { + (*context.Context)(ctx).Redirect(status, localurl) +} + +// Abort stops this request. +// if beego.ErrorMaps exists, panic body. +func (ctx *Context) Abort(status int, body string) { + (*context.Context)(ctx).Abort(status, body) +} + +// WriteString Write string to response body. +// it sends response body. +func (ctx *Context) WriteString(content string) { + (*context.Context)(ctx).WriteString(content) +} + +// GetCookie Get cookie from request by a given key. +// It's alias of BeegoInput.Cookie. +func (ctx *Context) GetCookie(key string) string { + return (*context.Context)(ctx).GetCookie(key) +} + +// SetCookie Set cookie for response. +// It's alias of BeegoOutput.Cookie. +func (ctx *Context) SetCookie(name string, value string, others ...interface{}) { + (*context.Context)(ctx).SetCookie(name, value, others) +} + +// GetSecureCookie Get secure cookie from request by a given key. +func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) { + return (*context.Context)(ctx).GetSecureCookie(Secret, key) +} + +// SetSecureCookie Set Secure cookie for response. +func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) { + (*context.Context)(ctx).SetSecureCookie(Secret, name, value, others) +} + +// XSRFToken creates a xsrf token string and returns. +func (ctx *Context) XSRFToken(key string, expire int64) string { + return (*context.Context)(ctx).XSRFToken(key, expire) +} + +// CheckXSRFCookie checks xsrf token in this request is valid or not. +// the token can provided in request header "X-Xsrftoken" and "X-CsrfToken" +// or in form field value named as "_xsrf". +func (ctx *Context) CheckXSRFCookie() bool { + return (*context.Context)(ctx).CheckXSRFCookie() +} + +// RenderMethodResult renders the return value of a controller method to the output +func (ctx *Context) RenderMethodResult(result interface{}) { + (*context.Context)(ctx).RenderMethodResult(result) +} + +// Response is a wrapper for the http.ResponseWriter +// started set to true if response was written to then don't execute other handler +type Response context.Response + +// Write writes the data to the connection as part of an HTTP reply, +// and sets `started` to true. +// started means the response has sent out. +func (r *Response) Write(p []byte) (int, error) { + return (*context.Response)(r).Write(p) +} + +// WriteHeader sends an HTTP response header with status code, +// and sets `started` to true. +func (r *Response) WriteHeader(code int) { + (*context.Response)(r).WriteHeader(code) +} + +// Hijack hijacker for http +func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return (*context.Response)(r).Hijack() +} + +// Flush http.Flusher +func (r *Response) Flush() { + (*context.Response)(r).Flush() +} + +// CloseNotify http.CloseNotifier +func (r *Response) CloseNotify() <-chan bool { + return (*context.Response)(r).CloseNotify() +} + +// Pusher http.Pusher +func (r *Response) Pusher() (pusher http.Pusher) { + return (*context.Response)(r).Pusher() +} diff --git a/adapter/context/input.go b/adapter/context/input.go new file mode 100644 index 00000000..51bb9ea5 --- /dev/null +++ b/adapter/context/input.go @@ -0,0 +1,282 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 context + +import ( + "github.com/astaxie/beego/server/web/context" +) + +// BeegoInput operates the http request header, data, cookie and body. +// it also contains router params and current session. +type BeegoInput context.BeegoInput + +// NewInput return BeegoInput generated by Context. +func NewInput() *BeegoInput { + return (*BeegoInput)(context.NewInput()) +} + +// Reset init the BeegoInput +func (input *BeegoInput) Reset(ctx *Context) { + (*context.BeegoInput)(input).Reset((*context.Context)(ctx)) +} + +// Protocol returns request protocol name, such as HTTP/1.1 . +func (input *BeegoInput) Protocol() string { + return (*context.BeegoInput)(input).Protocol() +} + +// URI returns full request url with query string, fragment. +func (input *BeegoInput) URI() string { + return input.Context.Request.RequestURI +} + +// URL returns request url path (without query string, fragment). +func (input *BeegoInput) URL() string { + return (*context.BeegoInput)(input).URL() +} + +// Site returns base site url as scheme://domain type. +func (input *BeegoInput) Site() string { + return (*context.BeegoInput)(input).Site() +} + +// Scheme returns request scheme as "http" or "https". +func (input *BeegoInput) Scheme() string { + return (*context.BeegoInput)(input).Scheme() +} + +// Domain returns host name. +// Alias of Host method. +func (input *BeegoInput) Domain() string { + return (*context.BeegoInput)(input).Domain() +} + +// Host returns host name. +// if no host info in request, return localhost. +func (input *BeegoInput) Host() string { + return (*context.BeegoInput)(input).Host() +} + +// Method returns http request method. +func (input *BeegoInput) Method() string { + return (*context.BeegoInput)(input).Method() +} + +// Is returns boolean of this request is on given method, such as Is("POST"). +func (input *BeegoInput) Is(method string) bool { + return (*context.BeegoInput)(input).Is(method) +} + +// IsGet Is this a GET method request? +func (input *BeegoInput) IsGet() bool { + return (*context.BeegoInput)(input).IsGet() +} + +// IsPost Is this a POST method request? +func (input *BeegoInput) IsPost() bool { + return (*context.BeegoInput)(input).IsPost() +} + +// IsHead Is this a Head method request? +func (input *BeegoInput) IsHead() bool { + return (*context.BeegoInput)(input).IsHead() +} + +// IsOptions Is this a OPTIONS method request? +func (input *BeegoInput) IsOptions() bool { + return (*context.BeegoInput)(input).IsOptions() +} + +// IsPut Is this a PUT method request? +func (input *BeegoInput) IsPut() bool { + return (*context.BeegoInput)(input).IsPut() +} + +// IsDelete Is this a DELETE method request? +func (input *BeegoInput) IsDelete() bool { + return (*context.BeegoInput)(input).IsDelete() +} + +// IsPatch Is this a PATCH method request? +func (input *BeegoInput) IsPatch() bool { + return (*context.BeegoInput)(input).IsPatch() +} + +// IsAjax returns boolean of this request is generated by ajax. +func (input *BeegoInput) IsAjax() bool { + return (*context.BeegoInput)(input).IsAjax() +} + +// IsSecure returns boolean of this request is in https. +func (input *BeegoInput) IsSecure() bool { + return (*context.BeegoInput)(input).IsSecure() +} + +// IsWebsocket returns boolean of this request is in webSocket. +func (input *BeegoInput) IsWebsocket() bool { + return (*context.BeegoInput)(input).IsWebsocket() +} + +// IsUpload returns boolean of whether file uploads in this request or not.. +func (input *BeegoInput) IsUpload() bool { + return (*context.BeegoInput)(input).IsUpload() +} + +// AcceptsHTML Checks if request accepts html response +func (input *BeegoInput) AcceptsHTML() bool { + return (*context.BeegoInput)(input).AcceptsHTML() +} + +// AcceptsXML Checks if request accepts xml response +func (input *BeegoInput) AcceptsXML() bool { + return (*context.BeegoInput)(input).AcceptsXML() +} + +// AcceptsJSON Checks if request accepts json response +func (input *BeegoInput) AcceptsJSON() bool { + return (*context.BeegoInput)(input).AcceptsJSON() +} + +// AcceptsYAML Checks if request accepts json response +func (input *BeegoInput) AcceptsYAML() bool { + return (*context.BeegoInput)(input).AcceptsYAML() +} + +// IP returns request client ip. +// if in proxy, return first proxy id. +// if error, return RemoteAddr. +func (input *BeegoInput) IP() string { + return (*context.BeegoInput)(input).IP() +} + +// Proxy returns proxy client ips slice. +func (input *BeegoInput) Proxy() []string { + return (*context.BeegoInput)(input).Proxy() +} + +// Referer returns http referer header. +func (input *BeegoInput) Referer() string { + return (*context.BeegoInput)(input).Referer() +} + +// Refer returns http referer header. +func (input *BeegoInput) Refer() string { + return (*context.BeegoInput)(input).Refer() +} + +// SubDomains returns sub domain string. +// if aa.bb.domain.com, returns aa.bb . +func (input *BeegoInput) SubDomains() string { + return (*context.BeegoInput)(input).SubDomains() +} + +// Port returns request client port. +// when error or empty, return 80. +func (input *BeegoInput) Port() int { + return (*context.BeegoInput)(input).Port() +} + +// UserAgent returns request client user agent string. +func (input *BeegoInput) UserAgent() string { + return (*context.BeegoInput)(input).UserAgent() +} + +// ParamsLen return the length of the params +func (input *BeegoInput) ParamsLen() int { + return (*context.BeegoInput)(input).ParamsLen() +} + +// Param returns router param by a given key. +func (input *BeegoInput) Param(key string) string { + return (*context.BeegoInput)(input).Param(key) +} + +// Params returns the map[key]value. +func (input *BeegoInput) Params() map[string]string { + return (*context.BeegoInput)(input).Params() +} + +// SetParam will set the param with key and value +func (input *BeegoInput) SetParam(key, val string) { + (*context.BeegoInput)(input).SetParam(key, val) +} + +// ResetParams clears any of the input's Params +// This function is used to clear parameters so they may be reset between filter +// passes. +func (input *BeegoInput) ResetParams() { + (*context.BeegoInput)(input).ResetParams() +} + +// Query returns input data item string by a given string. +func (input *BeegoInput) Query(key string) string { + return (*context.BeegoInput)(input).Query(key) +} + +// Header returns request header item string by a given string. +// if non-existed, return empty string. +func (input *BeegoInput) Header(key string) string { + return (*context.BeegoInput)(input).Header(key) +} + +// Cookie returns request cookie item string by a given key. +// if non-existed, return empty string. +func (input *BeegoInput) Cookie(key string) string { + return (*context.BeegoInput)(input).Cookie(key) +} + +// Session returns current session item value by a given key. +// if non-existed, return nil. +func (input *BeegoInput) Session(key interface{}) interface{} { + return (*context.BeegoInput)(input).Session(key) +} + +// CopyBody returns the raw request body data as bytes. +func (input *BeegoInput) CopyBody(MaxMemory int64) []byte { + return (*context.BeegoInput)(input).CopyBody(MaxMemory) +} + +// Data return the implicit data in the input +func (input *BeegoInput) Data() map[interface{}]interface{} { + return (*context.BeegoInput)(input).Data() +} + +// GetData returns the stored data in this context. +func (input *BeegoInput) GetData(key interface{}) interface{} { + return (*context.BeegoInput)(input).GetData(key) +} + +// SetData stores data with given key in this context. +// This data are only available in this context. +func (input *BeegoInput) SetData(key, val interface{}) { + (*context.BeegoInput)(input).SetData(key, val) +} + +// ParseFormOrMulitForm parseForm or parseMultiForm based on Content-type +func (input *BeegoInput) ParseFormOrMulitForm(maxMemory int64) error { + return (*context.BeegoInput)(input).ParseFormOrMultiForm(maxMemory) +} + +// Bind data from request.Form[key] to dest +// like /?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie +// var id int beegoInput.Bind(&id, "id") id ==123 +// var isok bool beegoInput.Bind(&isok, "isok") isok ==true +// var ft float64 beegoInput.Bind(&ft, "ft") ft ==1.2 +// ol := make([]int, 0, 2) beegoInput.Bind(&ol, "ol") ol ==[1 2] +// ul := make([]string, 0, 2) beegoInput.Bind(&ul, "ul") ul ==[str array] +// user struct{Name} beegoInput.Bind(&user, "user") user == {Name:"astaxie"} +func (input *BeegoInput) Bind(dest interface{}, key string) error { + return (*context.BeegoInput)(input).Bind(dest, key) +} diff --git a/adapter/context/output.go b/adapter/context/output.go new file mode 100644 index 00000000..0223679b --- /dev/null +++ b/adapter/context/output.go @@ -0,0 +1,154 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 context + +import ( + "github.com/astaxie/beego/server/web/context" +) + +// BeegoOutput does work for sending response header. +type BeegoOutput context.BeegoOutput + +// NewOutput returns new BeegoOutput. +// it contains nothing now. +func NewOutput() *BeegoOutput { + return (*BeegoOutput)(context.NewOutput()) +} + +// Reset init BeegoOutput +func (output *BeegoOutput) Reset(ctx *Context) { + (*context.BeegoOutput)(output).Reset((*context.Context)(ctx)) +} + +// Header sets response header item string via given key. +func (output *BeegoOutput) Header(key, val string) { + (*context.BeegoOutput)(output).Header(key, val) +} + +// Body sets response body content. +// if EnableGzip, compress content string. +// it sends out response body directly. +func (output *BeegoOutput) Body(content []byte) error { + return (*context.BeegoOutput)(output).Body(content) +} + +// Cookie sets cookie value via given key. +// others are ordered as cookie's max age time, path,domain, secure and httponly. +func (output *BeegoOutput) Cookie(name string, value string, others ...interface{}) { + (*context.BeegoOutput)(output).Cookie(name, value, others) +} + +// JSON writes json to response body. +// if encoding is true, it converts utf-8 to \u0000 type. +func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) error { + return (*context.BeegoOutput)(output).JSON(data, hasIndent, encoding) +} + +// YAML writes yaml to response body. +func (output *BeegoOutput) YAML(data interface{}) error { + return (*context.BeegoOutput)(output).YAML(data) +} + +// JSONP writes jsonp to response body. +func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error { + return (*context.BeegoOutput)(output).JSONP(data, hasIndent) +} + +// XML writes xml string to response body. +func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error { + return (*context.BeegoOutput)(output).XML(data, hasIndent) +} + +// ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header +func (output *BeegoOutput) ServeFormatted(data interface{}, hasIndent bool, hasEncode ...bool) { + (*context.BeegoOutput)(output).ServeFormatted(data, hasIndent, hasEncode...) +} + +// Download forces response for download file. +// it prepares the download response header automatically. +func (output *BeegoOutput) Download(file string, filename ...string) { + (*context.BeegoOutput)(output).Download(file, filename...) +} + +// ContentType sets the content type from ext string. +// MIME type is given in mime package. +func (output *BeegoOutput) ContentType(ext string) { + (*context.BeegoOutput)(output).ContentType(ext) +} + +// SetStatus sets response status code. +// It writes response header directly. +func (output *BeegoOutput) SetStatus(status int) { + (*context.BeegoOutput)(output).SetStatus(status) +} + +// IsCachable returns boolean of this request is cached. +// HTTP 304 means cached. +func (output *BeegoOutput) IsCachable() bool { + return (*context.BeegoOutput)(output).IsCachable() +} + +// IsEmpty returns boolean of this request is empty. +// HTTP 201,204 and 304 means empty. +func (output *BeegoOutput) IsEmpty() bool { + return (*context.BeegoOutput)(output).IsEmpty() +} + +// IsOk returns boolean of this request runs well. +// HTTP 200 means ok. +func (output *BeegoOutput) IsOk() bool { + return (*context.BeegoOutput)(output).IsOk() +} + +// IsSuccessful returns boolean of this request runs successfully. +// HTTP 2xx means ok. +func (output *BeegoOutput) IsSuccessful() bool { + return (*context.BeegoOutput)(output).IsSuccessful() +} + +// IsRedirect returns boolean of this request is redirection header. +// HTTP 301,302,307 means redirection. +func (output *BeegoOutput) IsRedirect() bool { + return (*context.BeegoOutput)(output).IsRedirect() +} + +// IsForbidden returns boolean of this request is forbidden. +// HTTP 403 means forbidden. +func (output *BeegoOutput) IsForbidden() bool { + return (*context.BeegoOutput)(output).IsForbidden() +} + +// IsNotFound returns boolean of this request is not found. +// HTTP 404 means not found. +func (output *BeegoOutput) IsNotFound() bool { + return (*context.BeegoOutput)(output).IsNotFound() +} + +// IsClientError returns boolean of this request client sends error data. +// HTTP 4xx means client error. +func (output *BeegoOutput) IsClientError() bool { + return (*context.BeegoOutput)(output).IsClientError() +} + +// IsServerError returns boolean of this server handler errors. +// HTTP 5xx means server internal error. +func (output *BeegoOutput) IsServerError() bool { + return (*context.BeegoOutput)(output).IsServerError() +} + +// Session sets session item value with given key. +func (output *BeegoOutput) Session(name interface{}, value interface{}) { + (*context.BeegoOutput)(output).Session(name, value) +} diff --git a/adapter/context/renderer.go b/adapter/context/renderer.go new file mode 100644 index 00000000..1309365a --- /dev/null +++ b/adapter/context/renderer.go @@ -0,0 +1,8 @@ +package context + +import ( + "github.com/astaxie/beego/server/web/context" +) + +// Renderer defines an http response renderer +type Renderer context.Renderer diff --git a/context/response.go b/adapter/context/response.go similarity index 83% rename from context/response.go rename to adapter/context/response.go index 9c3c715a..24e196a4 100644 --- a/context/response.go +++ b/adapter/context/response.go @@ -1,16 +1,15 @@ package context import ( - "strconv" - "net/http" + "strconv" ) const ( - //BadRequest indicates http error 400 + // BadRequest indicates http error 400 BadRequest StatusCode = http.StatusBadRequest - //NotFound indicates http error 404 + // NotFound indicates http error 404 NotFound StatusCode = http.StatusNotFound ) diff --git a/adapter/controller.go b/adapter/controller.go new file mode 100644 index 00000000..14dc9b97 --- /dev/null +++ b/adapter/controller.go @@ -0,0 +1,399 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 adapter + +import ( + "mime/multipart" + "net/url" + + "github.com/astaxie/beego/adapter/session" + webContext "github.com/astaxie/beego/server/web/context" + + "github.com/astaxie/beego/server/web" +) + +var ( + // ErrAbort custom error when user stop request handler manually. + ErrAbort = web.ErrAbort + // GlobalControllerRouter store comments with controller. pkgpath+controller:comments + GlobalControllerRouter = web.GlobalControllerRouter +) + +// ControllerFilter store the filter for controller +type ControllerFilter web.ControllerFilter + +// ControllerFilterComments store the comment for controller level filter +type ControllerFilterComments web.ControllerFilterComments + +// ControllerImportComments store the import comment for controller needed +type ControllerImportComments web.ControllerImportComments + +// ControllerComments store the comment for the controller method +type ControllerComments web.ControllerComments + +// ControllerCommentsSlice implements the sort interface +type ControllerCommentsSlice web.ControllerCommentsSlice + +func (p ControllerCommentsSlice) Len() int { + return (web.ControllerCommentsSlice)(p).Len() +} +func (p ControllerCommentsSlice) Less(i, j int) bool { + return (web.ControllerCommentsSlice)(p).Less(i, j) +} +func (p ControllerCommentsSlice) Swap(i, j int) { + (web.ControllerCommentsSlice)(p).Swap(i, j) +} + +// Controller defines some basic http request handler operations, such as +// http context, template and view, session and xsrf. +type Controller web.Controller + +func (c *Controller) Init(ctx *webContext.Context, controllerName, actionName string, app interface{}) { + (*web.Controller)(c).Init(ctx, controllerName, actionName, app) +} + +// ControllerInterface is an interface to uniform all controller handler. +type ControllerInterface web.ControllerInterface + +// Prepare runs after Init before request function execution. +func (c *Controller) Prepare() { + (*web.Controller)(c).Prepare() +} + +// Finish runs after request function execution. +func (c *Controller) Finish() { + (*web.Controller)(c).Finish() +} + +// Get adds a request function to handle GET request. +func (c *Controller) Get() { + (*web.Controller)(c).Get() +} + +// Post adds a request function to handle POST request. +func (c *Controller) Post() { + (*web.Controller)(c).Post() +} + +// Delete adds a request function to handle DELETE request. +func (c *Controller) Delete() { + (*web.Controller)(c).Delete() +} + +// Put adds a request function to handle PUT request. +func (c *Controller) Put() { + (*web.Controller)(c).Put() +} + +// Head adds a request function to handle HEAD request. +func (c *Controller) Head() { + (*web.Controller)(c).Head() +} + +// Patch adds a request function to handle PATCH request. +func (c *Controller) Patch() { + (*web.Controller)(c).Patch() +} + +// Options adds a request function to handle OPTIONS request. +func (c *Controller) Options() { + (*web.Controller)(c).Options() +} + +// Trace adds a request function to handle Trace request. +// this method SHOULD NOT be overridden. +// https://tools.ietf.org/html/rfc7231#section-4.3.8 +// The TRACE method requests a remote, application-level loop-back of +// the request message. The final recipient of the request SHOULD +// reflect the message received, excluding some fields described below, +// back to the client as the message body of a 200 (OK) response with a +// Content-Type of "message/http" (Section 8.3.1 of [RFC7230]). +func (c *Controller) Trace() { + (*web.Controller)(c).Trace() +} + +// HandlerFunc call function with the name +func (c *Controller) HandlerFunc(fnname string) bool { + return (*web.Controller)(c).HandlerFunc(fnname) +} + +// URLMapping register the internal Controller router. +func (c *Controller) URLMapping() { + (*web.Controller)(c).URLMapping() +} + +// Mapping the method to function +func (c *Controller) Mapping(method string, fn func()) { + (*web.Controller)(c).Mapping(method, fn) +} + +// Render sends the response with rendered template bytes as text/html type. +func (c *Controller) Render() error { + return (*web.Controller)(c).Render() +} + +// RenderString returns the rendered template string. Do not send out response. +func (c *Controller) RenderString() (string, error) { + return (*web.Controller)(c).RenderString() +} + +// RenderBytes returns the bytes of rendered template string. Do not send out response. +func (c *Controller) RenderBytes() ([]byte, error) { + return (*web.Controller)(c).RenderBytes() +} + +// Redirect sends the redirection response to url with status code. +func (c *Controller) Redirect(url string, code int) { + (*web.Controller)(c).Redirect(url, code) +} + +// SetData set the data depending on the accepted +func (c *Controller) SetData(data interface{}) { + (*web.Controller)(c).SetData(data) +} + +// Abort stops controller handler and show the error data if code is defined in ErrorMap or code string. +func (c *Controller) Abort(code string) { + (*web.Controller)(c).Abort(code) +} + +// CustomAbort stops controller handler and show the error data, it's similar Aborts, but support status code and body. +func (c *Controller) CustomAbort(status int, body string) { + (*web.Controller)(c).CustomAbort(status, body) +} + +// StopRun makes panic of USERSTOPRUN error and go to recover function if defined. +func (c *Controller) StopRun() { + (*web.Controller)(c).StopRun() +} + +// URLFor does another controller handler in this request function. +// it goes to this controller method if endpoint is not clear. +func (c *Controller) URLFor(endpoint string, values ...interface{}) string { + return (*web.Controller)(c).URLFor(endpoint, values...) +} + +// ServeJSON sends a json response with encoding charset. +func (c *Controller) ServeJSON(encoding ...bool) { + (*web.Controller)(c).ServeJSON(encoding...) +} + +// ServeJSONP sends a jsonp response. +func (c *Controller) ServeJSONP() { + (*web.Controller)(c).ServeJSONP() +} + +// ServeXML sends xml response. +func (c *Controller) ServeXML() { + (*web.Controller)(c).ServeXML() +} + +// ServeYAML sends yaml response. +func (c *Controller) ServeYAML() { + (*web.Controller)(c).ServeYAML() +} + +// ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header +func (c *Controller) ServeFormatted(encoding ...bool) { + (*web.Controller)(c).ServeFormatted(encoding...) +} + +// Input returns the input data map from POST or PUT request body and query string. +func (c *Controller) Input() url.Values { + return (*web.Controller)(c).Input() +} + +// ParseForm maps input data map to obj struct. +func (c *Controller) ParseForm(obj interface{}) error { + return (*web.Controller)(c).ParseForm(obj) +} + +// GetString returns the input value by key string or the default value while it's present and input is blank +func (c *Controller) GetString(key string, def ...string) string { + return (*web.Controller)(c).GetString(key, def...) +} + +// GetStrings returns the input string slice by key string or the default value while it's present and input is blank +// it's designed for multi-value input field such as checkbox(input[type=checkbox]), multi-selection. +func (c *Controller) GetStrings(key string, def ...[]string) []string { + return (*web.Controller)(c).GetStrings(key, def...) +} + +// GetInt returns input as an int or the default value while it's present and input is blank +func (c *Controller) GetInt(key string, def ...int) (int, error) { + return (*web.Controller)(c).GetInt(key, def...) +} + +// GetInt8 return input as an int8 or the default value while it's present and input is blank +func (c *Controller) GetInt8(key string, def ...int8) (int8, error) { + return (*web.Controller)(c).GetInt8(key, def...) +} + +// GetUint8 return input as an uint8 or the default value while it's present and input is blank +func (c *Controller) GetUint8(key string, def ...uint8) (uint8, error) { + return (*web.Controller)(c).GetUint8(key, def...) +} + +// GetInt16 returns input as an int16 or the default value while it's present and input is blank +func (c *Controller) GetInt16(key string, def ...int16) (int16, error) { + return (*web.Controller)(c).GetInt16(key, def...) +} + +// GetUint16 returns input as an uint16 or the default value while it's present and input is blank +func (c *Controller) GetUint16(key string, def ...uint16) (uint16, error) { + return (*web.Controller)(c).GetUint16(key, def...) +} + +// GetInt32 returns input as an int32 or the default value while it's present and input is blank +func (c *Controller) GetInt32(key string, def ...int32) (int32, error) { + return (*web.Controller)(c).GetInt32(key, def...) +} + +// GetUint32 returns input as an uint32 or the default value while it's present and input is blank +func (c *Controller) GetUint32(key string, def ...uint32) (uint32, error) { + return (*web.Controller)(c).GetUint32(key, def...) +} + +// GetInt64 returns input value as int64 or the default value while it's present and input is blank. +func (c *Controller) GetInt64(key string, def ...int64) (int64, error) { + return (*web.Controller)(c).GetInt64(key, def...) +} + +// GetUint64 returns input value as uint64 or the default value while it's present and input is blank. +func (c *Controller) GetUint64(key string, def ...uint64) (uint64, error) { + return (*web.Controller)(c).GetUint64(key, def...) +} + +// GetBool returns input value as bool or the default value while it's present and input is blank. +func (c *Controller) GetBool(key string, def ...bool) (bool, error) { + return (*web.Controller)(c).GetBool(key, def...) +} + +// GetFloat returns input value as float64 or the default value while it's present and input is blank. +func (c *Controller) GetFloat(key string, def ...float64) (float64, error) { + return (*web.Controller)(c).GetFloat(key, def...) +} + +// GetFile returns the file data in file upload field named as key. +// it returns the first one of multi-uploaded files. +func (c *Controller) GetFile(key string) (multipart.File, *multipart.FileHeader, error) { + return (*web.Controller)(c).GetFile(key) +} + +// GetFiles return multi-upload files +// files, err:=c.GetFiles("myfiles") +// if err != nil { +// http.Error(w, err.Error(), http.StatusNoContent) +// return +// } +// for i, _ := range files { +// //for each fileheader, get a handle to the actual file +// file, err := files[i].Open() +// defer file.Close() +// if err != nil { +// http.Error(w, err.Error(), http.StatusInternalServerError) +// return +// } +// //create destination file making sure the path is writeable. +// dst, err := os.Create("upload/" + files[i].Filename) +// defer dst.Close() +// if err != nil { +// http.Error(w, err.Error(), http.StatusInternalServerError) +// return +// } +// //copy the uploaded file to the destination file +// if _, err := io.Copy(dst, file); err != nil { +// http.Error(w, err.Error(), http.StatusInternalServerError) +// return +// } +// } +func (c *Controller) GetFiles(key string) ([]*multipart.FileHeader, error) { + return (*web.Controller)(c).GetFiles(key) +} + +// SaveToFile saves uploaded file to new path. +// it only operates the first one of mutil-upload form file field. +func (c *Controller) SaveToFile(fromfile, tofile string) error { + return (*web.Controller)(c).SaveToFile(fromfile, tofile) +} + +// StartSession starts session and load old session data info this controller. +func (c *Controller) StartSession() session.Store { + s := (*web.Controller)(c).StartSession() + return session.CreateNewToOldStoreAdapter(s) +} + +// SetSession puts value into session. +func (c *Controller) SetSession(name interface{}, value interface{}) { + (*web.Controller)(c).SetSession(name, value) +} + +// GetSession gets value from session. +func (c *Controller) GetSession(name interface{}) interface{} { + return (*web.Controller)(c).GetSession(name) +} + +// DelSession removes value from session. +func (c *Controller) DelSession(name interface{}) { + (*web.Controller)(c).DelSession(name) +} + +// SessionRegenerateID regenerates session id for this session. +// the session data have no changes. +func (c *Controller) SessionRegenerateID() { + (*web.Controller)(c).SessionRegenerateID() +} + +// DestroySession cleans session data and session cookie. +func (c *Controller) DestroySession() { + (*web.Controller)(c).DestroySession() +} + +// IsAjax returns this request is ajax or not. +func (c *Controller) IsAjax() bool { + return (*web.Controller)(c).IsAjax() +} + +// GetSecureCookie returns decoded cookie value from encoded browser cookie values. +func (c *Controller) GetSecureCookie(Secret, key string) (string, bool) { + return (*web.Controller)(c).GetSecureCookie(Secret, key) +} + +// SetSecureCookie puts value into cookie after encoded the value. +func (c *Controller) SetSecureCookie(Secret, name, value string, others ...interface{}) { + (*web.Controller)(c).SetSecureCookie(Secret, name, value, others...) +} + +// XSRFToken creates a CSRF token string and returns. +func (c *Controller) XSRFToken() string { + return (*web.Controller)(c).XSRFToken() +} + +// CheckXSRFCookie checks xsrf token in this request is valid or not. +// the token can provided in request header "X-Xsrftoken" and "X-CsrfToken" +// or in form field value named as "_xsrf". +func (c *Controller) CheckXSRFCookie() bool { + return (*web.Controller)(c).CheckXSRFCookie() +} + +// XSRFFormHTML writes an input field contains xsrf token value. +func (c *Controller) XSRFFormHTML() string { + return (*web.Controller)(c).XSRFFormHTML() +} + +// GetControllerAndAction gets the executing controller name and action name. +func (c *Controller) GetControllerAndAction() (string, string) { + return (*web.Controller)(c).GetControllerAndAction() +} diff --git a/adapter/doc.go b/adapter/doc.go new file mode 100644 index 00000000..c8f2174c --- /dev/null +++ b/adapter/doc.go @@ -0,0 +1,16 @@ +// Copyright 2020 +// +// Licensed 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. + +// used to keep compatible with v1.x +package adapter diff --git a/adapter/error.go b/adapter/error.go new file mode 100644 index 00000000..35ff7f35 --- /dev/null +++ b/adapter/error.go @@ -0,0 +1,202 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 adapter + +import ( + "net/http" + + "github.com/astaxie/beego/adapter/context" + beecontext "github.com/astaxie/beego/server/web/context" + + "github.com/astaxie/beego/server/web" +) + +const ( + errorTypeHandler = iota + errorTypeController +) + +var tpl = ` + + + + + beego application error + + + + + +
+ + + + + + + + + + +
Request Method: {{.RequestMethod}}
Request URL: {{.RequestURL}}
RemoteAddr: {{.RemoteAddr }}
+
+ Stack +
{{.Stack}}
+
+
+ + + +` + +var errtpl = ` + + + + + {{.Title}} + + + +
+
+ +
+ {{.Content}} + Go Home
+ +
Powered by beego {{.BeegoVersion}} +
+
+
+ + +` + +// ErrorMaps holds map of http handlers for each error string. +// there is 10 kinds default error(40x and 50x) +var ErrorMaps = web.ErrorMaps + +// ErrorHandler registers http.HandlerFunc to each http err code string. +// usage: +// beego.ErrorHandler("404",NotFound) +// beego.ErrorHandler("500",InternalServerError) +func ErrorHandler(code string, h http.HandlerFunc) *App { + return (*App)(web.ErrorHandler(code, h)) +} + +// ErrorController registers ControllerInterface to each http err code string. +// usage: +// beego.ErrorController(&controllers.ErrorController{}) +func ErrorController(c ControllerInterface) *App { + return (*App)(web.ErrorController(c)) +} + +// Exception Write HttpStatus with errCode and Exec error handler if exist. +func Exception(errCode uint64, ctx *context.Context) { + web.Exception(errCode, (*beecontext.Context)(ctx)) +} diff --git a/filter.go b/adapter/filter.go similarity index 79% rename from filter.go rename to adapter/filter.go index 9cc6e913..283d8879 100644 --- a/filter.go +++ b/adapter/filter.go @@ -12,9 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package adapter -import "github.com/astaxie/beego/context" +import ( + "github.com/astaxie/beego/adapter/context" + "github.com/astaxie/beego/server/web" + beecontext "github.com/astaxie/beego/server/web/context" +) // FilterFunc defines a filter function which is invoked before the controller handler is executed. type FilterFunc func(*context.Context) @@ -22,23 +26,11 @@ type FilterFunc func(*context.Context) // FilterRouter defines a filter operation which is invoked before the controller handler is executed. // It can match the URL against a pattern, and execute a filter function // when a request with a matching URL arrives. -type FilterRouter struct { - filterFunc FilterFunc - tree *Tree - pattern string - returnOnOutput bool - resetParams bool -} +type FilterRouter web.FilterRouter // ValidRouter checks if the current request is matched by this filter. // If the request is matched, the values of the URL parameters defined // by the filter pattern are also returned. func (f *FilterRouter) ValidRouter(url string, ctx *context.Context) bool { - isOk := f.tree.Match(url, ctx) - if isOk != nil { - if b, ok := isOk.(bool); ok { - return b - } - } - return false + return (*web.FilterRouter)(f).ValidRouter(url, (*beecontext.Context)(ctx)) } diff --git a/adapter/flash.go b/adapter/flash.go new file mode 100644 index 00000000..2b47ee62 --- /dev/null +++ b/adapter/flash.go @@ -0,0 +1,63 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 adapter + +import ( + "github.com/astaxie/beego/server/web" +) + +// FlashData is a tools to maintain data when using across request. +type FlashData web.FlashData + +// NewFlash return a new empty FlashData struct. +func NewFlash() *FlashData { + return (*FlashData)(web.NewFlash()) +} + +// Set message to flash +func (fd *FlashData) Set(key string, msg string, args ...interface{}) { + (*web.FlashData)(fd).Set(key, msg, args...) +} + +// Success writes success message to flash. +func (fd *FlashData) Success(msg string, args ...interface{}) { + (*web.FlashData)(fd).Success(msg, args...) +} + +// Notice writes notice message to flash. +func (fd *FlashData) Notice(msg string, args ...interface{}) { + (*web.FlashData)(fd).Notice(msg, args...) +} + +// Warning writes warning message to flash. +func (fd *FlashData) Warning(msg string, args ...interface{}) { + (*web.FlashData)(fd).Warning(msg, args...) +} + +// Error writes error message to flash. +func (fd *FlashData) Error(msg string, args ...interface{}) { + (*web.FlashData)(fd).Error(msg, args...) +} + +// Store does the saving operation of flash data. +// the data are encoded and saved in cookie. +func (fd *FlashData) Store(c *Controller) { + (*web.FlashData)(fd).Store((*web.Controller)(c)) +} + +// ReadFromRequest parsed flash data from encoded values in cookie. +func ReadFromRequest(c *Controller) *FlashData { + return (*FlashData)(web.ReadFromRequest((*web.Controller)(c))) +} diff --git a/adapter/fs.go b/adapter/fs.go new file mode 100644 index 00000000..e48e75b5 --- /dev/null +++ b/adapter/fs.go @@ -0,0 +1,35 @@ +// Copyright 2020 +// +// Licensed 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 adapter + +import ( + "net/http" + "path/filepath" + + "github.com/astaxie/beego/server/web" +) + +type FileSystem web.FileSystem + +func (d FileSystem) Open(name string) (http.File, error) { + return (web.FileSystem)(d).Open(name) +} + +// Walk walks the file tree rooted at root in filesystem, calling walkFn for each file or +// directory in the tree, including root. All errors that arise visiting files +// and directories are filtered by walkFn. +func Walk(fs http.FileSystem, root string, walkFn filepath.WalkFunc) error { + return web.Walk(fs, root, walkFn) +} diff --git a/adapter/grace/grace.go b/adapter/grace/grace.go new file mode 100644 index 00000000..75ceef21 --- /dev/null +++ b/adapter/grace/grace.go @@ -0,0 +1,94 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 grace use to hot reload +// Description: http://grisha.org/blog/2014/06/03/graceful-restart-in-golang/ +// +// Usage: +// +// import( +// "log" +// "net/http" +// "os" +// +// "github.com/astaxie/beego/grace" +// ) +// +// func handler(w http.ResponseWriter, r *http.Request) { +// w.Write([]byte("WORLD!")) +// } +// +// func main() { +// mux := http.NewServeMux() +// mux.HandleFunc("/hello", handler) +// +// err := grace.ListenAndServe("localhost:8080", mux) +// if err != nil { +// log.Println(err) +// } +// log.Println("Server on 8080 stopped") +// os.Exit(0) +// } +package grace + +import ( + "net/http" + "time" + + "github.com/astaxie/beego/server/web/grace" +) + +const ( + // PreSignal is the position to add filter before signal + PreSignal = iota + // PostSignal is the position to add filter after signal + PostSignal + // StateInit represent the application inited + StateInit + // StateRunning represent the application is running + StateRunning + // StateShuttingDown represent the application is shutting down + StateShuttingDown + // StateTerminate represent the application is killed + StateTerminate +) + +var ( + + // DefaultReadTimeOut is the HTTP read timeout + DefaultReadTimeOut time.Duration + // DefaultWriteTimeOut is the HTTP Write timeout + DefaultWriteTimeOut time.Duration + // DefaultMaxHeaderBytes is the Max HTTP Header size, default is 0, no limit + DefaultMaxHeaderBytes int + // DefaultTimeout is the shutdown server's timeout. default is 60s + DefaultTimeout = grace.DefaultTimeout +) + +// NewServer returns a new graceServer. +func NewServer(addr string, handler http.Handler) (srv *Server) { + return (*Server)(grace.NewServer(addr, handler)) +} + +// ListenAndServe refer http.ListenAndServe +func ListenAndServe(addr string, handler http.Handler) error { + server := NewServer(addr, handler) + return server.ListenAndServe() +} + +// ListenAndServeTLS refer http.ListenAndServeTLS +func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error { + server := NewServer(addr, handler) + return server.ListenAndServeTLS(certFile, keyFile) +} diff --git a/adapter/grace/server.go b/adapter/grace/server.go new file mode 100644 index 00000000..0dfb2fd6 --- /dev/null +++ b/adapter/grace/server.go @@ -0,0 +1,48 @@ +package grace + +import ( + "os" + + "github.com/astaxie/beego/server/web/grace" +) + +// Server embedded http.Server +type Server grace.Server + +// Serve accepts incoming connections on the Listener l, +// creating a new service goroutine for each. +// The service goroutines read requests and then call srv.Handler to reply to them. +func (srv *Server) Serve() (err error) { + return (*grace.Server)(srv).Serve() +} + +// ListenAndServe listens on the TCP network address srv.Addr and then calls Serve +// to handle requests on incoming connections. If srv.Addr is blank, ":http" is +// used. +func (srv *Server) ListenAndServe() (err error) { + return (*grace.Server)(srv).ListenAndServe() +} + +// ListenAndServeTLS listens on the TCP network address srv.Addr and then calls +// Serve to handle requests on incoming TLS connections. +// +// Filenames containing a certificate and matching private key for the server must +// be provided. If the certificate is signed by a certificate authority, the +// certFile should be the concatenation of the server's certificate followed by the +// CA's certificate. +// +// If srv.Addr is blank, ":https" is used. +func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error { + return (*grace.Server)(srv).ListenAndServeTLS(certFile, keyFile) +} + +// ListenAndServeMutualTLS listens on the TCP network address srv.Addr and then calls +// Serve to handle requests on incoming mutual TLS connections. +func (srv *Server) ListenAndServeMutualTLS(certFile, keyFile, trustFile string) error { + return (*grace.Server)(srv).ListenAndServeMutualTLS(certFile, keyFile, trustFile) +} + +// RegisterSignalHook registers a function to be run PreSignal or PostSignal for a given signal. +func (srv *Server) RegisterSignalHook(ppFlag int, sig os.Signal, f func()) error { + return (*grace.Server)(srv).RegisterSignalHook(ppFlag, sig, f) +} diff --git a/adapter/httplib/httplib.go b/adapter/httplib/httplib.go new file mode 100644 index 00000000..d9ff1ea5 --- /dev/null +++ b/adapter/httplib/httplib.go @@ -0,0 +1,300 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 httplib is used as http.Client +// Usage: +// +// import "github.com/astaxie/beego/httplib" +// +// b := httplib.Post("http://beego.me/") +// b.Param("username","astaxie") +// b.Param("password","123456") +// b.PostFile("uploadfile1", "httplib.pdf") +// b.PostFile("uploadfile2", "httplib.txt") +// str, err := b.String() +// if err != nil { +// t.Fatal(err) +// } +// fmt.Println(str) +// +// more docs http://beego.me/docs/module/httplib.md +package httplib + +import ( + "crypto/tls" + "net" + "net/http" + "net/url" + "time" + + "github.com/astaxie/beego/client/httplib" +) + +// SetDefaultSetting Overwrite default settings +func SetDefaultSetting(setting BeegoHTTPSettings) { + httplib.SetDefaultSetting(httplib.BeegoHTTPSettings(setting)) +} + +// NewBeegoRequest return *BeegoHttpRequest with specific method +func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest { + return &BeegoHTTPRequest{ + delegate: httplib.NewBeegoRequest(rawurl, method), + } +} + +// Get returns *BeegoHttpRequest with GET method. +func Get(url string) *BeegoHTTPRequest { + return NewBeegoRequest(url, "GET") +} + +// Post returns *BeegoHttpRequest with POST method. +func Post(url string) *BeegoHTTPRequest { + return NewBeegoRequest(url, "POST") +} + +// Put returns *BeegoHttpRequest with PUT method. +func Put(url string) *BeegoHTTPRequest { + return NewBeegoRequest(url, "PUT") +} + +// Delete returns *BeegoHttpRequest DELETE method. +func Delete(url string) *BeegoHTTPRequest { + return NewBeegoRequest(url, "DELETE") +} + +// Head returns *BeegoHttpRequest with HEAD method. +func Head(url string) *BeegoHTTPRequest { + return NewBeegoRequest(url, "HEAD") +} + +// BeegoHTTPSettings is the http.Client setting +type BeegoHTTPSettings httplib.BeegoHTTPSettings + +// BeegoHTTPRequest provides more useful methods for requesting one url than http.Request. +type BeegoHTTPRequest struct { + delegate *httplib.BeegoHTTPRequest +} + +// GetRequest return the request object +func (b *BeegoHTTPRequest) GetRequest() *http.Request { + return b.delegate.GetRequest() +} + +// Setting Change request settings +func (b *BeegoHTTPRequest) Setting(setting BeegoHTTPSettings) *BeegoHTTPRequest { + b.delegate.Setting(httplib.BeegoHTTPSettings(setting)) + return b +} + +// SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password. +func (b *BeegoHTTPRequest) SetBasicAuth(username, password string) *BeegoHTTPRequest { + b.delegate.SetBasicAuth(username, password) + return b +} + +// SetEnableCookie sets enable/disable cookiejar +func (b *BeegoHTTPRequest) SetEnableCookie(enable bool) *BeegoHTTPRequest { + b.delegate.SetEnableCookie(enable) + return b +} + +// SetUserAgent sets User-Agent header field +func (b *BeegoHTTPRequest) SetUserAgent(useragent string) *BeegoHTTPRequest { + b.delegate.SetUserAgent(useragent) + return b +} + +// Debug sets show debug or not when executing request. +func (b *BeegoHTTPRequest) Debug(isdebug bool) *BeegoHTTPRequest { + b.delegate.Debug(isdebug) + return b +} + +// Retries sets Retries times. +// default is 0 means no retried. +// -1 means retried forever. +// others means retried times. +func (b *BeegoHTTPRequest) Retries(times int) *BeegoHTTPRequest { + b.delegate.Retries(times) + return b +} + +func (b *BeegoHTTPRequest) RetryDelay(delay time.Duration) *BeegoHTTPRequest { + b.delegate.RetryDelay(delay) + return b +} + +// DumpBody setting whether need to Dump the Body. +func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest { + b.delegate.DumpBody(isdump) + return b +} + +// DumpRequest return the DumpRequest +func (b *BeegoHTTPRequest) DumpRequest() []byte { + return b.delegate.DumpRequest() +} + +// SetTimeout sets connect time out and read-write time out for BeegoRequest. +func (b *BeegoHTTPRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHTTPRequest { + b.delegate.SetTimeout(connectTimeout, readWriteTimeout) + return b +} + +// SetTLSClientConfig sets tls connection configurations if visiting https url. +func (b *BeegoHTTPRequest) SetTLSClientConfig(config *tls.Config) *BeegoHTTPRequest { + b.delegate.SetTLSClientConfig(config) + return b +} + +// Header add header item string in request. +func (b *BeegoHTTPRequest) Header(key, value string) *BeegoHTTPRequest { + b.delegate.Header(key, value) + return b +} + +// SetHost set the request host +func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest { + b.delegate.SetHost(host) + return b +} + +// SetProtocolVersion Set the protocol version for incoming requests. +// Client requests always use HTTP/1.1. +func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest { + b.delegate.SetProtocolVersion(vers) + return b +} + +// SetCookie add cookie into request. +func (b *BeegoHTTPRequest) SetCookie(cookie *http.Cookie) *BeegoHTTPRequest { + b.delegate.SetCookie(cookie) + return b +} + +// SetTransport set the setting transport +func (b *BeegoHTTPRequest) SetTransport(transport http.RoundTripper) *BeegoHTTPRequest { + b.delegate.SetTransport(transport) + return b +} + +// SetProxy set the http proxy +// example: +// +// func(req *http.Request) (*url.URL, error) { +// u, _ := url.ParseRequestURI("http://127.0.0.1:8118") +// return u, nil +// } +func (b *BeegoHTTPRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *BeegoHTTPRequest { + b.delegate.SetProxy(proxy) + return b +} + +// SetCheckRedirect specifies the policy for handling redirects. +// +// If CheckRedirect is nil, the Client uses its default policy, +// which is to stop after 10 consecutive requests. +func (b *BeegoHTTPRequest) SetCheckRedirect(redirect func(req *http.Request, via []*http.Request) error) *BeegoHTTPRequest { + b.delegate.SetCheckRedirect(redirect) + return b +} + +// Param adds query param in to request. +// params build query string as ?key1=value1&key2=value2... +func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest { + b.delegate.Param(key, value) + return b +} + +// PostFile add a post file to the request +func (b *BeegoHTTPRequest) PostFile(formname, filename string) *BeegoHTTPRequest { + b.delegate.PostFile(formname, filename) + return b +} + +// Body adds request raw body. +// it supports string and []byte. +func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest { + b.delegate.Body(data) + return b +} + +// XMLBody adds request raw body encoding by XML. +func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) { + _, err := b.delegate.XMLBody(obj) + return b, err +} + +// YAMLBody adds request raw body encoding by YAML. +func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error) { + _, err := b.delegate.YAMLBody(obj) + return b, err +} + +// JSONBody adds request raw body encoding by JSON. +func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) { + _, err := b.delegate.JSONBody(obj) + return b, err +} + +// DoRequest will do the client.Do +func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) { + return b.delegate.DoRequest() +} + +// String returns the body string in response. +// it calls Response inner. +func (b *BeegoHTTPRequest) String() (string, error) { + return b.delegate.String() +} + +// Bytes returns the body []byte in response. +// it calls Response inner. +func (b *BeegoHTTPRequest) Bytes() ([]byte, error) { + return b.delegate.Bytes() +} + +// ToFile saves the body data in response to one file. +// it calls Response inner. +func (b *BeegoHTTPRequest) ToFile(filename string) error { + return b.delegate.ToFile(filename) +} + +// ToJSON returns the map that marshals from the body bytes as json in response . +// it calls Response inner. +func (b *BeegoHTTPRequest) ToJSON(v interface{}) error { + return b.delegate.ToJSON(v) +} + +// ToXML returns the map that marshals from the body bytes as xml in response . +// it calls Response inner. +func (b *BeegoHTTPRequest) ToXML(v interface{}) error { + return b.delegate.ToXML(v) +} + +// ToYAML returns the map that marshals from the body bytes as yaml in response . +// it calls Response inner. +func (b *BeegoHTTPRequest) ToYAML(v interface{}) error { + return b.delegate.ToYAML(v) +} + +// Response executes request client gets response mannually. +func (b *BeegoHTTPRequest) Response() (*http.Response, error) { + return b.delegate.Response() +} + +// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field. +func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) { + return httplib.TimeoutDialer(cTimeout, rwTimeout) +} diff --git a/httplib/httplib_test.go b/adapter/httplib/httplib_test.go similarity index 86% rename from httplib/httplib_test.go rename to adapter/httplib/httplib_test.go index dd2a4f1c..e7605c87 100644 --- a/httplib/httplib_test.go +++ b/adapter/httplib/httplib_test.go @@ -15,6 +15,7 @@ package httplib import ( + "errors" "io/ioutil" "net" "net/http" @@ -33,6 +34,34 @@ func TestResponse(t *testing.T) { t.Log(resp) } +func TestDoRequest(t *testing.T) { + req := Get("https://goolnk.com/33BD2j") + retryAmount := 1 + req.Retries(1) + req.RetryDelay(1400 * time.Millisecond) + retryDelay := 1400 * time.Millisecond + + req.SetCheckRedirect(func(redirectReq *http.Request, redirectVia []*http.Request) error { + return errors.New("Redirect triggered") + }) + + startTime := time.Now().UnixNano() / int64(time.Millisecond) + + _, err := req.Response() + if err == nil { + t.Fatal("Response should have yielded an error") + } + + endTime := time.Now().UnixNano() / int64(time.Millisecond) + elapsedTime := endTime - startTime + delayedTime := int64(retryAmount) * retryDelay.Milliseconds() + + if elapsedTime < delayedTime { + t.Errorf("Not enough retries. Took %dms. Delay was meant to take %dms", elapsedTime, delayedTime) + } + +} + func TestGet(t *testing.T) { req := Get("http://httpbin.org/get") b, err := req.Bytes() @@ -69,7 +98,7 @@ func TestSimplePost(t *testing.T) { } } -//func TestPostFile(t *testing.T) { +// func TestPostFile(t *testing.T) { // v := "smallfish" // req := Post("http://httpbin.org/post") // req.Debug(true) @@ -86,7 +115,7 @@ func TestSimplePost(t *testing.T) { // if n == -1 { // t.Fatal(v + " not found in post") // } -//} +// } func TestSimplePut(t *testing.T) { str, err := Put("http://httpbin.org/put").String() diff --git a/log.go b/adapter/log.go similarity index 88% rename from log.go rename to adapter/log.go index cc4c0f81..9d07ec1a 100644 --- a/log.go +++ b/adapter/log.go @@ -12,25 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package adapter import ( "strings" - "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/core/logs" + + webLog "github.com/astaxie/beego/core/logs" ) // Log levels to control the logging output. // Deprecated: use github.com/astaxie/beego/logs instead. const ( - LevelEmergency = iota - LevelAlert - LevelCritical - LevelError - LevelWarning - LevelNotice - LevelInformational - LevelDebug + LevelEmergency = webLog.LevelEmergency + LevelAlert = webLog.LevelAlert + LevelCritical = webLog.LevelCritical + LevelError = webLog.LevelError + LevelWarning = webLog.LevelWarning + LevelNotice = webLog.LevelNotice + LevelInformational = webLog.LevelInformational + LevelDebug = webLog.LevelDebug ) // BeeLogger references the used application logger. diff --git a/adapter/logs/accesslog.go b/adapter/logs/accesslog.go new file mode 100644 index 00000000..a2150884 --- /dev/null +++ b/adapter/logs/accesslog.go @@ -0,0 +1,27 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 logs + +import ( + "github.com/astaxie/beego/core/logs" +) + +// AccessLogRecord struct for holding access log data. +type AccessLogRecord logs.AccessLogRecord + +// AccessLog - Format and print access log. +func AccessLog(r *AccessLogRecord, format string) { + logs.AccessLog((*logs.AccessLogRecord)(r), format) +} diff --git a/adapter/logs/alils/alils.go b/adapter/logs/alils/alils.go new file mode 100644 index 00000000..941cba4c --- /dev/null +++ b/adapter/logs/alils/alils.go @@ -0,0 +1,5 @@ +package alils + +import ( + _ "github.com/astaxie/beego/core/logs/alils" +) diff --git a/adapter/logs/es/es.go b/adapter/logs/es/es.go new file mode 100644 index 00000000..0f0fd607 --- /dev/null +++ b/adapter/logs/es/es.go @@ -0,0 +1,5 @@ +package es + +import ( + _ "github.com/astaxie/beego/core/logs/es" +) diff --git a/adapter/logs/log.go b/adapter/logs/log.go new file mode 100644 index 00000000..54eb24d5 --- /dev/null +++ b/adapter/logs/log.go @@ -0,0 +1,347 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 logs provide a general log interface +// Usage: +// +// import "github.com/astaxie/beego/logs" +// +// log := NewLogger(10000) +// log.SetLogger("console", "") +// +// > the first params stand for how many channel +// +// Use it like this: +// +// log.Trace("trace") +// log.Info("info") +// log.Warn("warning") +// log.Debug("debug") +// log.Critical("critical") +// +// more docs http://beego.me/docs/module/logs.md +package logs + +import ( + "log" + "time" + + "github.com/astaxie/beego/core/logs" +) + +// RFC5424 log message levels. +const ( + LevelEmergency = iota + LevelAlert + LevelCritical + LevelError + LevelWarning + LevelNotice + LevelInformational + LevelDebug +) + +// levelLogLogger is defined to implement log.Logger +// the real log level will be LevelEmergency +const levelLoggerImpl = -1 + +// Name for adapter with beego official support +const ( + AdapterConsole = "console" + AdapterFile = "file" + AdapterMultiFile = "multifile" + AdapterMail = "smtp" + AdapterConn = "conn" + AdapterEs = "es" + AdapterJianLiao = "jianliao" + AdapterSlack = "slack" + AdapterAliLS = "alils" +) + +// Legacy log level constants to ensure backwards compatibility. +const ( + LevelInfo = LevelInformational + LevelTrace = LevelDebug + LevelWarn = LevelWarning +) + +type newLoggerFunc func() Logger + +// Logger defines the behavior of a log provider. +type Logger interface { + Init(config string) error + WriteMsg(when time.Time, msg string, level int) error + Destroy() + Flush() +} + +// Register makes a log provide available by the provided name. +// If Register is called twice with the same name or if driver is nil, +// it panics. +func Register(name string, log newLoggerFunc) { + logs.Register(name, func() logs.Logger { + return &oldToNewAdapter{ + old: log(), + } + }) +} + +// BeeLogger is default logger in beego application. +// it can contain several providers and log message into all providers. +type BeeLogger logs.BeeLogger + +const defaultAsyncMsgLen = 1e3 + +// NewLogger returns a new BeeLogger. +// channelLen means the number of messages in chan(used where asynchronous is true). +// if the buffering chan is full, logger adapters write to file or other way. +func NewLogger(channelLens ...int64) *BeeLogger { + return (*BeeLogger)(logs.NewLogger(channelLens...)) +} + +// Async set the log to asynchronous and start the goroutine +func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger { + (*logs.BeeLogger)(bl).Async(msgLen...) + return bl +} + +// SetLogger provides a given logger adapter into BeeLogger with config string. +// config need to be correct JSON as string: {"interval":360}. +func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error { + return (*logs.BeeLogger)(bl).SetLogger(adapterName, configs...) +} + +// DelLogger remove a logger adapter in BeeLogger. +func (bl *BeeLogger) DelLogger(adapterName string) error { + return (*logs.BeeLogger)(bl).DelLogger(adapterName) +} + +func (bl *BeeLogger) Write(p []byte) (n int, err error) { + return (*logs.BeeLogger)(bl).Write(p) +} + +// SetLevel Set log message level. +// If message level (such as LevelDebug) is higher than logger level (such as LevelWarning), +// log providers will not even be sent the message. +func (bl *BeeLogger) SetLevel(l int) { + (*logs.BeeLogger)(bl).SetLevel(l) +} + +// GetLevel Get Current log message level. +func (bl *BeeLogger) GetLevel() int { + return (*logs.BeeLogger)(bl).GetLevel() +} + +// SetLogFuncCallDepth set log funcCallDepth +func (bl *BeeLogger) SetLogFuncCallDepth(d int) { + (*logs.BeeLogger)(bl).SetLogFuncCallDepth(d) +} + +// GetLogFuncCallDepth return log funcCallDepth for wrapper +func (bl *BeeLogger) GetLogFuncCallDepth() int { + return (*logs.BeeLogger)(bl).GetLogFuncCallDepth() +} + +// EnableFuncCallDepth enable log funcCallDepth +func (bl *BeeLogger) EnableFuncCallDepth(b bool) { + (*logs.BeeLogger)(bl).EnableFuncCallDepth(b) +} + +// set prefix +func (bl *BeeLogger) SetPrefix(s string) { + (*logs.BeeLogger)(bl).SetPrefix(s) +} + +// Emergency Log EMERGENCY level message. +func (bl *BeeLogger) Emergency(format string, v ...interface{}) { + (*logs.BeeLogger)(bl).Emergency(format, v...) +} + +// Alert Log ALERT level message. +func (bl *BeeLogger) Alert(format string, v ...interface{}) { + (*logs.BeeLogger)(bl).Alert(format, v...) +} + +// Critical Log CRITICAL level message. +func (bl *BeeLogger) Critical(format string, v ...interface{}) { + (*logs.BeeLogger)(bl).Critical(format, v...) +} + +// Error Log ERROR level message. +func (bl *BeeLogger) Error(format string, v ...interface{}) { + (*logs.BeeLogger)(bl).Error(format, v...) +} + +// Warning Log WARNING level message. +func (bl *BeeLogger) Warning(format string, v ...interface{}) { + (*logs.BeeLogger)(bl).Warning(format, v...) +} + +// Notice Log NOTICE level message. +func (bl *BeeLogger) Notice(format string, v ...interface{}) { + (*logs.BeeLogger)(bl).Notice(format, v...) +} + +// Informational Log INFORMATIONAL level message. +func (bl *BeeLogger) Informational(format string, v ...interface{}) { + (*logs.BeeLogger)(bl).Informational(format, v...) +} + +// Debug Log DEBUG level message. +func (bl *BeeLogger) Debug(format string, v ...interface{}) { + (*logs.BeeLogger)(bl).Debug(format, v...) +} + +// Warn Log WARN level message. +// compatibility alias for Warning() +func (bl *BeeLogger) Warn(format string, v ...interface{}) { + (*logs.BeeLogger)(bl).Warn(format, v...) +} + +// Info Log INFO level message. +// compatibility alias for Informational() +func (bl *BeeLogger) Info(format string, v ...interface{}) { + (*logs.BeeLogger)(bl).Info(format, v...) +} + +// Trace Log TRACE level message. +// compatibility alias for Debug() +func (bl *BeeLogger) Trace(format string, v ...interface{}) { + (*logs.BeeLogger)(bl).Trace(format, v...) +} + +// Flush flush all chan data. +func (bl *BeeLogger) Flush() { + (*logs.BeeLogger)(bl).Flush() +} + +// Close close logger, flush all chan data and destroy all adapters in BeeLogger. +func (bl *BeeLogger) Close() { + (*logs.BeeLogger)(bl).Close() +} + +// Reset close all outputs, and set bl.outputs to nil +func (bl *BeeLogger) Reset() { + (*logs.BeeLogger)(bl).Reset() +} + +// GetBeeLogger returns the default BeeLogger +func GetBeeLogger() *BeeLogger { + return (*BeeLogger)(logs.GetBeeLogger()) +} + +// GetLogger returns the default BeeLogger +func GetLogger(prefixes ...string) *log.Logger { + return logs.GetLogger(prefixes...) +} + +// Reset will remove all the adapter +func Reset() { + logs.Reset() +} + +// Async set the beelogger with Async mode and hold msglen messages +func Async(msgLen ...int64) *BeeLogger { + return (*BeeLogger)(logs.Async(msgLen...)) +} + +// SetLevel sets the global log level used by the simple logger. +func SetLevel(l int) { + logs.SetLevel(l) +} + +// SetPrefix sets the prefix +func SetPrefix(s string) { + logs.SetPrefix(s) +} + +// EnableFuncCallDepth enable log funcCallDepth +func EnableFuncCallDepth(b bool) { + logs.EnableFuncCallDepth(b) +} + +// SetLogFuncCall set the CallDepth, default is 4 +func SetLogFuncCall(b bool) { + logs.SetLogFuncCall(b) +} + +// SetLogFuncCallDepth set log funcCallDepth +func SetLogFuncCallDepth(d int) { + logs.SetLogFuncCallDepth(d) +} + +// SetLogger sets a new logger. +func SetLogger(adapter string, config ...string) error { + return logs.SetLogger(adapter, config...) +} + +// Emergency logs a message at emergency level. +func Emergency(f interface{}, v ...interface{}) { + logs.Emergency(f, v...) +} + +// Alert logs a message at alert level. +func Alert(f interface{}, v ...interface{}) { + logs.Alert(f, v...) +} + +// Critical logs a message at critical level. +func Critical(f interface{}, v ...interface{}) { + logs.Critical(f, v...) +} + +// Error logs a message at error level. +func Error(f interface{}, v ...interface{}) { + logs.Error(f, v...) +} + +// Warning logs a message at warning level. +func Warning(f interface{}, v ...interface{}) { + logs.Warning(f, v...) +} + +// Warn compatibility alias for Warning() +func Warn(f interface{}, v ...interface{}) { + logs.Warn(f, v...) +} + +// Notice logs a message at notice level. +func Notice(f interface{}, v ...interface{}) { + logs.Notice(f, v...) +} + +// Informational logs a message at info level. +func Informational(f interface{}, v ...interface{}) { + logs.Informational(f, v...) +} + +// Info compatibility alias for Warning() +func Info(f interface{}, v ...interface{}) { + logs.Info(f, v...) +} + +// Debug logs a message at debug level. +func Debug(f interface{}, v ...interface{}) { + logs.Debug(f, v...) +} + +// Trace logs a message at trace level. +// compatibility alias for Warning() +func Trace(f interface{}, v ...interface{}) { + logs.Trace(f, v...) +} + +func init() { + SetLogFuncCallDepth(4) +} diff --git a/adapter/logs/log_adapter.go b/adapter/logs/log_adapter.go new file mode 100644 index 00000000..6b7022d6 --- /dev/null +++ b/adapter/logs/log_adapter.go @@ -0,0 +1,69 @@ +// Copyright 2020 +// +// Licensed 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 logs + +import ( + "time" + + "github.com/astaxie/beego/core/logs" +) + +type oldToNewAdapter struct { + old Logger +} + +func (o *oldToNewAdapter) Init(config string) error { + return o.old.Init(config) +} + +func (o *oldToNewAdapter) WriteMsg(lm *logs.LogMsg) error { + return o.old.WriteMsg(lm.When, lm.OldStyleFormat(), lm.Level) +} + +func (o *oldToNewAdapter) Destroy() { + o.old.Destroy() +} + +func (o *oldToNewAdapter) Flush() { + o.old.Flush() +} + +func (o *oldToNewAdapter) SetFormatter(f logs.LogFormatter) { + panic("unsupported operation, you should not invoke this method") +} + +type newToOldAdapter struct { + n logs.Logger +} + +func (n *newToOldAdapter) Init(config string) error { + return n.n.Init(config) +} + +func (n *newToOldAdapter) WriteMsg(when time.Time, msg string, level int) error { + return n.n.WriteMsg(&logs.LogMsg{ + When: when, + Msg: msg, + Level: level, + }) +} + +func (n *newToOldAdapter) Destroy() { + panic("implement me") +} + +func (n *newToOldAdapter) Flush() { + panic("implement me") +} diff --git a/adapter/logs/logger.go b/adapter/logs/logger.go new file mode 100644 index 00000000..5a8e0a1c --- /dev/null +++ b/adapter/logs/logger.go @@ -0,0 +1,38 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 logs + +import ( + "github.com/astaxie/beego/core/logs" +) + +// ColorByStatus return color by http code +// 2xx return Green +// 3xx return White +// 4xx return Yellow +// 5xx return Red +func ColorByStatus(code int) string { + return logs.ColorByStatus(code) +} + +// ColorByMethod return color by http code +func ColorByMethod(method string) string { + return logs.ColorByMethod(method) +} + +// ResetColor return reset color +func ResetColor() string { + return logs.ResetColor() +} diff --git a/adapter/logs/logger_test.go b/adapter/logs/logger_test.go new file mode 100644 index 00000000..9f2cc5a5 --- /dev/null +++ b/adapter/logs/logger_test.go @@ -0,0 +1,24 @@ +// Copyright 2016 beego Author. All Rights Reserved. +// +// Licensed 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 logs + +import ( + "testing" +) + +func TestBeeLogger_Info(t *testing.T) { + log := NewLogger(1000) + log.SetLogger("file", `{"net":"tcp","addr":":7020"}`) +} diff --git a/metric/prometheus.go b/adapter/metric/prometheus.go similarity index 87% rename from metric/prometheus.go rename to adapter/metric/prometheus.go index 7722240b..4660f626 100644 --- a/metric/prometheus.go +++ b/adapter/metric/prometheus.go @@ -24,7 +24,8 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/astaxie/beego" - "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/core/logs" + "github.com/astaxie/beego/server/web" ) func PrometheusMiddleWare(next http.Handler) http.Handler { @@ -32,9 +33,9 @@ func PrometheusMiddleWare(next http.Handler) http.Handler { Name: "beego", Subsystem: "http_request", ConstLabels: map[string]string{ - "server": beego.BConfig.ServerName, - "env": beego.BConfig.RunMode, - "appname": beego.BConfig.AppName, + "server": web.BConfig.ServerName, + "env": web.BConfig.RunMode, + "appname": web.BConfig.AppName, }, Help: "The statics info for http request", }, []string{"pattern", "method", "status", "duration"}) @@ -57,15 +58,15 @@ func registerBuildInfo() { Subsystem: "build_info", Help: "The building information", ConstLabels: map[string]string{ - "appname": beego.BConfig.AppName, + "appname": web.BConfig.AppName, "build_version": beego.BuildVersion, "build_revision": beego.BuildGitRevision, "build_status": beego.BuildStatus, "build_tag": beego.BuildTag, - "build_time": strings.Replace(beego.BuildTime, "--", " ", 1), + "build_time": strings.Replace(beego.BuildTime, "--", " ", 1), "go_version": beego.GoVersion, "git_branch": beego.GitBranch, - "start_time": time.Now().Format("2006-01-02 15:04:05"), + "start_time": time.Now().Format("2006-01-02 15:04:05"), }, }, []string{}) @@ -74,7 +75,7 @@ func registerBuildInfo() { } func report(dur time.Duration, writer http.ResponseWriter, q *http.Request, vec *prometheus.SummaryVec) { - ctrl := beego.BeeApp.Handlers + ctrl := web.BeeApp.Handlers ctx := ctrl.GetContext() ctx.Reset(writer, q) defer ctrl.GiveBackContext(ctx) diff --git a/metric/prometheus_test.go b/adapter/metric/prometheus_test.go similarity index 96% rename from metric/prometheus_test.go rename to adapter/metric/prometheus_test.go index d82a6dec..751348bf 100644 --- a/metric/prometheus_test.go +++ b/adapter/metric/prometheus_test.go @@ -22,7 +22,7 @@ import ( "github.com/prometheus/client_golang/prometheus" - "github.com/astaxie/beego/context" + "github.com/astaxie/beego/adapter/context" ) func TestPrometheusMiddleWare(t *testing.T) { diff --git a/adapter/migration/ddl.go b/adapter/migration/ddl.go new file mode 100644 index 00000000..b43b4d34 --- /dev/null +++ b/adapter/migration/ddl.go @@ -0,0 +1,198 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 migration + +import ( + "github.com/astaxie/beego/client/orm/migration" +) + +// Index struct defines the structure of Index Columns +type Index migration.Index + +// Unique struct defines a single unique key combination +type Unique migration.Unique + +// Column struct defines a single column of a table +type Column migration.Column + +// Foreign struct defines a single foreign relationship +type Foreign migration.Foreign + +// RenameColumn struct allows renaming of columns +type RenameColumn migration.RenameColumn + +// CreateTable creates the table on system +func (m *Migration) CreateTable(tablename, engine, charset string, p ...func()) { + (*migration.Migration)(m).CreateTable(tablename, engine, charset, p...) +} + +// AlterTable set the ModifyType to alter +func (m *Migration) AlterTable(tablename string) { + (*migration.Migration)(m).AlterTable(tablename) +} + +// NewCol creates a new standard column and attaches it to m struct +func (m *Migration) NewCol(name string) *Column { + return (*Column)((*migration.Migration)(m).NewCol(name)) +} + +// PriCol creates a new primary column and attaches it to m struct +func (m *Migration) PriCol(name string) *Column { + return (*Column)((*migration.Migration)(m).PriCol(name)) +} + +// UniCol creates / appends columns to specified unique key and attaches it to m struct +func (m *Migration) UniCol(uni, name string) *Column { + return (*Column)((*migration.Migration)(m).UniCol(uni, name)) +} + +// ForeignCol creates a new foreign column and returns the instance of column +func (m *Migration) ForeignCol(colname, foreigncol, foreigntable string) (foreign *Foreign) { + return (*Foreign)((*migration.Migration)(m).ForeignCol(colname, foreigncol, foreigntable)) +} + +// SetOnDelete sets the on delete of foreign +func (foreign *Foreign) SetOnDelete(del string) *Foreign { + (*migration.Foreign)(foreign).SetOnDelete(del) + return foreign +} + +// SetOnUpdate sets the on update of foreign +func (foreign *Foreign) SetOnUpdate(update string) *Foreign { + (*migration.Foreign)(foreign).SetOnUpdate(update) + return foreign +} + +// Remove marks the columns to be removed. +// it allows reverse m to create the column. +func (c *Column) Remove() { + (*migration.Column)(c).Remove() +} + +// SetAuto enables auto_increment of column (can be used once) +func (c *Column) SetAuto(inc bool) *Column { + (*migration.Column)(c).SetAuto(inc) + return c +} + +// SetNullable sets the column to be null +func (c *Column) SetNullable(null bool) *Column { + (*migration.Column)(c).SetNullable(null) + return c +} + +// SetDefault sets the default value, prepend with "DEFAULT " +func (c *Column) SetDefault(def string) *Column { + (*migration.Column)(c).SetDefault(def) + return c +} + +// SetUnsigned sets the column to be unsigned int +func (c *Column) SetUnsigned(unsign bool) *Column { + (*migration.Column)(c).SetUnsigned(unsign) + return c +} + +// SetDataType sets the dataType of the column +func (c *Column) SetDataType(dataType string) *Column { + (*migration.Column)(c).SetDataType(dataType) + return c +} + +// SetOldNullable allows reverting to previous nullable on reverse ms +func (c *RenameColumn) SetOldNullable(null bool) *RenameColumn { + (*migration.RenameColumn)(c).SetOldNullable(null) + return c +} + +// SetOldDefault allows reverting to previous default on reverse ms +func (c *RenameColumn) SetOldDefault(def string) *RenameColumn { + (*migration.RenameColumn)(c).SetOldDefault(def) + return c +} + +// SetOldUnsigned allows reverting to previous unsgined on reverse ms +func (c *RenameColumn) SetOldUnsigned(unsign bool) *RenameColumn { + (*migration.RenameColumn)(c).SetOldUnsigned(unsign) + return c +} + +// SetOldDataType allows reverting to previous datatype on reverse ms +func (c *RenameColumn) SetOldDataType(dataType string) *RenameColumn { + (*migration.RenameColumn)(c).SetOldDataType(dataType) + return c +} + +// SetPrimary adds the columns to the primary key (can only be used any number of times in only one m) +func (c *Column) SetPrimary(m *Migration) *Column { + (*migration.Column)(c).SetPrimary((*migration.Migration)(m)) + return c +} + +// AddColumnsToUnique adds the columns to Unique Struct +func (unique *Unique) AddColumnsToUnique(columns ...*Column) *Unique { + cls := toNewColumnsArray(columns) + (*migration.Unique)(unique).AddColumnsToUnique(cls...) + return unique +} + +// AddColumns adds columns to m struct +func (m *Migration) AddColumns(columns ...*Column) *Migration { + cls := toNewColumnsArray(columns) + (*migration.Migration)(m).AddColumns(cls...) + return m +} + +func toNewColumnsArray(columns []*Column) []*migration.Column { + cls := make([]*migration.Column, 0, len(columns)) + for _, c := range columns { + cls = append(cls, (*migration.Column)(c)) + } + return cls +} + +// AddPrimary adds the column to primary in m struct +func (m *Migration) AddPrimary(primary *Column) *Migration { + (*migration.Migration)(m).AddPrimary((*migration.Column)(primary)) + return m +} + +// AddUnique adds the column to unique in m struct +func (m *Migration) AddUnique(unique *Unique) *Migration { + (*migration.Migration)(m).AddUnique((*migration.Unique)(unique)) + return m +} + +// AddForeign adds the column to foreign in m struct +func (m *Migration) AddForeign(foreign *Foreign) *Migration { + (*migration.Migration)(m).AddForeign((*migration.Foreign)(foreign)) + return m +} + +// AddIndex adds the column to index in m struct +func (m *Migration) AddIndex(index *Index) *Migration { + (*migration.Migration)(m).AddIndex((*migration.Index)(index)) + return m +} + +// RenameColumn allows renaming of columns +func (m *Migration) RenameColumn(from, to string) *RenameColumn { + return (*RenameColumn)((*migration.Migration)(m).RenameColumn(from, to)) +} + +// GetSQL returns the generated sql depending on ModifyType +func (m *Migration) GetSQL() (sql string) { + return (*migration.Migration)(m).GetSQL() +} diff --git a/migration/doc.go b/adapter/migration/doc.go similarity index 100% rename from migration/doc.go rename to adapter/migration/doc.go diff --git a/adapter/migration/migration.go b/adapter/migration/migration.go new file mode 100644 index 00000000..677c35ca --- /dev/null +++ b/adapter/migration/migration.go @@ -0,0 +1,111 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 migration is used for migration +// +// The table structure is as follow: +// +// CREATE TABLE `migrations` ( +// `id_migration` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key', +// `name` varchar(255) DEFAULT NULL COMMENT 'migration name, unique', +// `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'date migrated or rolled back', +// `statements` longtext COMMENT 'SQL statements for this migration', +// `rollback_statements` longtext, +// `status` enum('update','rollback') DEFAULT NULL COMMENT 'update indicates it is a normal migration while rollback means this migration is rolled back', +// PRIMARY KEY (`id_migration`) +// ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +package migration + +import ( + "github.com/astaxie/beego/client/orm/migration" +) + +// const the data format for the bee generate migration datatype +const ( + DateFormat = "20060102_150405" + DBDateFormat = "2006-01-02 15:04:05" +) + +// Migrationer is an interface for all Migration struct +type Migrationer interface { + Up() + Down() + Reset() + Exec(name, status string) error + GetCreated() int64 +} + +// Migration defines the migrations by either SQL or DDL +type Migration migration.Migration + +// Up implement in the Inheritance struct for upgrade +func (m *Migration) Up() { + (*migration.Migration)(m).Up() +} + +// Down implement in the Inheritance struct for down +func (m *Migration) Down() { + (*migration.Migration)(m).Down() +} + +// Migrate adds the SQL to the execution list +func (m *Migration) Migrate(migrationType string) { + (*migration.Migration)(m).Migrate(migrationType) +} + +// SQL add sql want to execute +func (m *Migration) SQL(sql string) { + (*migration.Migration)(m).SQL(sql) +} + +// Reset the sqls +func (m *Migration) Reset() { + (*migration.Migration)(m).Reset() +} + +// Exec execute the sql already add in the sql +func (m *Migration) Exec(name, status string) error { + return (*migration.Migration)(m).Exec(name, status) +} + +// GetCreated get the unixtime from the Created +func (m *Migration) GetCreated() int64 { + return (*migration.Migration)(m).GetCreated() +} + +// Register register the Migration in the map +func Register(name string, m Migrationer) error { + return migration.Register(name, m) +} + +// Upgrade upgrade the migration from lasttime +func Upgrade(lasttime int64) error { + return migration.Upgrade(lasttime) +} + +// Rollback rollback the migration by the name +func Rollback(name string) error { + return migration.Rollback(name) +} + +// Reset reset all migration +// run all migration's down function +func Reset() error { + return migration.Reset() +} + +// Refresh first Reset, then Upgrade +func Refresh() error { + return migration.Refresh() +} diff --git a/adapter/namespace.go b/adapter/namespace.go new file mode 100644 index 00000000..98cbd8a5 --- /dev/null +++ b/adapter/namespace.go @@ -0,0 +1,378 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 adapter + +import ( + "net/http" + + adtContext "github.com/astaxie/beego/adapter/context" + "github.com/astaxie/beego/server/web/context" + + "github.com/astaxie/beego/server/web" +) + +type namespaceCond func(*adtContext.Context) bool + +// LinkNamespace used as link action +type LinkNamespace func(*Namespace) + +// Namespace is store all the info +type Namespace web.Namespace + +// NewNamespace get new Namespace +func NewNamespace(prefix string, params ...LinkNamespace) *Namespace { + nps := oldToNewLinkNs(params) + return (*Namespace)(web.NewNamespace(prefix, nps...)) +} + +func oldToNewLinkNs(params []LinkNamespace) []web.LinkNamespace { + nps := make([]web.LinkNamespace, 0, len(params)) + for _, p := range params { + nps = append(nps, func(namespace *web.Namespace) { + p((*Namespace)(namespace)) + }) + } + return nps +} + +// Cond set condition function +// if cond return true can run this namespace, else can't +// usage: +// ns.Cond(func (ctx *context.Context) bool{ +// if ctx.Input.Domain() == "api.beego.me" { +// return true +// } +// return false +// }) +// Cond as the first filter +func (n *Namespace) Cond(cond namespaceCond) *Namespace { + (*web.Namespace)(n).Cond(func(context *context.Context) bool { + return cond((*adtContext.Context)(context)) + }) + return n +} + +// Filter add filter in the Namespace +// action has before & after +// FilterFunc +// usage: +// Filter("before", func (ctx *context.Context){ +// _, ok := ctx.Input.Session("uid").(int) +// if !ok && ctx.Request.RequestURI != "/login" { +// ctx.Redirect(302, "/login") +// } +// }) +func (n *Namespace) Filter(action string, filter ...FilterFunc) *Namespace { + nfs := oldToNewFilter(filter) + (*web.Namespace)(n).Filter(action, nfs...) + return n +} + +func oldToNewFilter(filter []FilterFunc) []web.FilterFunc { + nfs := make([]web.FilterFunc, 0, len(filter)) + for _, f := range filter { + nfs = append(nfs, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + } + return nfs +} + +// Router same as beego.Rourer +// refer: https://godoc.org/github.com/astaxie/beego#Router +func (n *Namespace) Router(rootpath string, c ControllerInterface, mappingMethods ...string) *Namespace { + (*web.Namespace)(n).Router(rootpath, c, mappingMethods...) + return n +} + +// AutoRouter same as beego.AutoRouter +// refer: https://godoc.org/github.com/astaxie/beego#AutoRouter +func (n *Namespace) AutoRouter(c ControllerInterface) *Namespace { + (*web.Namespace)(n).AutoRouter(c) + return n +} + +// AutoPrefix same as beego.AutoPrefix +// refer: https://godoc.org/github.com/astaxie/beego#AutoPrefix +func (n *Namespace) AutoPrefix(prefix string, c ControllerInterface) *Namespace { + (*web.Namespace)(n).AutoPrefix(prefix, c) + return n +} + +// Get same as beego.Get +// refer: https://godoc.org/github.com/astaxie/beego#Get +func (n *Namespace) Get(rootpath string, f FilterFunc) *Namespace { + (*web.Namespace)(n).Get(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + return n +} + +// Post same as beego.Post +// refer: https://godoc.org/github.com/astaxie/beego#Post +func (n *Namespace) Post(rootpath string, f FilterFunc) *Namespace { + (*web.Namespace)(n).Post(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + return n +} + +// Delete same as beego.Delete +// refer: https://godoc.org/github.com/astaxie/beego#Delete +func (n *Namespace) Delete(rootpath string, f FilterFunc) *Namespace { + (*web.Namespace)(n).Delete(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + return n +} + +// Put same as beego.Put +// refer: https://godoc.org/github.com/astaxie/beego#Put +func (n *Namespace) Put(rootpath string, f FilterFunc) *Namespace { + (*web.Namespace)(n).Put(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + return n +} + +// Head same as beego.Head +// refer: https://godoc.org/github.com/astaxie/beego#Head +func (n *Namespace) Head(rootpath string, f FilterFunc) *Namespace { + (*web.Namespace)(n).Head(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + return n +} + +// Options same as beego.Options +// refer: https://godoc.org/github.com/astaxie/beego#Options +func (n *Namespace) Options(rootpath string, f FilterFunc) *Namespace { + (*web.Namespace)(n).Options(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + return n +} + +// Patch same as beego.Patch +// refer: https://godoc.org/github.com/astaxie/beego#Patch +func (n *Namespace) Patch(rootpath string, f FilterFunc) *Namespace { + (*web.Namespace)(n).Patch(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + return n +} + +// Any same as beego.Any +// refer: https://godoc.org/github.com/astaxie/beego#Any +func (n *Namespace) Any(rootpath string, f FilterFunc) *Namespace { + (*web.Namespace)(n).Any(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + return n +} + +// Handler same as beego.Handler +// refer: https://godoc.org/github.com/astaxie/beego#Handler +func (n *Namespace) Handler(rootpath string, h http.Handler) *Namespace { + (*web.Namespace)(n).Handler(rootpath, h) + return n +} + +// Include add include class +// refer: https://godoc.org/github.com/astaxie/beego#Include +func (n *Namespace) Include(cList ...ControllerInterface) *Namespace { + nL := oldToNewCtrlIntfs(cList) + (*web.Namespace)(n).Include(nL...) + return n +} + +// Namespace add nest Namespace +// usage: +// ns := beego.NewNamespace(“/v1”). +// Namespace( +// beego.NewNamespace("/shop"). +// Get("/:id", func(ctx *context.Context) { +// ctx.Output.Body([]byte("shopinfo")) +// }), +// beego.NewNamespace("/order"). +// Get("/:id", func(ctx *context.Context) { +// ctx.Output.Body([]byte("orderinfo")) +// }), +// beego.NewNamespace("/crm"). +// Get("/:id", func(ctx *context.Context) { +// ctx.Output.Body([]byte("crminfo")) +// }), +// ) +func (n *Namespace) Namespace(ns ...*Namespace) *Namespace { + nns := oldToNewNs(ns) + (*web.Namespace)(n).Namespace(nns...) + return n +} + +func oldToNewNs(ns []*Namespace) []*web.Namespace { + nns := make([]*web.Namespace, 0, len(ns)) + for _, n := range ns { + nns = append(nns, (*web.Namespace)(n)) + } + return nns +} + +// AddNamespace register Namespace into beego.Handler +// support multi Namespace +func AddNamespace(nl ...*Namespace) { + nnl := oldToNewNs(nl) + web.AddNamespace(nnl...) +} + +// NSCond is Namespace Condition +func NSCond(cond namespaceCond) LinkNamespace { + return func(namespace *Namespace) { + web.NSCond(func(b *context.Context) bool { + return cond((*adtContext.Context)(b)) + }) + } +} + +// NSBefore Namespace BeforeRouter filter +func NSBefore(filterList ...FilterFunc) LinkNamespace { + return func(namespace *Namespace) { + nfs := oldToNewFilter(filterList) + web.NSBefore(nfs...) + } +} + +// NSAfter add Namespace FinishRouter filter +func NSAfter(filterList ...FilterFunc) LinkNamespace { + return func(namespace *Namespace) { + nfs := oldToNewFilter(filterList) + web.NSAfter(nfs...) + } +} + +// NSInclude Namespace Include ControllerInterface +func NSInclude(cList ...ControllerInterface) LinkNamespace { + return func(namespace *Namespace) { + nfs := oldToNewCtrlIntfs(cList) + web.NSInclude(nfs...) + } +} + +// NSRouter call Namespace Router +func NSRouter(rootpath string, c ControllerInterface, mappingMethods ...string) LinkNamespace { + return func(namespace *Namespace) { + web.Router(rootpath, c, mappingMethods...) + } +} + +// NSGet call Namespace Get +func NSGet(rootpath string, f FilterFunc) LinkNamespace { + return func(ns *Namespace) { + web.NSGet(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + } +} + +// NSPost call Namespace Post +func NSPost(rootpath string, f FilterFunc) LinkNamespace { + return func(ns *Namespace) { + web.Post(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + } +} + +// NSHead call Namespace Head +func NSHead(rootpath string, f FilterFunc) LinkNamespace { + return func(ns *Namespace) { + web.NSHead(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + } +} + +// NSPut call Namespace Put +func NSPut(rootpath string, f FilterFunc) LinkNamespace { + return func(ns *Namespace) { + web.NSPut(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + } +} + +// NSDelete call Namespace Delete +func NSDelete(rootpath string, f FilterFunc) LinkNamespace { + return func(ns *Namespace) { + web.NSDelete(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + } +} + +// NSAny call Namespace Any +func NSAny(rootpath string, f FilterFunc) LinkNamespace { + return func(ns *Namespace) { + web.NSAny(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + } +} + +// NSOptions call Namespace Options +func NSOptions(rootpath string, f FilterFunc) LinkNamespace { + return func(ns *Namespace) { + web.NSOptions(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + } +} + +// NSPatch call Namespace Patch +func NSPatch(rootpath string, f FilterFunc) LinkNamespace { + return func(ns *Namespace) { + web.NSPatch(rootpath, func(ctx *context.Context) { + f((*adtContext.Context)(ctx)) + }) + } +} + +// NSAutoRouter call Namespace AutoRouter +func NSAutoRouter(c ControllerInterface) LinkNamespace { + return func(ns *Namespace) { + web.NSAutoRouter(c) + } +} + +// NSAutoPrefix call Namespace AutoPrefix +func NSAutoPrefix(prefix string, c ControllerInterface) LinkNamespace { + return func(ns *Namespace) { + web.NSAutoPrefix(prefix, c) + } +} + +// NSNamespace add sub Namespace +func NSNamespace(prefix string, params ...LinkNamespace) LinkNamespace { + return func(ns *Namespace) { + nps := oldToNewLinkNs(params) + web.NSNamespace(prefix, nps...) + } +} + +// NSHandler add handler +func NSHandler(rootpath string, h http.Handler) LinkNamespace { + return func(ns *Namespace) { + web.NSHandler(rootpath, h) + } +} diff --git a/adapter/orm/cmd.go b/adapter/orm/cmd.go new file mode 100644 index 00000000..fcbd1be4 --- /dev/null +++ b/adapter/orm/cmd.go @@ -0,0 +1,28 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "github.com/astaxie/beego/client/orm" +) + +// RunCommand listen for orm command and then run it if command arguments passed. +func RunCommand() { + orm.RunCommand() +} + +func RunSyncdb(name string, force bool, verbose bool) error { + return orm.RunSyncdb(name, force, verbose) +} diff --git a/adapter/orm/db.go b/adapter/orm/db.go new file mode 100644 index 00000000..fd878732 --- /dev/null +++ b/adapter/orm/db.go @@ -0,0 +1,24 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "github.com/astaxie/beego/client/orm" +) + +var ( + // ErrMissPK missing pk error + ErrMissPK = orm.ErrMissPK +) diff --git a/adapter/orm/db_alias.go b/adapter/orm/db_alias.go new file mode 100644 index 00000000..81a07207 --- /dev/null +++ b/adapter/orm/db_alias.go @@ -0,0 +1,121 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "context" + "database/sql" + "time" + + "github.com/astaxie/beego/client/orm" +) + +// DriverType database driver constant int. +type DriverType orm.DriverType + +// Enum the Database driver +const ( + DRMySQL = DriverType(orm.DRMySQL) + DRSqlite = DriverType(orm.DRSqlite) // sqlite + DROracle = DriverType(orm.DROracle) // oracle + DRPostgres = DriverType(orm.DRPostgres) // pgsql + DRTiDB = DriverType(orm.DRTiDB) // TiDB +) + +type DB orm.DB + +func (d *DB) Begin() (*sql.Tx, error) { + return (*orm.DB)(d).Begin() +} + +func (d *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) { + return (*orm.DB)(d).BeginTx(ctx, opts) +} + +func (d *DB) Prepare(query string) (*sql.Stmt, error) { + return (*orm.DB)(d).Prepare(query) +} + +func (d *DB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) { + return (*orm.DB)(d).PrepareContext(ctx, query) +} + +func (d *DB) Exec(query string, args ...interface{}) (sql.Result, error) { + return (*orm.DB)(d).Exec(query, args...) +} + +func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + return (*orm.DB)(d).ExecContext(ctx, query, args...) +} + +func (d *DB) Query(query string, args ...interface{}) (*sql.Rows, error) { + return (*orm.DB)(d).Query(query, args...) +} + +func (d *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { + return (*orm.DB)(d).QueryContext(ctx, query, args...) +} + +func (d *DB) QueryRow(query string, args ...interface{}) *sql.Row { + return (*orm.DB)(d).QueryRow(query, args) +} + +func (d *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { + return (*orm.DB)(d).QueryRowContext(ctx, query, args...) +} + +// AddAliasWthDB add a aliasName for the drivename +func AddAliasWthDB(aliasName, driverName string, db *sql.DB) error { + return orm.AddAliasWthDB(aliasName, driverName, db) +} + +// RegisterDataBase Setting the database connect params. Use the database driver self dataSource args. +func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) error { + opts := make([]orm.DBOption, 0, 2) + if len(params) > 0 { + opts = append(opts, orm.MaxIdleConnections(params[0])) + } + + if len(params) > 1 { + opts = append(opts, orm.MaxOpenConnections(params[1])) + } + return orm.RegisterDataBase(aliasName, driverName, dataSource, opts...) +} + +// RegisterDriver Register a database driver use specify driver name, this can be definition the driver is which database type. +func RegisterDriver(driverName string, typ DriverType) error { + return orm.RegisterDriver(driverName, orm.DriverType(typ)) +} + +// SetDataBaseTZ Change the database default used timezone +func SetDataBaseTZ(aliasName string, tz *time.Location) error { + return orm.SetDataBaseTZ(aliasName, tz) +} + +// SetMaxIdleConns Change the max idle conns for *sql.DB, use specify database alias name +func SetMaxIdleConns(aliasName string, maxIdleConns int) { + orm.SetMaxIdleConns(aliasName, maxIdleConns) +} + +// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name +func SetMaxOpenConns(aliasName string, maxOpenConns int) { + orm.SetMaxOpenConns(aliasName, maxOpenConns) +} + +// GetDB Get *sql.DB from registered database by db alias name. +// Use "default" as alias name if you not set. +func GetDB(aliasNames ...string) (*sql.DB, error) { + return orm.GetDB(aliasNames...) +} diff --git a/adapter/orm/models.go b/adapter/orm/models.go new file mode 100644 index 00000000..5df64d6d --- /dev/null +++ b/adapter/orm/models.go @@ -0,0 +1,25 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "github.com/astaxie/beego/client/orm" +) + +// ResetModelCache Clean model cache. Then you can re-RegisterModel. +// Common use this api for test case. +func ResetModelCache() { + orm.ResetModelCache() +} diff --git a/adapter/orm/models_boot.go b/adapter/orm/models_boot.go new file mode 100644 index 00000000..0b07de59 --- /dev/null +++ b/adapter/orm/models_boot.go @@ -0,0 +1,40 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "github.com/astaxie/beego/client/orm" +) + +// RegisterModel register models +func RegisterModel(models ...interface{}) { + orm.RegisterModel(models...) +} + +// RegisterModelWithPrefix register models with a prefix +func RegisterModelWithPrefix(prefix string, models ...interface{}) { + orm.RegisterModelWithPrefix(prefix, models) +} + +// RegisterModelWithSuffix register models with a suffix +func RegisterModelWithSuffix(suffix string, models ...interface{}) { + orm.RegisterModelWithSuffix(suffix, models...) +} + +// BootStrap bootstrap models. +// make all model parsed and can not add more models +func BootStrap() { + orm.BootStrap() +} diff --git a/adapter/orm/models_fields.go b/adapter/orm/models_fields.go new file mode 100644 index 00000000..6210567b --- /dev/null +++ b/adapter/orm/models_fields.go @@ -0,0 +1,625 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "time" + + "github.com/astaxie/beego/client/orm" +) + +// Define the Type enum +const ( + TypeBooleanField = orm.TypeBooleanField + TypeVarCharField = orm.TypeVarCharField + TypeCharField = orm.TypeCharField + TypeTextField = orm.TypeTextField + TypeTimeField = orm.TypeTimeField + TypeDateField = orm.TypeDateField + TypeDateTimeField = orm.TypeDateTimeField + TypeBitField = orm.TypeBitField + TypeSmallIntegerField = orm.TypeSmallIntegerField + TypeIntegerField = orm.TypeIntegerField + TypeBigIntegerField = orm.TypeBigIntegerField + TypePositiveBitField = orm.TypePositiveBitField + TypePositiveSmallIntegerField = orm.TypePositiveSmallIntegerField + TypePositiveIntegerField = orm.TypePositiveIntegerField + TypePositiveBigIntegerField = orm.TypePositiveBigIntegerField + TypeFloatField = orm.TypeFloatField + TypeDecimalField = orm.TypeDecimalField + TypeJSONField = orm.TypeJSONField + TypeJsonbField = orm.TypeJsonbField + RelForeignKey = orm.RelForeignKey + RelOneToOne = orm.RelOneToOne + RelManyToMany = orm.RelManyToMany + RelReverseOne = orm.RelReverseOne + RelReverseMany = orm.RelReverseMany +) + +// Define some logic enum +const ( + IsIntegerField = orm.IsIntegerField + IsPositiveIntegerField = orm.IsPositiveIntegerField + IsRelField = orm.IsRelField + IsFieldType = orm.IsFieldType +) + +// BooleanField A true/false field. +type BooleanField orm.BooleanField + +// Value return the BooleanField +func (e BooleanField) Value() bool { + return orm.BooleanField(e).Value() +} + +// Set will set the BooleanField +func (e *BooleanField) Set(d bool) { + (*orm.BooleanField)(e).Set(d) +} + +// String format the Bool to string +func (e *BooleanField) String() string { + return (*orm.BooleanField)(e).String() +} + +// FieldType return BooleanField the type +func (e *BooleanField) FieldType() int { + return (*orm.BooleanField)(e).FieldType() +} + +// SetRaw set the interface to bool +func (e *BooleanField) SetRaw(value interface{}) error { + return (*orm.BooleanField)(e).SetRaw(value) +} + +// RawValue return the current value +func (e *BooleanField) RawValue() interface{} { + return (*orm.BooleanField)(e).RawValue() +} + +// verify the BooleanField implement the Fielder interface +var _ Fielder = new(BooleanField) + +// CharField A string field +// required values tag: size +// The size is enforced at the database level and in models’s validation. +// eg: `orm:"size(120)"` +type CharField orm.CharField + +// Value return the CharField's Value +func (e CharField) Value() string { + return orm.CharField(e).Value() +} + +// Set CharField value +func (e *CharField) Set(d string) { + (*orm.CharField)(e).Set(d) +} + +// String return the CharField +func (e *CharField) String() string { + return (*orm.CharField)(e).String() +} + +// FieldType return the enum type +func (e *CharField) FieldType() int { + return (*orm.CharField)(e).FieldType() +} + +// SetRaw set the interface to string +func (e *CharField) SetRaw(value interface{}) error { + return (*orm.CharField)(e).SetRaw(value) +} + +// RawValue return the CharField value +func (e *CharField) RawValue() interface{} { + return (*orm.CharField)(e).RawValue() +} + +// verify CharField implement Fielder +var _ Fielder = new(CharField) + +// TimeField A time, represented in go by a time.Time instance. +// only time values like 10:00:00 +// Has a few extra, optional attr tag: +// +// auto_now: +// Automatically set the field to now every time the object is saved. Useful for “last-modified” timestamps. +// Note that the current date is always used; it’s not just a default value that you can override. +// +// auto_now_add: +// Automatically set the field to now when the object is first created. Useful for creation of timestamps. +// Note that the current date is always used; it’s not just a default value that you can override. +// +// eg: `orm:"auto_now"` or `orm:"auto_now_add"` +type TimeField orm.TimeField + +// Value return the time.Time +func (e TimeField) Value() time.Time { + return orm.TimeField(e).Value() +} + +// Set set the TimeField's value +func (e *TimeField) Set(d time.Time) { + (*orm.TimeField)(e).Set(d) +} + +// String convert time to string +func (e *TimeField) String() string { + return (*orm.TimeField)(e).String() +} + +// FieldType return enum type Date +func (e *TimeField) FieldType() int { + return (*orm.TimeField)(e).FieldType() +} + +// SetRaw convert the interface to time.Time. Allow string and time.Time +func (e *TimeField) SetRaw(value interface{}) error { + return (*orm.TimeField)(e).SetRaw(value) +} + +// RawValue return time value +func (e *TimeField) RawValue() interface{} { + return (*orm.TimeField)(e).RawValue() +} + +var _ Fielder = new(TimeField) + +// DateField A date, represented in go by a time.Time instance. +// only date values like 2006-01-02 +// Has a few extra, optional attr tag: +// +// auto_now: +// Automatically set the field to now every time the object is saved. Useful for “last-modified” timestamps. +// Note that the current date is always used; it’s not just a default value that you can override. +// +// auto_now_add: +// Automatically set the field to now when the object is first created. Useful for creation of timestamps. +// Note that the current date is always used; it’s not just a default value that you can override. +// +// eg: `orm:"auto_now"` or `orm:"auto_now_add"` +type DateField orm.DateField + +// Value return the time.Time +func (e DateField) Value() time.Time { + return orm.DateField(e).Value() +} + +// Set set the DateField's value +func (e *DateField) Set(d time.Time) { + (*orm.DateField)(e).Set(d) +} + +// String convert datetime to string +func (e *DateField) String() string { + return (*orm.DateField)(e).String() +} + +// FieldType return enum type Date +func (e *DateField) FieldType() int { + return (*orm.DateField)(e).FieldType() +} + +// SetRaw convert the interface to time.Time. Allow string and time.Time +func (e *DateField) SetRaw(value interface{}) error { + return (*orm.DateField)(e).SetRaw(value) +} + +// RawValue return Date value +func (e *DateField) RawValue() interface{} { + return (*orm.DateField)(e).RawValue() +} + +// verify DateField implement fielder interface +var _ Fielder = new(DateField) + +// DateTimeField A date, represented in go by a time.Time instance. +// datetime values like 2006-01-02 15:04:05 +// Takes the same extra arguments as DateField. +type DateTimeField orm.DateTimeField + +// Value return the datetime value +func (e DateTimeField) Value() time.Time { + return orm.DateTimeField(e).Value() +} + +// Set set the time.Time to datetime +func (e *DateTimeField) Set(d time.Time) { + (*orm.DateTimeField)(e).Set(d) +} + +// String return the time's String +func (e *DateTimeField) String() string { + return (*orm.DateTimeField)(e).String() +} + +// FieldType return the enum TypeDateTimeField +func (e *DateTimeField) FieldType() int { + return (*orm.DateTimeField)(e).FieldType() +} + +// SetRaw convert the string or time.Time to DateTimeField +func (e *DateTimeField) SetRaw(value interface{}) error { + return (*orm.DateTimeField)(e).SetRaw(value) +} + +// RawValue return the datetime value +func (e *DateTimeField) RawValue() interface{} { + return (*orm.DateTimeField)(e).RawValue() +} + +// verify datetime implement fielder +var _ Fielder = new(DateTimeField) + +// FloatField A floating-point number represented in go by a float32 value. +type FloatField orm.FloatField + +// Value return the FloatField value +func (e FloatField) Value() float64 { + return orm.FloatField(e).Value() +} + +// Set the Float64 +func (e *FloatField) Set(d float64) { + (*orm.FloatField)(e).Set(d) +} + +// String return the string +func (e *FloatField) String() string { + return (*orm.FloatField)(e).String() +} + +// FieldType return the enum type +func (e *FloatField) FieldType() int { + return (*orm.FloatField)(e).FieldType() +} + +// SetRaw converter interface Float64 float32 or string to FloatField +func (e *FloatField) SetRaw(value interface{}) error { + return (*orm.FloatField)(e).SetRaw(value) +} + +// RawValue return the FloatField value +func (e *FloatField) RawValue() interface{} { + return (*orm.FloatField)(e).RawValue() +} + +// verify FloatField implement Fielder +var _ Fielder = new(FloatField) + +// SmallIntegerField -32768 to 32767 +type SmallIntegerField orm.SmallIntegerField + +// Value return int16 value +func (e SmallIntegerField) Value() int16 { + return orm.SmallIntegerField(e).Value() +} + +// Set the SmallIntegerField value +func (e *SmallIntegerField) Set(d int16) { + (*orm.SmallIntegerField)(e).Set(d) +} + +// String convert smallint to string +func (e *SmallIntegerField) String() string { + return (*orm.SmallIntegerField)(e).String() +} + +// FieldType return enum type SmallIntegerField +func (e *SmallIntegerField) FieldType() int { + return (*orm.SmallIntegerField)(e).FieldType() +} + +// SetRaw convert interface int16/string to int16 +func (e *SmallIntegerField) SetRaw(value interface{}) error { + return (*orm.SmallIntegerField)(e).SetRaw(value) +} + +// RawValue return smallint value +func (e *SmallIntegerField) RawValue() interface{} { + return (*orm.SmallIntegerField)(e).RawValue() +} + +// verify SmallIntegerField implement Fielder +var _ Fielder = new(SmallIntegerField) + +// IntegerField -2147483648 to 2147483647 +type IntegerField orm.IntegerField + +// Value return the int32 +func (e IntegerField) Value() int32 { + return orm.IntegerField(e).Value() +} + +// Set IntegerField value +func (e *IntegerField) Set(d int32) { + (*orm.IntegerField)(e).Set(d) +} + +// String convert Int32 to string +func (e *IntegerField) String() string { + return (*orm.IntegerField)(e).String() +} + +// FieldType return the enum type +func (e *IntegerField) FieldType() int { + return (*orm.IntegerField)(e).FieldType() +} + +// SetRaw convert interface int32/string to int32 +func (e *IntegerField) SetRaw(value interface{}) error { + return (*orm.IntegerField)(e).SetRaw(value) +} + +// RawValue return IntegerField value +func (e *IntegerField) RawValue() interface{} { + return (*orm.IntegerField)(e).RawValue() +} + +// verify IntegerField implement Fielder +var _ Fielder = new(IntegerField) + +// BigIntegerField -9223372036854775808 to 9223372036854775807. +type BigIntegerField orm.BigIntegerField + +// Value return int64 +func (e BigIntegerField) Value() int64 { + return orm.BigIntegerField(e).Value() +} + +// Set the BigIntegerField value +func (e *BigIntegerField) Set(d int64) { + (*orm.BigIntegerField)(e).Set(d) +} + +// String convert BigIntegerField to string +func (e *BigIntegerField) String() string { + return (*orm.BigIntegerField)(e).String() +} + +// FieldType return enum type +func (e *BigIntegerField) FieldType() int { + return (*orm.BigIntegerField)(e).FieldType() +} + +// SetRaw convert interface int64/string to int64 +func (e *BigIntegerField) SetRaw(value interface{}) error { + return (*orm.BigIntegerField)(e).SetRaw(value) +} + +// RawValue return BigIntegerField value +func (e *BigIntegerField) RawValue() interface{} { + return (*orm.BigIntegerField)(e).RawValue() +} + +// verify BigIntegerField implement Fielder +var _ Fielder = new(BigIntegerField) + +// PositiveSmallIntegerField 0 to 65535 +type PositiveSmallIntegerField orm.PositiveSmallIntegerField + +// Value return uint16 +func (e PositiveSmallIntegerField) Value() uint16 { + return orm.PositiveSmallIntegerField(e).Value() +} + +// Set PositiveSmallIntegerField value +func (e *PositiveSmallIntegerField) Set(d uint16) { + (*orm.PositiveSmallIntegerField)(e).Set(d) +} + +// String convert uint16 to string +func (e *PositiveSmallIntegerField) String() string { + return (*orm.PositiveSmallIntegerField)(e).String() +} + +// FieldType return enum type +func (e *PositiveSmallIntegerField) FieldType() int { + return (*orm.PositiveSmallIntegerField)(e).FieldType() +} + +// SetRaw convert Interface uint16/string to uint16 +func (e *PositiveSmallIntegerField) SetRaw(value interface{}) error { + return (*orm.PositiveSmallIntegerField)(e).SetRaw(value) +} + +// RawValue returns PositiveSmallIntegerField value +func (e *PositiveSmallIntegerField) RawValue() interface{} { + return (*orm.PositiveSmallIntegerField)(e).RawValue() +} + +// verify PositiveSmallIntegerField implement Fielder +var _ Fielder = new(PositiveSmallIntegerField) + +// PositiveIntegerField 0 to 4294967295 +type PositiveIntegerField orm.PositiveIntegerField + +// Value return PositiveIntegerField value. Uint32 +func (e PositiveIntegerField) Value() uint32 { + return orm.PositiveIntegerField(e).Value() +} + +// Set the PositiveIntegerField value +func (e *PositiveIntegerField) Set(d uint32) { + (*orm.PositiveIntegerField)(e).Set(d) +} + +// String convert PositiveIntegerField to string +func (e *PositiveIntegerField) String() string { + return (*orm.PositiveIntegerField)(e).String() +} + +// FieldType return enum type +func (e *PositiveIntegerField) FieldType() int { + return (*orm.PositiveIntegerField)(e).FieldType() +} + +// SetRaw convert interface uint32/string to Uint32 +func (e *PositiveIntegerField) SetRaw(value interface{}) error { + return (*orm.PositiveIntegerField)(e).SetRaw(value) +} + +// RawValue return the PositiveIntegerField Value +func (e *PositiveIntegerField) RawValue() interface{} { + return (*orm.PositiveIntegerField)(e).RawValue() +} + +// verify PositiveIntegerField implement Fielder +var _ Fielder = new(PositiveIntegerField) + +// PositiveBigIntegerField 0 to 18446744073709551615 +type PositiveBigIntegerField orm.PositiveBigIntegerField + +// Value return uint64 +func (e PositiveBigIntegerField) Value() uint64 { + return orm.PositiveBigIntegerField(e).Value() +} + +// Set PositiveBigIntegerField value +func (e *PositiveBigIntegerField) Set(d uint64) { + (*orm.PositiveBigIntegerField)(e).Set(d) +} + +// String convert PositiveBigIntegerField to string +func (e *PositiveBigIntegerField) String() string { + return (*orm.PositiveBigIntegerField)(e).String() +} + +// FieldType return enum type +func (e *PositiveBigIntegerField) FieldType() int { + return (*orm.PositiveBigIntegerField)(e).FieldType() +} + +// SetRaw convert interface uint64/string to Uint64 +func (e *PositiveBigIntegerField) SetRaw(value interface{}) error { + return (*orm.PositiveBigIntegerField)(e).SetRaw(value) +} + +// RawValue return PositiveBigIntegerField value +func (e *PositiveBigIntegerField) RawValue() interface{} { + return (*orm.PositiveBigIntegerField)(e).RawValue() +} + +// verify PositiveBigIntegerField implement Fielder +var _ Fielder = new(PositiveBigIntegerField) + +// TextField A large text field. +type TextField orm.TextField + +// Value return TextField value +func (e TextField) Value() string { + return orm.TextField(e).Value() +} + +// Set the TextField value +func (e *TextField) Set(d string) { + (*orm.TextField)(e).Set(d) +} + +// String convert TextField to string +func (e *TextField) String() string { + return (*orm.TextField)(e).String() +} + +// FieldType return enum type +func (e *TextField) FieldType() int { + return (*orm.TextField)(e).FieldType() +} + +// SetRaw convert interface string to string +func (e *TextField) SetRaw(value interface{}) error { + return (*orm.TextField)(e).SetRaw(value) +} + +// RawValue return TextField value +func (e *TextField) RawValue() interface{} { + return (*orm.TextField)(e).RawValue() +} + +// verify TextField implement Fielder +var _ Fielder = new(TextField) + +// JSONField postgres json field. +type JSONField orm.JSONField + +// Value return JSONField value +func (j JSONField) Value() string { + return orm.JSONField(j).Value() +} + +// Set the JSONField value +func (j *JSONField) Set(d string) { + (*orm.JSONField)(j).Set(d) +} + +// String convert JSONField to string +func (j *JSONField) String() string { + return (*orm.JSONField)(j).String() +} + +// FieldType return enum type +func (j *JSONField) FieldType() int { + return (*orm.JSONField)(j).FieldType() +} + +// SetRaw convert interface string to string +func (j *JSONField) SetRaw(value interface{}) error { + return (*orm.JSONField)(j).SetRaw(value) +} + +// RawValue return JSONField value +func (j *JSONField) RawValue() interface{} { + return (*orm.JSONField)(j).RawValue() +} + +// verify JSONField implement Fielder +var _ Fielder = new(JSONField) + +// JsonbField postgres json field. +type JsonbField orm.JsonbField + +// Value return JsonbField value +func (j JsonbField) Value() string { + return orm.JsonbField(j).Value() +} + +// Set the JsonbField value +func (j *JsonbField) Set(d string) { + (*orm.JsonbField)(j).Set(d) +} + +// String convert JsonbField to string +func (j *JsonbField) String() string { + return (*orm.JsonbField)(j).String() +} + +// FieldType return enum type +func (j *JsonbField) FieldType() int { + return (*orm.JsonbField)(j).FieldType() +} + +// SetRaw convert interface string to string +func (j *JsonbField) SetRaw(value interface{}) error { + return (*orm.JsonbField)(j).SetRaw(value) +} + +// RawValue return JsonbField value +func (j *JsonbField) RawValue() interface{} { + return (*orm.JsonbField)(j).RawValue() +} + +// verify JsonbField implement Fielder +var _ Fielder = new(JsonbField) diff --git a/adapter/orm/orm.go b/adapter/orm/orm.go new file mode 100644 index 00000000..15df76ed --- /dev/null +++ b/adapter/orm/orm.go @@ -0,0 +1,314 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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. + +// +build go1.8 + +// Package orm provide ORM for MySQL/PostgreSQL/sqlite +// Simple Usage +// +// package main +// +// import ( +// "fmt" +// "github.com/astaxie/beego/orm" +// _ "github.com/go-sql-driver/mysql" // import your used driver +// ) +// +// // Model Struct +// type User struct { +// Id int `orm:"auto"` +// Name string `orm:"size(100)"` +// } +// +// func init() { +// orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30) +// } +// +// func main() { +// o := orm.NewOrm() +// user := User{Name: "slene"} +// // insert +// id, err := o.Insert(&user) +// // update +// user.Name = "astaxie" +// num, err := o.Update(&user) +// // read one +// u := User{Id: user.Id} +// err = o.Read(&u) +// // delete +// num, err = o.Delete(&u) +// } +// +// more docs: http://beego.me/docs/mvc/model/overview.md +package orm + +import ( + "context" + "database/sql" + "errors" + + "github.com/astaxie/beego/client/orm" + "github.com/astaxie/beego/client/orm/hints" + "github.com/astaxie/beego/core/utils" +) + +// DebugQueries define the debug +const ( + DebugQueries = iota +) + +// Define common vars +var ( + Debug = orm.Debug + DebugLog = orm.DebugLog + DefaultRowsLimit = orm.DefaultRowsLimit + DefaultRelsDepth = orm.DefaultRelsDepth + DefaultTimeLoc = orm.DefaultTimeLoc + ErrTxHasBegan = errors.New(" transaction already begin") + ErrTxDone = errors.New(" transaction not begin") + ErrMultiRows = errors.New(" return multi rows") + ErrNoRows = errors.New(" no row found") + ErrStmtClosed = errors.New(" stmt already closed") + ErrArgs = errors.New(" args error may be empty") + ErrNotImplement = errors.New("have not implement") +) + +type ormer struct { + delegate orm.Ormer + txDelegate orm.TxOrmer + isTx bool +} + +var _ Ormer = new(ormer) + +// read data to model +func (o *ormer) Read(md interface{}, cols ...string) error { + if o.isTx { + return o.txDelegate.Read(md, cols...) + } + return o.delegate.Read(md, cols...) +} + +// read data to model, like Read(), but use "SELECT FOR UPDATE" form +func (o *ormer) ReadForUpdate(md interface{}, cols ...string) error { + if o.isTx { + return o.txDelegate.ReadForUpdate(md, cols...) + } + return o.delegate.ReadForUpdate(md, cols...) +} + +// Try to read a row from the database, or insert one if it doesn't exist +func (o *ormer) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) { + if o.isTx { + return o.txDelegate.ReadOrCreate(md, col1, cols...) + } + return o.delegate.ReadOrCreate(md, col1, cols...) +} + +// insert model data to database +func (o *ormer) Insert(md interface{}) (int64, error) { + if o.isTx { + return o.txDelegate.Insert(md) + } + return o.delegate.Insert(md) +} + +// insert some models to database +func (o *ormer) InsertMulti(bulk int, mds interface{}) (int64, error) { + if o.isTx { + return o.txDelegate.InsertMulti(bulk, mds) + } + return o.delegate.InsertMulti(bulk, mds) +} + +// InsertOrUpdate data to database +func (o *ormer) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) { + if o.isTx { + return o.txDelegate.InsertOrUpdate(md, colConflitAndArgs...) + } + return o.delegate.InsertOrUpdate(md, colConflitAndArgs...) +} + +// update model to database. +// cols set the columns those want to update. +func (o *ormer) Update(md interface{}, cols ...string) (int64, error) { + if o.isTx { + return o.txDelegate.Update(md, cols...) + } + return o.delegate.Update(md, cols...) +} + +// delete model in database +// cols shows the delete conditions values read from. default is pk +func (o *ormer) Delete(md interface{}, cols ...string) (int64, error) { + if o.isTx { + return o.txDelegate.Delete(md, cols...) + } + return o.delegate.Delete(md, cols...) +} + +// create a models to models queryer +func (o *ormer) QueryM2M(md interface{}, name string) QueryM2Mer { + if o.isTx { + return o.txDelegate.QueryM2M(md, name) + } + return o.delegate.QueryM2M(md, name) +} + +// load related models to md model. +// args are limit, offset int and order string. +// +// example: +// orm.LoadRelated(post,"Tags") +// for _,tag := range post.Tags{...} +// +// make sure the relation is defined in model struct tags. +func (o *ormer) LoadRelated(md interface{}, name string, args ...interface{}) (int64, error) { + kvs := make([]utils.KV, 0, 4) + for i, arg := range args { + switch i { + case 0: + if v, ok := arg.(bool); ok { + if v { + kvs = append(kvs, hints.DefaultRelDepth()) + } + } else if v, ok := arg.(int); ok { + kvs = append(kvs, hints.RelDepth(v)) + } + case 1: + kvs = append(kvs, hints.Limit(orm.ToInt64(arg))) + case 2: + kvs = append(kvs, hints.Offset(orm.ToInt64(arg))) + case 3: + kvs = append(kvs, hints.Offset(orm.ToInt64(arg))) + } + } + if o.isTx { + return o.txDelegate.LoadRelated(md, name, kvs...) + } + return o.delegate.LoadRelated(md, name, kvs...) +} + +// return a QuerySeter for table operations. +// table name can be string or struct. +// e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)), +func (o *ormer) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) { + if o.isTx { + return o.txDelegate.QueryTable(ptrStructOrTableName) + } + return o.delegate.QueryTable(ptrStructOrTableName) +} + +// switch to another registered database driver by given name. +func (o *ormer) Using(name string) error { + if o.isTx { + return ErrTxHasBegan + } + o.delegate = orm.NewOrmUsingDB(name) + return nil +} + +// begin transaction +func (o *ormer) Begin() error { + if o.isTx { + return ErrTxHasBegan + } + return o.BeginTx(context.Background(), nil) +} + +func (o *ormer) BeginTx(ctx context.Context, opts *sql.TxOptions) error { + if o.isTx { + return ErrTxHasBegan + } + txOrmer, err := o.delegate.BeginWithCtxAndOpts(ctx, opts) + if err != nil { + return err + } + o.txDelegate = txOrmer + o.isTx = true + return nil +} + +// commit transaction +func (o *ormer) Commit() error { + if !o.isTx { + return ErrTxDone + } + err := o.txDelegate.Commit() + if err == nil { + o.isTx = false + o.txDelegate = nil + } else if err == sql.ErrTxDone { + return ErrTxDone + } + return err +} + +// rollback transaction +func (o *ormer) Rollback() error { + if !o.isTx { + return ErrTxDone + } + err := o.txDelegate.Rollback() + if err == nil { + o.isTx = false + o.txDelegate = nil + } else if err == sql.ErrTxDone { + return ErrTxDone + } + return err +} + +// return a raw query seter for raw sql string. +func (o *ormer) Raw(query string, args ...interface{}) RawSeter { + if o.isTx { + return o.txDelegate.Raw(query, args...) + } + return o.delegate.Raw(query, args...) +} + +// return current using database Driver +func (o *ormer) Driver() Driver { + if o.isTx { + return o.txDelegate.Driver() + } + return o.delegate.Driver() +} + +// return sql.DBStats for current database +func (o *ormer) DBStats() *sql.DBStats { + if o.isTx { + return o.txDelegate.DBStats() + } + return o.delegate.DBStats() +} + +// NewOrm create new orm +func NewOrm() Ormer { + o := orm.NewOrm() + return &ormer{ + delegate: o, + } +} + +// NewOrmWithDB create a new ormer object with specify *sql.DB for query +func NewOrmWithDB(driverName, aliasName string, db *sql.DB) (Ormer, error) { + o, err := orm.NewOrmWithDB(driverName, aliasName, db) + if err != nil { + return nil, err + } + return &ormer{ + delegate: o, + }, nil +} diff --git a/adapter/orm/orm_conds.go b/adapter/orm/orm_conds.go new file mode 100644 index 00000000..f70f0f5b --- /dev/null +++ b/adapter/orm/orm_conds.go @@ -0,0 +1,83 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "github.com/astaxie/beego/client/orm" +) + +// ExprSep define the expression separation +const ( + ExprSep = "__" +) + +// Condition struct. +// work for WHERE conditions. +type Condition orm.Condition + +// NewCondition return new condition struct +func NewCondition() *Condition { + return (*Condition)(orm.NewCondition()) +} + +// Raw add raw sql to condition +func (c Condition) Raw(expr string, sql string) *Condition { + return (*Condition)((orm.Condition)(c).Raw(expr, sql)) +} + +// And add expression to condition +func (c Condition) And(expr string, args ...interface{}) *Condition { + return (*Condition)((orm.Condition)(c).And(expr, args...)) +} + +// AndNot add NOT expression to condition +func (c Condition) AndNot(expr string, args ...interface{}) *Condition { + return (*Condition)((orm.Condition)(c).AndNot(expr, args...)) +} + +// AndCond combine a condition to current condition +func (c *Condition) AndCond(cond *Condition) *Condition { + return (*Condition)((*orm.Condition)(c).AndCond((*orm.Condition)(cond))) +} + +// AndNotCond combine a AND NOT condition to current condition +func (c *Condition) AndNotCond(cond *Condition) *Condition { + return (*Condition)((*orm.Condition)(c).AndNotCond((*orm.Condition)(cond))) +} + +// Or add OR expression to condition +func (c Condition) Or(expr string, args ...interface{}) *Condition { + return (*Condition)((orm.Condition)(c).Or(expr, args...)) +} + +// OrNot add OR NOT expression to condition +func (c Condition) OrNot(expr string, args ...interface{}) *Condition { + return (*Condition)((orm.Condition)(c).OrNot(expr, args...)) +} + +// OrCond combine a OR condition to current condition +func (c *Condition) OrCond(cond *Condition) *Condition { + return (*Condition)((*orm.Condition)(c).OrCond((*orm.Condition)(cond))) +} + +// OrNotCond combine a OR NOT condition to current condition +func (c *Condition) OrNotCond(cond *Condition) *Condition { + return (*Condition)((*orm.Condition)(c).OrNotCond((*orm.Condition)(cond))) +} + +// IsEmpty check the condition arguments are empty or not. +func (c *Condition) IsEmpty() bool { + return (*orm.Condition)(c).IsEmpty() +} diff --git a/adapter/orm/orm_log.go b/adapter/orm/orm_log.go new file mode 100644 index 00000000..3ff7f01c --- /dev/null +++ b/adapter/orm/orm_log.go @@ -0,0 +1,32 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "io" + + "github.com/astaxie/beego/client/orm" +) + +// Log implement the log.Logger +type Log orm.Log + +// costomer log func +var LogFunc = orm.LogFunc + +// NewLog set io.Writer to create a Logger. +func NewLog(out io.Writer) *Log { + return (*Log)(orm.NewLog(out)) +} diff --git a/adapter/orm/orm_queryset.go b/adapter/orm/orm_queryset.go new file mode 100644 index 00000000..1926a6c0 --- /dev/null +++ b/adapter/orm/orm_queryset.go @@ -0,0 +1,32 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "github.com/astaxie/beego/client/orm" +) + +// define Col operations +const ( + ColAdd = orm.ColAdd + ColMinus = orm.ColMinus + ColMultiply = orm.ColMultiply + ColExcept = orm.ColExcept + ColBitAnd = orm.ColBitAnd + ColBitRShift = orm.ColBitRShift + ColBitLShift = orm.ColBitLShift + ColBitXOR = orm.ColBitXOR + ColBitOr = orm.ColBitOr +) diff --git a/adapter/orm/qb.go b/adapter/orm/qb.go new file mode 100644 index 00000000..63eaed8a --- /dev/null +++ b/adapter/orm/qb.go @@ -0,0 +1,27 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "github.com/astaxie/beego/client/orm" +) + +// QueryBuilder is the Query builder interface +type QueryBuilder orm.QueryBuilder + +// NewQueryBuilder return the QueryBuilder +func NewQueryBuilder(driver string) (qb QueryBuilder, err error) { + return orm.NewQueryBuilder(driver) +} diff --git a/adapter/orm/qb_mysql.go b/adapter/orm/qb_mysql.go new file mode 100644 index 00000000..ef87ebab --- /dev/null +++ b/adapter/orm/qb_mysql.go @@ -0,0 +1,150 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "github.com/astaxie/beego/client/orm" +) + +// CommaSpace is the separation +const CommaSpace = orm.CommaSpace + +// MySQLQueryBuilder is the SQL build +type MySQLQueryBuilder orm.MySQLQueryBuilder + +// Select will join the fields +func (qb *MySQLQueryBuilder) Select(fields ...string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).Select(fields...) +} + +// ForUpdate add the FOR UPDATE clause +func (qb *MySQLQueryBuilder) ForUpdate() QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).ForUpdate() +} + +// From join the tables +func (qb *MySQLQueryBuilder) From(tables ...string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).From(tables...) +} + +// InnerJoin INNER JOIN the table +func (qb *MySQLQueryBuilder) InnerJoin(table string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).InnerJoin(table) +} + +// LeftJoin LEFT JOIN the table +func (qb *MySQLQueryBuilder) LeftJoin(table string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).LeftJoin(table) +} + +// RightJoin RIGHT JOIN the table +func (qb *MySQLQueryBuilder) RightJoin(table string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).RightJoin(table) +} + +// On join with on cond +func (qb *MySQLQueryBuilder) On(cond string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).On(cond) +} + +// Where join the Where cond +func (qb *MySQLQueryBuilder) Where(cond string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).Where(cond) +} + +// And join the and cond +func (qb *MySQLQueryBuilder) And(cond string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).And(cond) +} + +// Or join the or cond +func (qb *MySQLQueryBuilder) Or(cond string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).Or(cond) +} + +// In join the IN (vals) +func (qb *MySQLQueryBuilder) In(vals ...string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).In(vals...) +} + +// OrderBy join the Order by fields +func (qb *MySQLQueryBuilder) OrderBy(fields ...string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).OrderBy(fields...) +} + +// Asc join the asc +func (qb *MySQLQueryBuilder) Asc() QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).Asc() +} + +// Desc join the desc +func (qb *MySQLQueryBuilder) Desc() QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).Desc() +} + +// Limit join the limit num +func (qb *MySQLQueryBuilder) Limit(limit int) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).Limit(limit) +} + +// Offset join the offset num +func (qb *MySQLQueryBuilder) Offset(offset int) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).Offset(offset) +} + +// GroupBy join the Group by fields +func (qb *MySQLQueryBuilder) GroupBy(fields ...string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).GroupBy(fields...) +} + +// Having join the Having cond +func (qb *MySQLQueryBuilder) Having(cond string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).Having(cond) +} + +// Update join the update table +func (qb *MySQLQueryBuilder) Update(tables ...string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).Update(tables...) +} + +// Set join the set kv +func (qb *MySQLQueryBuilder) Set(kv ...string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).Set(kv...) +} + +// Delete join the Delete tables +func (qb *MySQLQueryBuilder) Delete(tables ...string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).Delete(tables...) +} + +// InsertInto join the insert SQL +func (qb *MySQLQueryBuilder) InsertInto(table string, fields ...string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).InsertInto(table, fields...) +} + +// Values join the Values(vals) +func (qb *MySQLQueryBuilder) Values(vals ...string) QueryBuilder { + return (*orm.MySQLQueryBuilder)(qb).Values(vals...) +} + +// Subquery join the sub as alias +func (qb *MySQLQueryBuilder) Subquery(sub string, alias string) string { + return (*orm.MySQLQueryBuilder)(qb).Subquery(sub, alias) +} + +// String join all Tokens +func (qb *MySQLQueryBuilder) String() string { + return (*orm.MySQLQueryBuilder)(qb).String() +} diff --git a/orm/qb_tidb.go b/adapter/orm/qb_tidb.go similarity index 60% rename from orm/qb_tidb.go rename to adapter/orm/qb_tidb.go index 87b3ae84..18631ef0 100644 --- a/orm/qb_tidb.go +++ b/adapter/orm/qb_tidb.go @@ -15,168 +15,133 @@ package orm import ( - "fmt" - "strconv" - "strings" + "github.com/astaxie/beego/client/orm" ) // TiDBQueryBuilder is the SQL build -type TiDBQueryBuilder struct { - Tokens []string -} +type TiDBQueryBuilder orm.TiDBQueryBuilder // Select will join the fields func (qb *TiDBQueryBuilder) Select(fields ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "SELECT", strings.Join(fields, CommaSpace)) - return qb + return (*orm.TiDBQueryBuilder)(qb).Select(fields...) } // ForUpdate add the FOR UPDATE clause func (qb *TiDBQueryBuilder) ForUpdate() QueryBuilder { - qb.Tokens = append(qb.Tokens, "FOR UPDATE") - return qb + return (*orm.TiDBQueryBuilder)(qb).ForUpdate() } // From join the tables func (qb *TiDBQueryBuilder) From(tables ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "FROM", strings.Join(tables, CommaSpace)) - return qb + return (*orm.TiDBQueryBuilder)(qb).From(tables...) } // InnerJoin INNER JOIN the table func (qb *TiDBQueryBuilder) InnerJoin(table string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "INNER JOIN", table) - return qb + return (*orm.TiDBQueryBuilder)(qb).InnerJoin(table) } // LeftJoin LEFT JOIN the table func (qb *TiDBQueryBuilder) LeftJoin(table string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "LEFT JOIN", table) - return qb + return (*orm.TiDBQueryBuilder)(qb).LeftJoin(table) } // RightJoin RIGHT JOIN the table func (qb *TiDBQueryBuilder) RightJoin(table string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "RIGHT JOIN", table) - return qb + return (*orm.TiDBQueryBuilder)(qb).RightJoin(table) } // On join with on cond func (qb *TiDBQueryBuilder) On(cond string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "ON", cond) - return qb + return (*orm.TiDBQueryBuilder)(qb).On(cond) } // Where join the Where cond func (qb *TiDBQueryBuilder) Where(cond string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "WHERE", cond) - return qb + return (*orm.TiDBQueryBuilder)(qb).Where(cond) } // And join the and cond func (qb *TiDBQueryBuilder) And(cond string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "AND", cond) - return qb + return (*orm.TiDBQueryBuilder)(qb).And(cond) } // Or join the or cond func (qb *TiDBQueryBuilder) Or(cond string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "OR", cond) - return qb + return (*orm.TiDBQueryBuilder)(qb).Or(cond) } // In join the IN (vals) func (qb *TiDBQueryBuilder) In(vals ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "IN", "(", strings.Join(vals, CommaSpace), ")") - return qb + return (*orm.TiDBQueryBuilder)(qb).In(vals...) } // OrderBy join the Order by fields func (qb *TiDBQueryBuilder) OrderBy(fields ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "ORDER BY", strings.Join(fields, CommaSpace)) - return qb + return (*orm.TiDBQueryBuilder)(qb).OrderBy(fields...) } // Asc join the asc func (qb *TiDBQueryBuilder) Asc() QueryBuilder { - qb.Tokens = append(qb.Tokens, "ASC") - return qb + return (*orm.TiDBQueryBuilder)(qb).Asc() } // Desc join the desc func (qb *TiDBQueryBuilder) Desc() QueryBuilder { - qb.Tokens = append(qb.Tokens, "DESC") - return qb + return (*orm.TiDBQueryBuilder)(qb).Desc() } // Limit join the limit num func (qb *TiDBQueryBuilder) Limit(limit int) QueryBuilder { - qb.Tokens = append(qb.Tokens, "LIMIT", strconv.Itoa(limit)) - return qb + return (*orm.TiDBQueryBuilder)(qb).Limit(limit) } // Offset join the offset num func (qb *TiDBQueryBuilder) Offset(offset int) QueryBuilder { - qb.Tokens = append(qb.Tokens, "OFFSET", strconv.Itoa(offset)) - return qb + return (*orm.TiDBQueryBuilder)(qb).Offset(offset) } // GroupBy join the Group by fields func (qb *TiDBQueryBuilder) GroupBy(fields ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "GROUP BY", strings.Join(fields, CommaSpace)) - return qb + return (*orm.TiDBQueryBuilder)(qb).GroupBy(fields...) } // Having join the Having cond func (qb *TiDBQueryBuilder) Having(cond string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "HAVING", cond) - return qb + return (*orm.TiDBQueryBuilder)(qb).Having(cond) } // Update join the update table func (qb *TiDBQueryBuilder) Update(tables ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "UPDATE", strings.Join(tables, CommaSpace)) - return qb + return (*orm.TiDBQueryBuilder)(qb).Update(tables...) } // Set join the set kv func (qb *TiDBQueryBuilder) Set(kv ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "SET", strings.Join(kv, CommaSpace)) - return qb + return (*orm.TiDBQueryBuilder)(qb).Set(kv...) } // Delete join the Delete tables func (qb *TiDBQueryBuilder) Delete(tables ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "DELETE") - if len(tables) != 0 { - qb.Tokens = append(qb.Tokens, strings.Join(tables, CommaSpace)) - } - return qb + return (*orm.TiDBQueryBuilder)(qb).Delete(tables...) } // InsertInto join the insert SQL func (qb *TiDBQueryBuilder) InsertInto(table string, fields ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "INSERT INTO", table) - if len(fields) != 0 { - fieldsStr := strings.Join(fields, CommaSpace) - qb.Tokens = append(qb.Tokens, "(", fieldsStr, ")") - } - return qb + return (*orm.TiDBQueryBuilder)(qb).InsertInto(table, fields...) } // Values join the Values(vals) func (qb *TiDBQueryBuilder) Values(vals ...string) QueryBuilder { - valsStr := strings.Join(vals, CommaSpace) - qb.Tokens = append(qb.Tokens, "VALUES", "(", valsStr, ")") - return qb + return (*orm.TiDBQueryBuilder)(qb).Values(vals...) } // Subquery join the sub as alias func (qb *TiDBQueryBuilder) Subquery(sub string, alias string) string { - return fmt.Sprintf("(%s) AS %s", sub, alias) + return (*orm.TiDBQueryBuilder)(qb).Subquery(sub, alias) } // String join all Tokens func (qb *TiDBQueryBuilder) String() string { - return strings.Join(qb.Tokens, " ") + return (*orm.TiDBQueryBuilder)(qb).String() } diff --git a/adapter/orm/query_setter_adapter.go b/adapter/orm/query_setter_adapter.go new file mode 100644 index 00000000..d6c268b6 --- /dev/null +++ b/adapter/orm/query_setter_adapter.go @@ -0,0 +1,34 @@ +// Copyright 2020 +// +// Licensed 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 orm + +import ( + "github.com/astaxie/beego/client/orm" +) + +type baseQuerySetter struct { +} + +func (b *baseQuerySetter) ForceIndex(indexes ...string) orm.QuerySeter { + panic("you should not invoke this method.") +} + +func (b *baseQuerySetter) UseIndex(indexes ...string) orm.QuerySeter { + panic("you should not invoke this method.") +} + +func (b *baseQuerySetter) IgnoreIndex(indexes ...string) orm.QuerySeter { + panic("you should not invoke this method.") +} diff --git a/adapter/orm/types.go b/adapter/orm/types.go new file mode 100644 index 00000000..6db5066c --- /dev/null +++ b/adapter/orm/types.go @@ -0,0 +1,150 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "context" + "database/sql" + + "github.com/astaxie/beego/client/orm" +) + +// Params stores the Params +type Params orm.Params + +// ParamsList stores paramslist +type ParamsList orm.ParamsList + +// Driver define database driver +type Driver orm.Driver + +// Fielder define field info +type Fielder orm.Fielder + +// Ormer define the orm interface +type Ormer interface { + // read data to model + // for example: + // this will find User by Id field + // u = &User{Id: user.Id} + // err = Ormer.Read(u) + // this will find User by UserName field + // u = &User{UserName: "astaxie", Password: "pass"} + // err = Ormer.Read(u, "UserName") + Read(md interface{}, cols ...string) error + // Like Read(), but with "FOR UPDATE" clause, useful in transaction. + // Some databases are not support this feature. + ReadForUpdate(md interface{}, cols ...string) error + // Try to read a row from the database, or insert one if it doesn't exist + ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) + // insert model data to database + // for example: + // user := new(User) + // id, err = Ormer.Insert(user) + // user must be a pointer and Insert will set user's pk field + Insert(interface{}) (int64, error) + // mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value") + // if colu type is integer : can use(+-*/), string : convert(colu,"value") + // postgres: InsertOrUpdate(model,"conflictColumnName") or InsertOrUpdate(model,"conflictColumnName","colu=colu+value") + // if colu type is integer : can use(+-*/), string : colu || "value" + InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) + // insert some models to database + InsertMulti(bulk int, mds interface{}) (int64, error) + // update model to database. + // cols set the columns those want to update. + // find model by Id(pk) field and update columns specified by fields, if cols is null then update all columns + // for example: + // user := User{Id: 2} + // user.Langs = append(user.Langs, "zh-CN", "en-US") + // user.Extra.Name = "beego" + // user.Extra.Data = "orm" + // num, err = Ormer.Update(&user, "Langs", "Extra") + Update(md interface{}, cols ...string) (int64, error) + // delete model in database + Delete(md interface{}, cols ...string) (int64, error) + // load related models to md model. + // args are limit, offset int and order string. + // + // example: + // Ormer.LoadRelated(post,"Tags") + // for _,tag := range post.Tags{...} + // args[0] bool true useDefaultRelsDepth ; false depth 0 + // args[0] int loadRelationDepth + // args[1] int limit default limit 1000 + // args[2] int offset default offset 0 + // args[3] string order for example : "-Id" + // make sure the relation is defined in model struct tags. + LoadRelated(md interface{}, name string, args ...interface{}) (int64, error) + // create a models to models queryer + // for example: + // post := Post{Id: 4} + // m2m := Ormer.QueryM2M(&post, "Tags") + QueryM2M(md interface{}, name string) QueryM2Mer + // return a QuerySeter for table operations. + // table name can be string or struct. + // e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)), + QueryTable(ptrStructOrTableName interface{}) QuerySeter + // switch to another registered database driver by given name. + Using(name string) error + // begin transaction + // for example: + // o := NewOrm() + // err := o.Begin() + // ... + // err = o.Rollback() + Begin() error + // begin transaction with provided context and option + // the provided context is used until the transaction is committed or rolled back. + // if the context is canceled, the transaction will be rolled back. + // the provided TxOptions is optional and may be nil if defaults should be used. + // if a non-default isolation level is used that the driver doesn't support, an error will be returned. + // for example: + // o := NewOrm() + // err := o.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead}) + // ... + // err = o.Rollback() + BeginTx(ctx context.Context, opts *sql.TxOptions) error + // commit transaction + Commit() error + // rollback transaction + Rollback() error + // return a raw query seter for raw sql string. + // for example: + // ormer.Raw("UPDATE `user` SET `user_name` = ? WHERE `user_name` = ?", "slene", "testing").Exec() + // // update user testing's name to slene + Raw(query string, args ...interface{}) RawSeter + Driver() Driver + DBStats() *sql.DBStats +} + +// Inserter insert prepared statement +type Inserter orm.Inserter + +// QuerySeter query seter +type QuerySeter orm.QuerySeter + +// QueryM2Mer model to model query struct +// all operations are on the m2m table only, will not affect the origin model table +type QueryM2Mer orm.QueryM2Mer + +// RawPreparer raw query statement +type RawPreparer orm.RawPreparer + +// RawSeter raw query seter +// create From Ormer.Raw +// for example: +// sql := fmt.Sprintf("SELECT %sid%s,%sname%s FROM %suser%s WHERE id = ?",Q,Q,Q,Q,Q,Q) +// rs := Ormer.Raw(sql, 1) +type RawSeter orm.RawSeter diff --git a/adapter/orm/utils.go b/adapter/orm/utils.go new file mode 100644 index 00000000..37ba86d8 --- /dev/null +++ b/adapter/orm/utils.go @@ -0,0 +1,286 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "fmt" + "reflect" + "strconv" + "strings" + "time" + + "github.com/astaxie/beego/client/orm" +) + +type fn func(string) string + +var ( + nameStrategyMap = map[string]fn{ + defaultNameStrategy: snakeString, + SnakeAcronymNameStrategy: snakeStringWithAcronym, + } + defaultNameStrategy = "snakeString" + SnakeAcronymNameStrategy = "snakeStringWithAcronym" + nameStrategy = defaultNameStrategy +) + +// StrTo is the target string +type StrTo orm.StrTo + +// Set string +func (f *StrTo) Set(v string) { + (*orm.StrTo)(f).Set(v) +} + +// Clear string +func (f *StrTo) Clear() { + (*orm.StrTo)(f).Clear() +} + +// Exist check string exist +func (f StrTo) Exist() bool { + return orm.StrTo(f).Exist() +} + +// Bool string to bool +func (f StrTo) Bool() (bool, error) { + return orm.StrTo(f).Bool() +} + +// Float32 string to float32 +func (f StrTo) Float32() (float32, error) { + return orm.StrTo(f).Float32() +} + +// Float64 string to float64 +func (f StrTo) Float64() (float64, error) { + return orm.StrTo(f).Float64() +} + +// Int string to int +func (f StrTo) Int() (int, error) { + return orm.StrTo(f).Int() +} + +// Int8 string to int8 +func (f StrTo) Int8() (int8, error) { + return orm.StrTo(f).Int8() +} + +// Int16 string to int16 +func (f StrTo) Int16() (int16, error) { + return orm.StrTo(f).Int16() +} + +// Int32 string to int32 +func (f StrTo) Int32() (int32, error) { + return orm.StrTo(f).Int32() +} + +// Int64 string to int64 +func (f StrTo) Int64() (int64, error) { + return orm.StrTo(f).Int64() +} + +// Uint string to uint +func (f StrTo) Uint() (uint, error) { + return orm.StrTo(f).Uint() +} + +// Uint8 string to uint8 +func (f StrTo) Uint8() (uint8, error) { + return orm.StrTo(f).Uint8() +} + +// Uint16 string to uint16 +func (f StrTo) Uint16() (uint16, error) { + return orm.StrTo(f).Uint16() +} + +// Uint32 string to uint32 +func (f StrTo) Uint32() (uint32, error) { + return orm.StrTo(f).Uint32() +} + +// Uint64 string to uint64 +func (f StrTo) Uint64() (uint64, error) { + return orm.StrTo(f).Uint64() +} + +// String string to string +func (f StrTo) String() string { + return orm.StrTo(f).String() +} + +// ToStr interface to string +func ToStr(value interface{}, args ...int) (s string) { + switch v := value.(type) { + case bool: + s = strconv.FormatBool(v) + case float32: + s = strconv.FormatFloat(float64(v), 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 32)) + case float64: + s = strconv.FormatFloat(v, 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 64)) + case int: + s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) + case int8: + s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) + case int16: + s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) + case int32: + s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) + case int64: + s = strconv.FormatInt(v, argInt(args).Get(0, 10)) + case uint: + s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) + case uint8: + s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) + case uint16: + s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) + case uint32: + s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) + case uint64: + s = strconv.FormatUint(v, argInt(args).Get(0, 10)) + case string: + s = v + case []byte: + s = string(v) + default: + s = fmt.Sprintf("%v", v) + } + return s +} + +// ToInt64 interface to int64 +func ToInt64(value interface{}) (d int64) { + val := reflect.ValueOf(value) + switch value.(type) { + case int, int8, int16, int32, int64: + d = val.Int() + case uint, uint8, uint16, uint32, uint64: + d = int64(val.Uint()) + default: + panic(fmt.Errorf("ToInt64 need numeric not `%T`", value)) + } + return +} + +func snakeStringWithAcronym(s string) string { + data := make([]byte, 0, len(s)*2) + num := len(s) + for i := 0; i < num; i++ { + d := s[i] + before := false + after := false + if i > 0 { + before = s[i-1] >= 'a' && s[i-1] <= 'z' + } + if i+1 < num { + after = s[i+1] >= 'a' && s[i+1] <= 'z' + } + if i > 0 && d >= 'A' && d <= 'Z' && (before || after) { + data = append(data, '_') + } + data = append(data, d) + } + return strings.ToLower(string(data[:])) +} + +// snake string, XxYy to xx_yy , XxYY to xx_y_y +func snakeString(s string) string { + data := make([]byte, 0, len(s)*2) + j := false + num := len(s) + for i := 0; i < num; i++ { + d := s[i] + if i > 0 && d >= 'A' && d <= 'Z' && j { + data = append(data, '_') + } + if d != '_' { + j = true + } + data = append(data, d) + } + return strings.ToLower(string(data[:])) +} + +// SetNameStrategy set different name strategy +func SetNameStrategy(s string) { + if SnakeAcronymNameStrategy != s { + nameStrategy = defaultNameStrategy + } + nameStrategy = s +} + +// camel string, xx_yy to XxYy +func camelString(s string) string { + data := make([]byte, 0, len(s)) + flag, num := true, len(s)-1 + for i := 0; i <= num; i++ { + d := s[i] + if d == '_' { + flag = true + continue + } else if flag { + if d >= 'a' && d <= 'z' { + d = d - 32 + } + flag = false + } + data = append(data, d) + } + return string(data[:]) +} + +type argString []string + +// get string by index from string slice +func (a argString) Get(i int, args ...string) (r string) { + if i >= 0 && i < len(a) { + r = a[i] + } else if len(args) > 0 { + r = args[0] + } + return +} + +type argInt []int + +// get int by index from int slice +func (a argInt) Get(i int, args ...int) (r int) { + if i >= 0 && i < len(a) { + r = a[i] + } + if len(args) > 0 { + r = args[0] + } + return +} + +// parse time to string with location +func timeParse(dateString, format string) (time.Time, error) { + tp, err := time.ParseInLocation(format, dateString, DefaultTimeLoc) + return tp, err +} + +// get pointer indirect type +func indirectType(v reflect.Type) reflect.Type { + switch v.Kind() { + case reflect.Ptr: + return indirectType(v.Elem()) + default: + return v + } +} diff --git a/orm/utils_test.go b/adapter/orm/utils_test.go similarity index 100% rename from orm/utils_test.go rename to adapter/orm/utils_test.go diff --git a/adapter/plugins/apiauth/apiauth.go b/adapter/plugins/apiauth/apiauth.go new file mode 100644 index 00000000..90311d8f --- /dev/null +++ b/adapter/plugins/apiauth/apiauth.go @@ -0,0 +1,94 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 apiauth provides handlers to enable apiauth support. +// +// Simple Usage: +// import( +// "github.com/astaxie/beego" +// "github.com/astaxie/beego/plugins/apiauth" +// ) +// +// func main(){ +// // apiauth every request +// beego.InsertFilter("*", beego.BeforeRouter,apiauth.APIBaiscAuth("appid","appkey")) +// beego.Run() +// } +// +// Advanced Usage: +// +// func getAppSecret(appid string) string { +// // get appsecret by appid +// // maybe store in configure, maybe in database +// } +// +// beego.InsertFilter("*", beego.BeforeRouter,apiauth.APISecretAuth(getAppSecret, 360)) +// +// Information: +// +// In the request user should include these params in the query +// +// 1. appid +// +// appid is assigned to the application +// +// 2. signature +// +// get the signature use apiauth.Signature() +// +// when you send to server remember use url.QueryEscape() +// +// 3. timestamp: +// +// send the request time, the format is yyyy-mm-dd HH:ii:ss +// +package apiauth + +import ( + "net/url" + + beego "github.com/astaxie/beego/adapter" + "github.com/astaxie/beego/adapter/context" + beecontext "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/server/web/filter/apiauth" +) + +// AppIDToAppSecret is used to get appsecret throw appid +type AppIDToAppSecret apiauth.AppIDToAppSecret + +// APIBasicAuth use the basic appid/appkey as the AppIdToAppSecret +func APIBasicAuth(appid, appkey string) beego.FilterFunc { + f := apiauth.APIBasicAuth(appid, appkey) + return func(c *context.Context) { + f((*beecontext.Context)(c)) + } +} + +// APIBaiscAuth calls APIBasicAuth for previous callers +func APIBaiscAuth(appid, appkey string) beego.FilterFunc { + return APIBasicAuth(appid, appkey) +} + +// APISecretAuth use AppIdToAppSecret verify and +func APISecretAuth(f AppIDToAppSecret, timeout int) beego.FilterFunc { + ft := apiauth.APISecretAuth(apiauth.AppIDToAppSecret(f), timeout) + return func(ctx *context.Context) { + ft((*beecontext.Context)(ctx)) + } +} + +// Signature used to generate signature with the appsecret/method/params/RequestURI +func Signature(appsecret, method string, params url.Values, requestURL string) string { + return apiauth.Signature(appsecret, method, params, requestURL) +} diff --git a/plugins/apiauth/apiauth_test.go b/adapter/plugins/apiauth/apiauth_test.go similarity index 100% rename from plugins/apiauth/apiauth_test.go rename to adapter/plugins/apiauth/apiauth_test.go diff --git a/adapter/plugins/auth/basic.go b/adapter/plugins/auth/basic.go new file mode 100644 index 00000000..578a16d9 --- /dev/null +++ b/adapter/plugins/auth/basic.go @@ -0,0 +1,81 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 auth provides handlers to enable basic auth support. +// Simple Usage: +// import( +// "github.com/astaxie/beego" +// "github.com/astaxie/beego/plugins/auth" +// ) +// +// func main(){ +// // authenticate every request +// beego.InsertFilter("*", beego.BeforeRouter,auth.Basic("username","secretpassword")) +// beego.Run() +// } +// +// +// Advanced Usage: +// +// func SecretAuth(username, password string) bool { +// return username == "astaxie" && password == "helloBeego" +// } +// authPlugin := auth.NewBasicAuthenticator(SecretAuth, "Authorization Required") +// beego.InsertFilter("*", beego.BeforeRouter,authPlugin) +package auth + +import ( + "net/http" + + beego "github.com/astaxie/beego/adapter" + "github.com/astaxie/beego/adapter/context" + beecontext "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/server/web/filter/auth" +) + +// Basic is the http basic auth +func Basic(username string, password string) beego.FilterFunc { + return func(c *context.Context) { + f := auth.Basic(username, password) + f((*beecontext.Context)(c)) + } +} + +// NewBasicAuthenticator return the BasicAuth +func NewBasicAuthenticator(secrets SecretProvider, realm string) beego.FilterFunc { + f := auth.NewBasicAuthenticator(auth.SecretProvider(secrets), realm) + return func(c *context.Context) { + f((*beecontext.Context)(c)) + } +} + +// SecretProvider is the SecretProvider function +type SecretProvider auth.SecretProvider + +// BasicAuth store the SecretProvider and Realm +type BasicAuth auth.BasicAuth + +// CheckAuth Checks the username/password combination from the request. Returns +// either an empty string (authentication failed) or the name of the +// authenticated user. +// Supports MD5 and SHA1 password entries +func (a *BasicAuth) CheckAuth(r *http.Request) string { + return (*auth.BasicAuth)(a).CheckAuth(r) +} + +// RequireAuth http.Handler for BasicAuth which initiates the authentication process +// (or requires reauthentication). +func (a *BasicAuth) RequireAuth(w http.ResponseWriter, r *http.Request) { + (*auth.BasicAuth)(a).RequireAuth(w, r) +} diff --git a/adapter/plugins/authz/authz.go b/adapter/plugins/authz/authz.go new file mode 100644 index 00000000..3f84467e --- /dev/null +++ b/adapter/plugins/authz/authz.go @@ -0,0 +1,80 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 authz provides handlers to enable ACL, RBAC, ABAC authorization support. +// Simple Usage: +// import( +// "github.com/astaxie/beego" +// "github.com/astaxie/beego/plugins/authz" +// "github.com/casbin/casbin" +// ) +// +// func main(){ +// // mediate the access for every request +// beego.InsertFilter("*", beego.BeforeRouter, authz.NewAuthorizer(casbin.NewEnforcer("authz_model.conf", "authz_policy.csv"))) +// beego.Run() +// } +// +// +// Advanced Usage: +// +// func main(){ +// e := casbin.NewEnforcer("authz_model.conf", "") +// e.AddRoleForUser("alice", "admin") +// e.AddPolicy(...) +// +// beego.InsertFilter("*", beego.BeforeRouter, authz.NewAuthorizer(e)) +// beego.Run() +// } +package authz + +import ( + "net/http" + + "github.com/casbin/casbin" + + beego "github.com/astaxie/beego/adapter" + "github.com/astaxie/beego/adapter/context" + beecontext "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/server/web/filter/authz" +) + +// NewAuthorizer returns the authorizer. +// Use a casbin enforcer as input +func NewAuthorizer(e *casbin.Enforcer) beego.FilterFunc { + f := authz.NewAuthorizer(e) + return func(context *context.Context) { + f((*beecontext.Context)(context)) + } +} + +// BasicAuthorizer stores the casbin handler +type BasicAuthorizer authz.BasicAuthorizer + +// GetUserName gets the user name from the request. +// Currently, only HTTP basic authentication is supported +func (a *BasicAuthorizer) GetUserName(r *http.Request) string { + return (*authz.BasicAuthorizer)(a).GetUserName(r) +} + +// CheckPermission checks the user/method/path combination from the request. +// Returns true (permission granted) or false (permission forbidden) +func (a *BasicAuthorizer) CheckPermission(r *http.Request) bool { + return (*authz.BasicAuthorizer)(a).CheckPermission(r) +} + +// RequirePermission returns the 403 Forbidden to the client +func (a *BasicAuthorizer) RequirePermission(w http.ResponseWriter) { + (*authz.BasicAuthorizer)(a).RequirePermission(w) +} diff --git a/plugins/authz/authz_model.conf b/adapter/plugins/authz/authz_model.conf similarity index 100% rename from plugins/authz/authz_model.conf rename to adapter/plugins/authz/authz_model.conf diff --git a/plugins/authz/authz_policy.csv b/adapter/plugins/authz/authz_policy.csv similarity index 100% rename from plugins/authz/authz_policy.csv rename to adapter/plugins/authz/authz_policy.csv diff --git a/plugins/authz/authz_test.go b/adapter/plugins/authz/authz_test.go similarity index 96% rename from plugins/authz/authz_test.go rename to adapter/plugins/authz/authz_test.go index 49aed84c..9b4f21c2 100644 --- a/plugins/authz/authz_test.go +++ b/adapter/plugins/authz/authz_test.go @@ -15,13 +15,14 @@ package authz import ( - "github.com/astaxie/beego" - "github.com/astaxie/beego/context" - "github.com/astaxie/beego/plugins/auth" - "github.com/casbin/casbin" "net/http" "net/http/httptest" "testing" + + beego "github.com/astaxie/beego/adapter" + "github.com/astaxie/beego/adapter/context" + "github.com/astaxie/beego/adapter/plugins/auth" + "github.com/casbin/casbin" ) func testRequest(t *testing.T, handler *beego.ControllerRegister, user string, path string, method string, code int) { diff --git a/adapter/plugins/cors/cors.go b/adapter/plugins/cors/cors.go new file mode 100644 index 00000000..a15d5417 --- /dev/null +++ b/adapter/plugins/cors/cors.go @@ -0,0 +1,71 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 cors provides handlers to enable CORS support. +// Usage +// import ( +// "github.com/astaxie/beego" +// "github.com/astaxie/beego/plugins/cors" +// ) +// +// func main() { +// // CORS for https://foo.* origins, allowing: +// // - PUT and PATCH methods +// // - Origin header +// // - Credentials share +// beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{ +// AllowOrigins: []string{"https://*.foo.com"}, +// AllowMethods: []string{"PUT", "PATCH"}, +// AllowHeaders: []string{"Origin"}, +// ExposeHeaders: []string{"Content-Length"}, +// AllowCredentials: true, +// })) +// beego.Run() +// } +package cors + +import ( + beego "github.com/astaxie/beego/adapter" + beecontext "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/server/web/filter/cors" + + "github.com/astaxie/beego/adapter/context" +) + +// Options represents Access Control options. +type Options cors.Options + +// Header converts options into CORS headers. +func (o *Options) Header(origin string) (headers map[string]string) { + return (*cors.Options)(o).Header(origin) +} + +// PreflightHeader converts options into CORS headers for a preflight response. +func (o *Options) PreflightHeader(origin, rMethod, rHeaders string) (headers map[string]string) { + return (*cors.Options)(o).PreflightHeader(origin, rMethod, rHeaders) +} + +// IsOriginAllowed looks up if the origin matches one of the patterns +// generated from Options.AllowOrigins patterns. +func (o *Options) IsOriginAllowed(origin string) bool { + return (*cors.Options)(o).IsOriginAllowed(origin) +} + +// Allow enables CORS for requests those match the provided options. +func Allow(opts *Options) beego.FilterFunc { + f := cors.Allow((*cors.Options)(opts)) + return func(c *context.Context) { + f((*beecontext.Context)(c)) + } +} diff --git a/adapter/policy.go b/adapter/policy.go new file mode 100644 index 00000000..6f334d2d --- /dev/null +++ b/adapter/policy.go @@ -0,0 +1,57 @@ +// Copyright 2016 beego authors. All Rights Reserved. +// +// Licensed 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 adapter + +import ( + "github.com/astaxie/beego/adapter/context" + "github.com/astaxie/beego/server/web" + beecontext "github.com/astaxie/beego/server/web/context" +) + +// PolicyFunc defines a policy function which is invoked before the controller handler is executed. +type PolicyFunc func(*context.Context) + +// FindPolicy Find Router info for URL +func (p *ControllerRegister) FindPolicy(cont *context.Context) []PolicyFunc { + pf := (*web.ControllerRegister)(p).FindPolicy((*beecontext.Context)(cont)) + npf := newToOldPolicyFunc(pf) + return npf +} + +func newToOldPolicyFunc(pf []web.PolicyFunc) []PolicyFunc { + npf := make([]PolicyFunc, 0, len(pf)) + for _, f := range pf { + npf = append(npf, func(c *context.Context) { + f((*beecontext.Context)(c)) + }) + } + return npf +} + +func oldToNewPolicyFunc(pf []PolicyFunc) []web.PolicyFunc { + npf := make([]web.PolicyFunc, 0, len(pf)) + for _, f := range pf { + npf = append(npf, func(c *beecontext.Context) { + f((*context.Context)(c)) + }) + } + return npf +} + +// Policy Register new policy in beego +func Policy(pattern, method string, policy ...PolicyFunc) { + pf := oldToNewPolicyFunc(policy) + web.Policy(pattern, method, pf...) +} diff --git a/adapter/router.go b/adapter/router.go new file mode 100644 index 00000000..c91a09f1 --- /dev/null +++ b/adapter/router.go @@ -0,0 +1,282 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 adapter + +import ( + "net/http" + "time" + + beecontext "github.com/astaxie/beego/adapter/context" + "github.com/astaxie/beego/server/web/context" + + "github.com/astaxie/beego/server/web" +) + +// default filter execution points +const ( + BeforeStatic = web.BeforeStatic + BeforeRouter = web.BeforeRouter + BeforeExec = web.BeforeExec + AfterExec = web.AfterExec + FinishRouter = web.FinishRouter +) + +var ( + // HTTPMETHOD list the supported http methods. + HTTPMETHOD = web.HTTPMETHOD + + // DefaultAccessLogFilter will skip the accesslog if return true + DefaultAccessLogFilter FilterHandler = &newToOldFtHdlAdapter{ + delegate: web.DefaultAccessLogFilter, + } +) + +// FilterHandler is an interface for +type FilterHandler interface { + Filter(*beecontext.Context) bool +} + +type newToOldFtHdlAdapter struct { + delegate web.FilterHandler +} + +func (n *newToOldFtHdlAdapter) Filter(ctx *beecontext.Context) bool { + return n.delegate.Filter((*context.Context)(ctx)) +} + +// ExceptMethodAppend to append a slice's value into "exceptMethod", for controller's methods shouldn't reflect to AutoRouter +func ExceptMethodAppend(action string) { + web.ExceptMethodAppend(action) +} + +// ControllerInfo holds information about the controller. +type ControllerInfo web.ControllerInfo + +func (c *ControllerInfo) GetPattern() string { + return (*web.ControllerInfo)(c).GetPattern() +} + +// ControllerRegister containers registered router rules, controller handlers and filters. +type ControllerRegister web.ControllerRegister + +// NewControllerRegister returns a new ControllerRegister. +func NewControllerRegister() *ControllerRegister { + return (*ControllerRegister)(web.NewControllerRegister()) +} + +// Add controller handler and pattern rules to ControllerRegister. +// usage: +// default methods is the same name as method +// Add("/user",&UserController{}) +// Add("/api/list",&RestController{},"*:ListFood") +// Add("/api/create",&RestController{},"post:CreateFood") +// Add("/api/update",&RestController{},"put:UpdateFood") +// Add("/api/delete",&RestController{},"delete:DeleteFood") +// Add("/api",&RestController{},"get,post:ApiFunc" +// Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc") +func (p *ControllerRegister) Add(pattern string, c ControllerInterface, mappingMethods ...string) { + (*web.ControllerRegister)(p).Add(pattern, c, mappingMethods...) +} + +// Include only when the Runmode is dev will generate router file in the router/auto.go from the controller +// Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) +func (p *ControllerRegister) Include(cList ...ControllerInterface) { + nls := oldToNewCtrlIntfs(cList) + (*web.ControllerRegister)(p).Include(nls...) +} + +// GetContext returns a context from pool, so usually you should remember to call Reset function to clean the context +// And don't forget to give back context to pool +// example: +// ctx := p.GetContext() +// ctx.Reset(w, q) +// defer p.GiveBackContext(ctx) +func (p *ControllerRegister) GetContext() *beecontext.Context { + return (*beecontext.Context)((*web.ControllerRegister)(p).GetContext()) +} + +// GiveBackContext put the ctx into pool so that it could be reuse +func (p *ControllerRegister) GiveBackContext(ctx *beecontext.Context) { + (*web.ControllerRegister)(p).GiveBackContext((*context.Context)(ctx)) +} + +// Get add get method +// usage: +// Get("/", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegister) Get(pattern string, f FilterFunc) { + (*web.ControllerRegister)(p).Get(pattern, func(ctx *context.Context) { + f((*beecontext.Context)(ctx)) + }) +} + +// Post add post method +// usage: +// Post("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegister) Post(pattern string, f FilterFunc) { + (*web.ControllerRegister)(p).Post(pattern, func(ctx *context.Context) { + f((*beecontext.Context)(ctx)) + }) +} + +// Put add put method +// usage: +// Put("/api/:id", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegister) Put(pattern string, f FilterFunc) { + (*web.ControllerRegister)(p).Put(pattern, func(ctx *context.Context) { + f((*beecontext.Context)(ctx)) + }) +} + +// Delete add delete method +// usage: +// Delete("/api/:id", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegister) Delete(pattern string, f FilterFunc) { + (*web.ControllerRegister)(p).Delete(pattern, func(ctx *context.Context) { + f((*beecontext.Context)(ctx)) + }) +} + +// Head add head method +// usage: +// Head("/api/:id", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegister) Head(pattern string, f FilterFunc) { + (*web.ControllerRegister)(p).Head(pattern, func(ctx *context.Context) { + f((*beecontext.Context)(ctx)) + }) +} + +// Patch add patch method +// usage: +// Patch("/api/:id", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegister) Patch(pattern string, f FilterFunc) { + (*web.ControllerRegister)(p).Patch(pattern, func(ctx *context.Context) { + f((*beecontext.Context)(ctx)) + }) +} + +// Options add options method +// usage: +// Options("/api/:id", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegister) Options(pattern string, f FilterFunc) { + (*web.ControllerRegister)(p).Options(pattern, func(ctx *context.Context) { + f((*beecontext.Context)(ctx)) + }) +} + +// Any add all method +// usage: +// Any("/api/:id", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegister) Any(pattern string, f FilterFunc) { + (*web.ControllerRegister)(p).Any(pattern, func(ctx *context.Context) { + f((*beecontext.Context)(ctx)) + }) +} + +// AddMethod add http method router +// usage: +// AddMethod("get","/api/:id", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) { + (*web.ControllerRegister)(p).AddMethod(method, pattern, func(ctx *context.Context) { + f((*beecontext.Context)(ctx)) + }) +} + +// Handler add user defined Handler +func (p *ControllerRegister) Handler(pattern string, h http.Handler, options ...interface{}) { + (*web.ControllerRegister)(p).Handler(pattern, h, options) +} + +// AddAuto router to ControllerRegister. +// example beego.AddAuto(&MainContorlller{}), +// MainController has method List and Page. +// visit the url /main/list to execute List function +// /main/page to execute Page function. +func (p *ControllerRegister) AddAuto(c ControllerInterface) { + (*web.ControllerRegister)(p).AddAuto(c) +} + +// AddAutoPrefix Add auto router to ControllerRegister with prefix. +// example beego.AddAutoPrefix("/admin",&MainContorlller{}), +// MainController has method List and Page. +// visit the url /admin/main/list to execute List function +// /admin/main/page to execute Page function. +func (p *ControllerRegister) AddAutoPrefix(prefix string, c ControllerInterface) { + (*web.ControllerRegister)(p).AddAutoPrefix(prefix, c) +} + +// InsertFilter Add a FilterFunc with pattern rule and action constant. +// params is for: +// 1. setting the returnOnOutput value (false allows multiple filters to execute) +// 2. determining whether or not params need to be reset. +func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) error { + opts := oldToNewFilterOpts(params) + return (*web.ControllerRegister)(p).InsertFilter(pattern, pos, func(ctx *context.Context) { + filter((*beecontext.Context)(ctx)) + }, opts...) +} + +func oldToNewFilterOpts(params []bool) []web.FilterOpt { + opts := make([]web.FilterOpt, 0, 4) + if len(params) > 0 { + opts = append(opts, web.WithReturnOnOutput(params[0])) + } else { + // the default value should be true + opts = append(opts, web.WithReturnOnOutput(true)) + } + if len(params) > 1 { + opts = append(opts, web.WithResetParams(params[1])) + } + return opts +} + +// URLFor does another controller handler in this request function. +// it can access any controller method. +func (p *ControllerRegister) URLFor(endpoint string, values ...interface{}) string { + return (*web.ControllerRegister)(p).URLFor(endpoint, values...) +} + +// Implement http.Handler interface. +func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + (*web.ControllerRegister)(p).ServeHTTP(rw, r) +} + +// FindRouter Find Router info for URL +func (p *ControllerRegister) FindRouter(ctx *beecontext.Context) (routerInfo *ControllerInfo, isFind bool) { + r, ok := (*web.ControllerRegister)(p).FindRouter((*context.Context)(ctx)) + return (*ControllerInfo)(r), ok +} + +// LogAccess logging info HTTP Access +func LogAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) { + web.LogAccess((*context.Context)(ctx), startTime, statusCode) +} diff --git a/adapter/session/couchbase/sess_couchbase.go b/adapter/session/couchbase/sess_couchbase.go new file mode 100644 index 00000000..b6afb612 --- /dev/null +++ b/adapter/session/couchbase/sess_couchbase.go @@ -0,0 +1,118 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 couchbase for session provider +// +// depend on github.com/couchbaselabs/go-couchbasee +// +// go install github.com/couchbaselabs/go-couchbase +// +// Usage: +// import( +// _ "github.com/astaxie/beego/session/couchbase" +// "github.com/astaxie/beego/session" +// ) +// +// func init() { +// globalSessions, _ = session.NewManager("couchbase", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"http://host:port/, Pool, Bucket"}``) +// go globalSessions.GC() +// } +// +// more docs: http://beego.me/docs/module/session.md +package couchbase + +import ( + "context" + "net/http" + + "github.com/astaxie/beego/adapter/session" + beecb "github.com/astaxie/beego/server/web/session/couchbase" +) + +// SessionStore store each session +type SessionStore beecb.SessionStore + +// Provider couchabse provided +type Provider beecb.Provider + +// Set value to couchabse session +func (cs *SessionStore) Set(key, value interface{}) error { + return (*beecb.SessionStore)(cs).Set(context.Background(), key, value) +} + +// Get value from couchabse session +func (cs *SessionStore) Get(key interface{}) interface{} { + return (*beecb.SessionStore)(cs).Get(context.Background(), key) +} + +// Delete value in couchbase session by given key +func (cs *SessionStore) Delete(key interface{}) error { + return (*beecb.SessionStore)(cs).Delete(context.Background(), key) +} + +// Flush Clean all values in couchbase session +func (cs *SessionStore) Flush() error { + return (*beecb.SessionStore)(cs).Flush(context.Background()) +} + +// SessionID Get couchbase session store id +func (cs *SessionStore) SessionID() string { + return (*beecb.SessionStore)(cs).SessionID(context.Background()) +} + +// SessionRelease Write couchbase session with Gob string +func (cs *SessionStore) SessionRelease(w http.ResponseWriter) { + (*beecb.SessionStore)(cs).SessionRelease(context.Background(), w) +} + +// SessionInit init couchbase session +// savepath like couchbase server REST/JSON URL +// e.g. http://host:port/, Pool, Bucket +func (cp *Provider) SessionInit(maxlifetime int64, savePath string) error { + return (*beecb.Provider)(cp).SessionInit(context.Background(), maxlifetime, savePath) +} + +// SessionRead read couchbase session by sid +func (cp *Provider) SessionRead(sid string) (session.Store, error) { + s, err := (*beecb.Provider)(cp).SessionRead(context.Background(), sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionExist Check couchbase session exist. +// it checkes sid exist or not. +func (cp *Provider) SessionExist(sid string) bool { + res, _ := (*beecb.Provider)(cp).SessionExist(context.Background(), sid) + return res +} + +// SessionRegenerate remove oldsid and use sid to generate new session +func (cp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { + s, err := (*beecb.Provider)(cp).SessionRegenerate(context.Background(), oldsid, sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionDestroy Remove bucket in this couchbase +func (cp *Provider) SessionDestroy(sid string) error { + return (*beecb.Provider)(cp).SessionDestroy(context.Background(), sid) +} + +// SessionGC Recycle +func (cp *Provider) SessionGC() { + (*beecb.Provider)(cp).SessionGC(context.Background()) +} + +// SessionAll return all active session +func (cp *Provider) SessionAll() int { + return (*beecb.Provider)(cp).SessionAll(context.Background()) +} diff --git a/adapter/session/ledis/ledis_session.go b/adapter/session/ledis/ledis_session.go new file mode 100644 index 00000000..350cbdaa --- /dev/null +++ b/adapter/session/ledis/ledis_session.go @@ -0,0 +1,86 @@ +// Package ledis provide session Provider +package ledis + +import ( + "context" + "net/http" + + "github.com/astaxie/beego/adapter/session" + beeLedis "github.com/astaxie/beego/server/web/session/ledis" +) + +// SessionStore ledis session store +type SessionStore beeLedis.SessionStore + +// Set value in ledis session +func (ls *SessionStore) Set(key, value interface{}) error { + return (*beeLedis.SessionStore)(ls).Set(context.Background(), key, value) +} + +// Get value in ledis session +func (ls *SessionStore) Get(key interface{}) interface{} { + return (*beeLedis.SessionStore)(ls).Get(context.Background(), key) +} + +// Delete value in ledis session +func (ls *SessionStore) Delete(key interface{}) error { + return (*beeLedis.SessionStore)(ls).Delete(context.Background(), key) +} + +// Flush clear all values in ledis session +func (ls *SessionStore) Flush() error { + return (*beeLedis.SessionStore)(ls).Flush(context.Background()) +} + +// SessionID get ledis session id +func (ls *SessionStore) SessionID() string { + return (*beeLedis.SessionStore)(ls).SessionID(context.Background()) +} + +// SessionRelease save session values to ledis +func (ls *SessionStore) SessionRelease(w http.ResponseWriter) { + (*beeLedis.SessionStore)(ls).SessionRelease(context.Background(), w) +} + +// Provider ledis session provider +type Provider beeLedis.Provider + +// SessionInit init ledis session +// savepath like ledis server saveDataPath,pool size +// e.g. 127.0.0.1:6379,100,astaxie +func (lp *Provider) SessionInit(maxlifetime int64, savePath string) error { + return (*beeLedis.Provider)(lp).SessionInit(context.Background(), maxlifetime, savePath) +} + +// SessionRead read ledis session by sid +func (lp *Provider) SessionRead(sid string) (session.Store, error) { + s, err := (*beeLedis.Provider)(lp).SessionRead(context.Background(), sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionExist check ledis session exist by sid +func (lp *Provider) SessionExist(sid string) bool { + res, _ := (*beeLedis.Provider)(lp).SessionExist(context.Background(), sid) + return res +} + +// SessionRegenerate generate new sid for ledis session +func (lp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { + s, err := (*beeLedis.Provider)(lp).SessionRegenerate(context.Background(), oldsid, sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionDestroy delete ledis session by id +func (lp *Provider) SessionDestroy(sid string) error { + return (*beeLedis.Provider)(lp).SessionDestroy(context.Background(), sid) +} + +// SessionGC Impelment method, no used. +func (lp *Provider) SessionGC() { + (*beeLedis.Provider)(lp).SessionGC(context.Background()) +} + +// SessionAll return all active session +func (lp *Provider) SessionAll() int { + return (*beeLedis.Provider)(lp).SessionAll(context.Background()) +} diff --git a/adapter/session/memcache/sess_memcache.go b/adapter/session/memcache/sess_memcache.go new file mode 100644 index 00000000..772839cd --- /dev/null +++ b/adapter/session/memcache/sess_memcache.go @@ -0,0 +1,118 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 memcache for session provider +// +// depend on github.com/bradfitz/gomemcache/memcache +// +// go install github.com/bradfitz/gomemcache/memcache +// +// Usage: +// import( +// _ "github.com/astaxie/beego/session/memcache" +// "github.com/astaxie/beego/session" +// ) +// +// func init() { +// globalSessions, _ = session.NewManager("memcache", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:11211"}``) +// go globalSessions.GC() +// } +// +// more docs: http://beego.me/docs/module/session.md +package memcache + +import ( + "context" + "net/http" + + "github.com/astaxie/beego/adapter/session" + + beemem "github.com/astaxie/beego/server/web/session/memcache" +) + +// SessionStore memcache session store +type SessionStore beemem.SessionStore + +// Set value in memcache session +func (rs *SessionStore) Set(key, value interface{}) error { + return (*beemem.SessionStore)(rs).Set(context.Background(), key, value) +} + +// Get value in memcache session +func (rs *SessionStore) Get(key interface{}) interface{} { + return (*beemem.SessionStore)(rs).Get(context.Background(), key) +} + +// Delete value in memcache session +func (rs *SessionStore) Delete(key interface{}) error { + return (*beemem.SessionStore)(rs).Delete(context.Background(), key) +} + +// Flush clear all values in memcache session +func (rs *SessionStore) Flush() error { + return (*beemem.SessionStore)(rs).Flush(context.Background()) +} + +// SessionID get memcache session id +func (rs *SessionStore) SessionID() string { + return (*beemem.SessionStore)(rs).SessionID(context.Background()) +} + +// SessionRelease save session values to memcache +func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { + (*beemem.SessionStore)(rs).SessionRelease(context.Background(), w) +} + +// MemProvider memcache session provider +type MemProvider beemem.MemProvider + +// SessionInit init memcache session +// savepath like +// e.g. 127.0.0.1:9090 +func (rp *MemProvider) SessionInit(maxlifetime int64, savePath string) error { + return (*beemem.MemProvider)(rp).SessionInit(context.Background(), maxlifetime, savePath) +} + +// SessionRead read memcache session by sid +func (rp *MemProvider) SessionRead(sid string) (session.Store, error) { + s, err := (*beemem.MemProvider)(rp).SessionRead(context.Background(), sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionExist check memcache session exist by sid +func (rp *MemProvider) SessionExist(sid string) bool { + res, _ := (*beemem.MemProvider)(rp).SessionExist(context.Background(), sid) + return res +} + +// SessionRegenerate generate new sid for memcache session +func (rp *MemProvider) SessionRegenerate(oldsid, sid string) (session.Store, error) { + s, err := (*beemem.MemProvider)(rp).SessionRegenerate(context.Background(), oldsid, sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionDestroy delete memcache session by id +func (rp *MemProvider) SessionDestroy(sid string) error { + return (*beemem.MemProvider)(rp).SessionDestroy(context.Background(), sid) +} + +// SessionGC Impelment method, no used. +func (rp *MemProvider) SessionGC() { + (*beemem.MemProvider)(rp).SessionGC(context.Background()) +} + +// SessionAll return all activeSession +func (rp *MemProvider) SessionAll() int { + return (*beemem.MemProvider)(rp).SessionAll(context.Background()) +} diff --git a/adapter/session/mysql/sess_mysql.go b/adapter/session/mysql/sess_mysql.go new file mode 100644 index 00000000..5d7e1dac --- /dev/null +++ b/adapter/session/mysql/sess_mysql.go @@ -0,0 +1,135 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 mysql for session provider +// +// depends on github.com/go-sql-driver/mysql: +// +// go install github.com/go-sql-driver/mysql +// +// mysql session support need create table as sql: +// CREATE TABLE `session` ( +// `session_key` char(64) NOT NULL, +// `session_data` blob, +// `session_expiry` int(11) unsigned NOT NULL, +// PRIMARY KEY (`session_key`) +// ) ENGINE=MyISAM DEFAULT CHARSET=utf8; +// +// Usage: +// import( +// _ "github.com/astaxie/beego/session/mysql" +// "github.com/astaxie/beego/session" +// ) +// +// func init() { +// globalSessions, _ = session.NewManager("mysql", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]"}``) +// go globalSessions.GC() +// } +// +// more docs: http://beego.me/docs/module/session.md +package mysql + +import ( + "context" + "net/http" + + "github.com/astaxie/beego/adapter/session" + "github.com/astaxie/beego/server/web/session/mysql" + + // import mysql driver + _ "github.com/go-sql-driver/mysql" +) + +var ( + // TableName store the session in MySQL + TableName = mysql.TableName + mysqlpder = &Provider{} +) + +// SessionStore mysql session store +type SessionStore mysql.SessionStore + +// Set value in mysql session. +// it is temp value in map. +func (st *SessionStore) Set(key, value interface{}) error { + return (*mysql.SessionStore)(st).Set(context.Background(), key, value) +} + +// Get value from mysql session +func (st *SessionStore) Get(key interface{}) interface{} { + return (*mysql.SessionStore)(st).Get(context.Background(), key) +} + +// Delete value in mysql session +func (st *SessionStore) Delete(key interface{}) error { + return (*mysql.SessionStore)(st).Delete(context.Background(), key) +} + +// Flush clear all values in mysql session +func (st *SessionStore) Flush() error { + return (*mysql.SessionStore)(st).Flush(context.Background()) +} + +// SessionID get session id of this mysql session store +func (st *SessionStore) SessionID() string { + return (*mysql.SessionStore)(st).SessionID(context.Background()) +} + +// SessionRelease save mysql session values to database. +// must call this method to save values to database. +func (st *SessionStore) SessionRelease(w http.ResponseWriter) { + (*mysql.SessionStore)(st).SessionRelease(context.Background(), w) +} + +// Provider mysql session provider +type Provider mysql.Provider + +// SessionInit init mysql session. +// savepath is the connection string of mysql. +func (mp *Provider) SessionInit(maxlifetime int64, savePath string) error { + return (*mysql.Provider)(mp).SessionInit(context.Background(), maxlifetime, savePath) +} + +// SessionRead get mysql session by sid +func (mp *Provider) SessionRead(sid string) (session.Store, error) { + s, err := (*mysql.Provider)(mp).SessionRead(context.Background(), sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionExist check mysql session exist +func (mp *Provider) SessionExist(sid string) bool { + res, _ := (*mysql.Provider)(mp).SessionExist(context.Background(), sid) + return res +} + +// SessionRegenerate generate new sid for mysql session +func (mp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { + s, err := (*mysql.Provider)(mp).SessionRegenerate(context.Background(), oldsid, sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionDestroy delete mysql session by sid +func (mp *Provider) SessionDestroy(sid string) error { + return (*mysql.Provider)(mp).SessionDestroy(context.Background(), sid) +} + +// SessionGC delete expired values in mysql session +func (mp *Provider) SessionGC() { + (*mysql.Provider)(mp).SessionGC(context.Background()) +} + +// SessionAll count values in mysql session +func (mp *Provider) SessionAll() int { + return (*mysql.Provider)(mp).SessionAll(context.Background()) +} diff --git a/adapter/session/postgres/sess_postgresql.go b/adapter/session/postgres/sess_postgresql.go new file mode 100644 index 00000000..879b2b83 --- /dev/null +++ b/adapter/session/postgres/sess_postgresql.go @@ -0,0 +1,139 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 postgres for session provider +// +// depends on github.com/lib/pq: +// +// go install github.com/lib/pq +// +// +// needs this table in your database: +// +// CREATE TABLE session ( +// session_key char(64) NOT NULL, +// session_data bytea, +// session_expiry timestamp NOT NULL, +// CONSTRAINT session_key PRIMARY KEY(session_key) +// ); +// +// will be activated with these settings in app.conf: +// +// SessionOn = true +// SessionProvider = postgresql +// SessionSavePath = "user=a password=b dbname=c sslmode=disable" +// SessionName = session +// +// +// Usage: +// import( +// _ "github.com/astaxie/beego/session/postgresql" +// "github.com/astaxie/beego/session" +// ) +// +// func init() { +// globalSessions, _ = session.NewManager("postgresql", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"user=pqgotest dbname=pqgotest sslmode=verify-full"}``) +// go globalSessions.GC() +// } +// +// more docs: http://beego.me/docs/module/session.md +package postgres + +import ( + "context" + "net/http" + + "github.com/astaxie/beego/adapter/session" + // import postgresql Driver + _ "github.com/lib/pq" + + "github.com/astaxie/beego/server/web/session/postgres" +) + +// SessionStore postgresql session store +type SessionStore postgres.SessionStore + +// Set value in postgresql session. +// it is temp value in map. +func (st *SessionStore) Set(key, value interface{}) error { + return (*postgres.SessionStore)(st).Set(context.Background(), key, value) +} + +// Get value from postgresql session +func (st *SessionStore) Get(key interface{}) interface{} { + return (*postgres.SessionStore)(st).Get(context.Background(), key) +} + +// Delete value in postgresql session +func (st *SessionStore) Delete(key interface{}) error { + return (*postgres.SessionStore)(st).Delete(context.Background(), key) +} + +// Flush clear all values in postgresql session +func (st *SessionStore) Flush() error { + return (*postgres.SessionStore)(st).Flush(context.Background()) +} + +// SessionID get session id of this postgresql session store +func (st *SessionStore) SessionID() string { + return (*postgres.SessionStore)(st).SessionID(context.Background()) +} + +// SessionRelease save postgresql session values to database. +// must call this method to save values to database. +func (st *SessionStore) SessionRelease(w http.ResponseWriter) { + (*postgres.SessionStore)(st).SessionRelease(context.Background(), w) +} + +// Provider postgresql session provider +type Provider postgres.Provider + +// SessionInit init postgresql session. +// savepath is the connection string of postgresql. +func (mp *Provider) SessionInit(maxlifetime int64, savePath string) error { + return (*postgres.Provider)(mp).SessionInit(context.Background(), maxlifetime, savePath) +} + +// SessionRead get postgresql session by sid +func (mp *Provider) SessionRead(sid string) (session.Store, error) { + s, err := (*postgres.Provider)(mp).SessionRead(context.Background(), sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionExist check postgresql session exist +func (mp *Provider) SessionExist(sid string) bool { + res, _ := (*postgres.Provider)(mp).SessionExist(context.Background(), sid) + return res +} + +// SessionRegenerate generate new sid for postgresql session +func (mp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { + s, err := (*postgres.Provider)(mp).SessionRegenerate(context.Background(), oldsid, sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionDestroy delete postgresql session by sid +func (mp *Provider) SessionDestroy(sid string) error { + return (*postgres.Provider)(mp).SessionDestroy(context.Background(), sid) +} + +// SessionGC delete expired values in postgresql session +func (mp *Provider) SessionGC() { + (*postgres.Provider)(mp).SessionGC(context.Background()) +} + +// SessionAll count values in postgresql session +func (mp *Provider) SessionAll() int { + return (*postgres.Provider)(mp).SessionAll(context.Background()) +} diff --git a/adapter/session/provider_adapter.go b/adapter/session/provider_adapter.go new file mode 100644 index 00000000..596bc6a6 --- /dev/null +++ b/adapter/session/provider_adapter.go @@ -0,0 +1,104 @@ +// Copyright 2020 +// +// Licensed 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 session + +import ( + "context" + + "github.com/astaxie/beego/server/web/session" +) + +type oldToNewProviderAdapter struct { + delegate Provider +} + +func (o *oldToNewProviderAdapter) SessionInit(ctx context.Context, gclifetime int64, config string) error { + return o.delegate.SessionInit(gclifetime, config) +} + +func (o *oldToNewProviderAdapter) SessionRead(ctx context.Context, sid string) (session.Store, error) { + store, err := o.delegate.SessionRead(sid) + return &oldToNewStoreAdapter{ + delegate: store, + }, err +} + +func (o *oldToNewProviderAdapter) SessionExist(ctx context.Context, sid string) (bool, error) { + return o.delegate.SessionExist(sid), nil +} + +func (o *oldToNewProviderAdapter) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { + s, err := o.delegate.SessionRegenerate(oldsid, sid) + return &oldToNewStoreAdapter{ + delegate: s, + }, err +} + +func (o *oldToNewProviderAdapter) SessionDestroy(ctx context.Context, sid string) error { + return o.delegate.SessionDestroy(sid) +} + +func (o *oldToNewProviderAdapter) SessionAll(ctx context.Context) int { + return o.delegate.SessionAll() +} + +func (o *oldToNewProviderAdapter) SessionGC(ctx context.Context) { + o.delegate.SessionGC() +} + +type newToOldProviderAdapter struct { + delegate session.Provider +} + +func (n *newToOldProviderAdapter) SessionInit(gclifetime int64, config string) error { + return n.delegate.SessionInit(context.Background(), gclifetime, config) +} + +func (n *newToOldProviderAdapter) SessionRead(sid string) (Store, error) { + s, err := n.delegate.SessionRead(context.Background(), sid) + if adt, ok := s.(*oldToNewStoreAdapter); err == nil && ok { + return adt.delegate, err + } + return &NewToOldStoreAdapter{ + delegate: s, + }, err +} + +func (n *newToOldProviderAdapter) SessionExist(sid string) bool { + res, _ := n.delegate.SessionExist(context.Background(), sid) + return res +} + +func (n *newToOldProviderAdapter) SessionRegenerate(oldsid, sid string) (Store, error) { + s, err := n.delegate.SessionRegenerate(context.Background(), oldsid, sid) + if adt, ok := s.(*oldToNewStoreAdapter); err == nil && ok { + return adt.delegate, err + } + return &NewToOldStoreAdapter{ + delegate: s, + }, err +} + +func (n *newToOldProviderAdapter) SessionDestroy(sid string) error { + return n.delegate.SessionDestroy(context.Background(), sid) +} + +func (n *newToOldProviderAdapter) SessionAll() int { + return n.delegate.SessionAll(context.Background()) +} + +func (n *newToOldProviderAdapter) SessionGC() { + n.delegate.SessionGC(context.Background()) +} diff --git a/adapter/session/redis/sess_redis.go b/adapter/session/redis/sess_redis.go new file mode 100644 index 00000000..bb8e8be4 --- /dev/null +++ b/adapter/session/redis/sess_redis.go @@ -0,0 +1,121 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 redis for session provider +// +// depend on github.com/gomodule/redigo/redis +// +// go install github.com/gomodule/redigo/redis +// +// Usage: +// import( +// _ "github.com/astaxie/beego/session/redis" +// "github.com/astaxie/beego/session" +// ) +// +// func init() { +// globalSessions, _ = session.NewManager("redis", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:7070"}``) +// go globalSessions.GC() +// } +// +// more docs: http://beego.me/docs/module/session.md +package redis + +import ( + "context" + "net/http" + + "github.com/astaxie/beego/adapter/session" + + beeRedis "github.com/astaxie/beego/server/web/session/redis" +) + +// MaxPoolSize redis max pool size +var MaxPoolSize = beeRedis.MaxPoolSize + +// SessionStore redis session store +type SessionStore beeRedis.SessionStore + +// Set value in redis session +func (rs *SessionStore) Set(key, value interface{}) error { + return (*beeRedis.SessionStore)(rs).Set(context.Background(), key, value) +} + +// Get value in redis session +func (rs *SessionStore) Get(key interface{}) interface{} { + return (*beeRedis.SessionStore)(rs).Get(context.Background(), key) +} + +// Delete value in redis session +func (rs *SessionStore) Delete(key interface{}) error { + return (*beeRedis.SessionStore)(rs).Delete(context.Background(), key) +} + +// Flush clear all values in redis session +func (rs *SessionStore) Flush() error { + return (*beeRedis.SessionStore)(rs).Flush(context.Background()) +} + +// SessionID get redis session id +func (rs *SessionStore) SessionID() string { + return (*beeRedis.SessionStore)(rs).SessionID(context.Background()) +} + +// SessionRelease save session values to redis +func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { + (*beeRedis.SessionStore)(rs).SessionRelease(context.Background(), w) +} + +// Provider redis session provider +type Provider beeRedis.Provider + +// SessionInit init redis session +// savepath like redis server addr,pool size,password,dbnum,IdleTimeout second +// e.g. 127.0.0.1:6379,100,astaxie,0,30 +func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error { + return (*beeRedis.Provider)(rp).SessionInit(context.Background(), maxlifetime, savePath) +} + +// SessionRead read redis session by sid +func (rp *Provider) SessionRead(sid string) (session.Store, error) { + s, err := (*beeRedis.Provider)(rp).SessionRead(context.Background(), sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionExist check redis session exist by sid +func (rp *Provider) SessionExist(sid string) bool { + res, _ := (*beeRedis.Provider)(rp).SessionExist(context.Background(), sid) + return res +} + +// SessionRegenerate generate new sid for redis session +func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { + s, err := (*beeRedis.Provider)(rp).SessionRegenerate(context.Background(), oldsid, sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionDestroy delete redis session by id +func (rp *Provider) SessionDestroy(sid string) error { + return (*beeRedis.Provider)(rp).SessionDestroy(context.Background(), sid) +} + +// SessionGC Impelment method, no used. +func (rp *Provider) SessionGC() { + (*beeRedis.Provider)(rp).SessionGC(context.Background()) +} + +// SessionAll return all activeSession +func (rp *Provider) SessionAll() int { + return (*beeRedis.Provider)(rp).SessionAll(context.Background()) +} diff --git a/adapter/session/redis_cluster/redis_cluster.go b/adapter/session/redis_cluster/redis_cluster.go new file mode 100644 index 00000000..1be22cd4 --- /dev/null +++ b/adapter/session/redis_cluster/redis_cluster.go @@ -0,0 +1,120 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 redis for session provider +// +// depend on github.com/go-redis/redis +// +// go install github.com/go-redis/redis +// +// Usage: +// import( +// _ "github.com/astaxie/beego/session/redis_cluster" +// "github.com/astaxie/beego/session" +// ) +// +// func init() { +// globalSessions, _ = session.NewManager("redis_cluster", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:7070;127.0.0.1:7071"}``) +// go globalSessions.GC() +// } +// +// more docs: http://beego.me/docs/module/session.md +package redis_cluster + +import ( + "context" + "net/http" + + "github.com/astaxie/beego/adapter/session" + cluster "github.com/astaxie/beego/server/web/session/redis_cluster" +) + +// MaxPoolSize redis_cluster max pool size +var MaxPoolSize = cluster.MaxPoolSize + +// SessionStore redis_cluster session store +type SessionStore cluster.SessionStore + +// Set value in redis_cluster session +func (rs *SessionStore) Set(key, value interface{}) error { + return (*cluster.SessionStore)(rs).Set(context.Background(), key, value) +} + +// Get value in redis_cluster session +func (rs *SessionStore) Get(key interface{}) interface{} { + return (*cluster.SessionStore)(rs).Get(context.Background(), key) +} + +// Delete value in redis_cluster session +func (rs *SessionStore) Delete(key interface{}) error { + return (*cluster.SessionStore)(rs).Delete(context.Background(), key) +} + +// Flush clear all values in redis_cluster session +func (rs *SessionStore) Flush() error { + return (*cluster.SessionStore)(rs).Flush(context.Background()) +} + +// SessionID get redis_cluster session id +func (rs *SessionStore) SessionID() string { + return (*cluster.SessionStore)(rs).SessionID(context.Background()) +} + +// SessionRelease save session values to redis_cluster +func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { + (*cluster.SessionStore)(rs).SessionRelease(context.Background(), w) +} + +// Provider redis_cluster session provider +type Provider cluster.Provider + +// SessionInit init redis_cluster session +// savepath like redis server addr,pool size,password,dbnum +// e.g. 127.0.0.1:6379;127.0.0.1:6380,100,test,0 +func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error { + return (*cluster.Provider)(rp).SessionInit(context.Background(), maxlifetime, savePath) +} + +// SessionRead read redis_cluster session by sid +func (rp *Provider) SessionRead(sid string) (session.Store, error) { + s, err := (*cluster.Provider)(rp).SessionRead(context.Background(), sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionExist check redis_cluster session exist by sid +func (rp *Provider) SessionExist(sid string) bool { + res, _ := (*cluster.Provider)(rp).SessionExist(context.Background(), sid) + return res +} + +// SessionRegenerate generate new sid for redis_cluster session +func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { + s, err := (*cluster.Provider)(rp).SessionRegenerate(context.Background(), oldsid, sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionDestroy delete redis session by id +func (rp *Provider) SessionDestroy(sid string) error { + return (*cluster.Provider)(rp).SessionDestroy(context.Background(), sid) +} + +// SessionGC Impelment method, no used. +func (rp *Provider) SessionGC() { + (*cluster.Provider)(rp).SessionGC(context.Background()) +} + +// SessionAll return all activeSession +func (rp *Provider) SessionAll() int { + return (*cluster.Provider)(rp).SessionAll(context.Background()) +} diff --git a/adapter/session/redis_sentinel/sess_redis_sentinel.go b/adapter/session/redis_sentinel/sess_redis_sentinel.go new file mode 100644 index 00000000..7ab9e7c5 --- /dev/null +++ b/adapter/session/redis_sentinel/sess_redis_sentinel.go @@ -0,0 +1,121 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 redis for session provider +// +// depend on github.com/go-redis/redis +// +// go install github.com/go-redis/redis +// +// Usage: +// import( +// _ "github.com/astaxie/beego/session/redis_sentinel" +// "github.com/astaxie/beego/session" +// ) +// +// func init() { +// globalSessions, _ = session.NewManager("redis_sentinel", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:26379;127.0.0.2:26379"}``) +// go globalSessions.GC() +// } +// +// more detail about params: please check the notes on the function SessionInit in this package +package redis_sentinel + +import ( + "context" + "net/http" + + "github.com/astaxie/beego/adapter/session" + + sentinel "github.com/astaxie/beego/server/web/session/redis_sentinel" +) + +// DefaultPoolSize redis_sentinel default pool size +var DefaultPoolSize = sentinel.DefaultPoolSize + +// SessionStore redis_sentinel session store +type SessionStore sentinel.SessionStore + +// Set value in redis_sentinel session +func (rs *SessionStore) Set(key, value interface{}) error { + return (*sentinel.SessionStore)(rs).Set(context.Background(), key, value) +} + +// Get value in redis_sentinel session +func (rs *SessionStore) Get(key interface{}) interface{} { + return (*sentinel.SessionStore)(rs).Get(context.Background(), key) +} + +// Delete value in redis_sentinel session +func (rs *SessionStore) Delete(key interface{}) error { + return (*sentinel.SessionStore)(rs).Delete(context.Background(), key) +} + +// Flush clear all values in redis_sentinel session +func (rs *SessionStore) Flush() error { + return (*sentinel.SessionStore)(rs).Flush(context.Background()) +} + +// SessionID get redis_sentinel session id +func (rs *SessionStore) SessionID() string { + return (*sentinel.SessionStore)(rs).SessionID(context.Background()) +} + +// SessionRelease save session values to redis_sentinel +func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { + (*sentinel.SessionStore)(rs).SessionRelease(context.Background(), w) +} + +// Provider redis_sentinel session provider +type Provider sentinel.Provider + +// SessionInit init redis_sentinel session +// savepath like redis sentinel addr,pool size,password,dbnum,masterName +// e.g. 127.0.0.1:26379;127.0.0.2:26379,100,1qaz2wsx,0,mymaster +func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error { + return (*sentinel.Provider)(rp).SessionInit(context.Background(), maxlifetime, savePath) +} + +// SessionRead read redis_sentinel session by sid +func (rp *Provider) SessionRead(sid string) (session.Store, error) { + s, err := (*sentinel.Provider)(rp).SessionRead(context.Background(), sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionExist check redis_sentinel session exist by sid +func (rp *Provider) SessionExist(sid string) bool { + res, _ := (*sentinel.Provider)(rp).SessionExist(context.Background(), sid) + return res +} + +// SessionRegenerate generate new sid for redis_sentinel session +func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { + s, err := (*sentinel.Provider)(rp).SessionRegenerate(context.Background(), oldsid, sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionDestroy delete redis session by id +func (rp *Provider) SessionDestroy(sid string) error { + return (*sentinel.Provider)(rp).SessionDestroy(context.Background(), sid) +} + +// SessionGC Impelment method, no used. +func (rp *Provider) SessionGC() { + (*sentinel.Provider)(rp).SessionGC(context.Background()) +} + +// SessionAll return all activeSession +func (rp *Provider) SessionAll() int { + return (*sentinel.Provider)(rp).SessionAll(context.Background()) +} diff --git a/session/redis_sentinel/sess_redis_sentinel_test.go b/adapter/session/redis_sentinel/sess_redis_sentinel_test.go similarity index 96% rename from session/redis_sentinel/sess_redis_sentinel_test.go rename to adapter/session/redis_sentinel/sess_redis_sentinel_test.go index fd4155c6..407d32ab 100644 --- a/session/redis_sentinel/sess_redis_sentinel_test.go +++ b/adapter/session/redis_sentinel/sess_redis_sentinel_test.go @@ -5,7 +5,7 @@ import ( "net/http/httptest" "testing" - "github.com/astaxie/beego/session" + "github.com/astaxie/beego/adapter/session" ) func TestRedisSentinel(t *testing.T) { @@ -23,7 +23,7 @@ func TestRedisSentinel(t *testing.T) { t.Log(e) return } - //todo test if e==nil + // todo test if e==nil go globalSessions.GC() r, _ := http.NewRequest("GET", "/", nil) diff --git a/adapter/session/sess_cookie.go b/adapter/session/sess_cookie.go new file mode 100644 index 00000000..3fcbd28e --- /dev/null +++ b/adapter/session/sess_cookie.go @@ -0,0 +1,114 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 session + +import ( + "context" + "net/http" + + "github.com/astaxie/beego/server/web/session" +) + +// CookieSessionStore Cookie SessionStore +type CookieSessionStore session.CookieSessionStore + +// Set value to cookie session. +// the value are encoded as gob with hash block string. +func (st *CookieSessionStore) Set(key, value interface{}) error { + return (*session.CookieSessionStore)(st).Set(context.Background(), key, value) +} + +// Get value from cookie session +func (st *CookieSessionStore) Get(key interface{}) interface{} { + return (*session.CookieSessionStore)(st).Get(context.Background(), key) +} + +// Delete value in cookie session +func (st *CookieSessionStore) Delete(key interface{}) error { + return (*session.CookieSessionStore)(st).Delete(context.Background(), key) +} + +// Flush Clean all values in cookie session +func (st *CookieSessionStore) Flush() error { + return (*session.CookieSessionStore)(st).Flush(context.Background()) +} + +// SessionID Return id of this cookie session +func (st *CookieSessionStore) SessionID() string { + return (*session.CookieSessionStore)(st).SessionID(context.Background()) +} + +// SessionRelease Write cookie session to http response cookie +func (st *CookieSessionStore) SessionRelease(w http.ResponseWriter) { + (*session.CookieSessionStore)(st).SessionRelease(context.Background(), w) +} + +// CookieProvider Cookie session provider +type CookieProvider session.CookieProvider + +// SessionInit Init cookie session provider with max lifetime and config json. +// maxlifetime is ignored. +// json config: +// securityKey - hash string +// blockKey - gob encode hash string. it's saved as aes crypto. +// securityName - recognized name in encoded cookie string +// cookieName - cookie name +// maxage - cookie max life time. +func (pder *CookieProvider) SessionInit(maxlifetime int64, config string) error { + return (*session.CookieProvider)(pder).SessionInit(context.Background(), maxlifetime, config) +} + +// SessionRead Get SessionStore in cooke. +// decode cooke string to map and put into SessionStore with sid. +func (pder *CookieProvider) SessionRead(sid string) (Store, error) { + s, err := (*session.CookieProvider)(pder).SessionRead(context.Background(), sid) + return &NewToOldStoreAdapter{ + delegate: s, + }, err +} + +// SessionExist Cookie session is always existed +func (pder *CookieProvider) SessionExist(sid string) bool { + res, _ := (*session.CookieProvider)(pder).SessionExist(context.Background(), sid) + return res +} + +// SessionRegenerate Implement method, no used. +func (pder *CookieProvider) SessionRegenerate(oldsid, sid string) (Store, error) { + s, err := (*session.CookieProvider)(pder).SessionRegenerate(context.Background(), oldsid, sid) + return &NewToOldStoreAdapter{ + delegate: s, + }, err +} + +// SessionDestroy Implement method, no used. +func (pder *CookieProvider) SessionDestroy(sid string) error { + return (*session.CookieProvider)(pder).SessionDestroy(context.Background(), sid) +} + +// SessionGC Implement method, no used. +func (pder *CookieProvider) SessionGC() { + (*session.CookieProvider)(pder).SessionGC(context.Background()) +} + +// SessionAll Implement method, return 0. +func (pder *CookieProvider) SessionAll() int { + return (*session.CookieProvider)(pder).SessionAll(context.Background()) +} + +// SessionUpdate Implement method, no used. +func (pder *CookieProvider) SessionUpdate(sid string) error { + return (*session.CookieProvider)(pder).SessionUpdate(context.Background(), sid) +} diff --git a/session/sess_cookie_test.go b/adapter/session/sess_cookie_test.go similarity index 100% rename from session/sess_cookie_test.go rename to adapter/session/sess_cookie_test.go diff --git a/adapter/session/sess_file.go b/adapter/session/sess_file.go new file mode 100644 index 00000000..2ba33e6d --- /dev/null +++ b/adapter/session/sess_file.go @@ -0,0 +1,106 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 session + +import ( + "context" + "net/http" + + "github.com/astaxie/beego/server/web/session" +) + +// FileSessionStore File session store +type FileSessionStore session.FileSessionStore + +// Set value to file session +func (fs *FileSessionStore) Set(key, value interface{}) error { + return (*session.FileSessionStore)(fs).Set(context.Background(), key, value) +} + +// Get value from file session +func (fs *FileSessionStore) Get(key interface{}) interface{} { + return (*session.FileSessionStore)(fs).Get(context.Background(), key) +} + +// Delete value in file session by given key +func (fs *FileSessionStore) Delete(key interface{}) error { + return (*session.FileSessionStore)(fs).Delete(context.Background(), key) +} + +// Flush Clean all values in file session +func (fs *FileSessionStore) Flush() error { + return (*session.FileSessionStore)(fs).Flush(context.Background()) +} + +// SessionID Get file session store id +func (fs *FileSessionStore) SessionID() string { + return (*session.FileSessionStore)(fs).SessionID(context.Background()) +} + +// SessionRelease Write file session to local file with Gob string +func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) { + (*session.FileSessionStore)(fs).SessionRelease(context.Background(), w) +} + +// FileProvider File session provider +type FileProvider session.FileProvider + +// SessionInit Init file session provider. +// savePath sets the session files path. +func (fp *FileProvider) SessionInit(maxlifetime int64, savePath string) error { + return (*session.FileProvider)(fp).SessionInit(context.Background(), maxlifetime, savePath) +} + +// SessionRead Read file session by sid. +// if file is not exist, create it. +// the file path is generated from sid string. +func (fp *FileProvider) SessionRead(sid string) (Store, error) { + s, err := (*session.FileProvider)(fp).SessionRead(context.Background(), sid) + return &NewToOldStoreAdapter{ + delegate: s, + }, err +} + +// SessionExist Check file session exist. +// it checks the file named from sid exist or not. +func (fp *FileProvider) SessionExist(sid string) bool { + res, _ := (*session.FileProvider)(fp).SessionExist(context.Background(), sid) + return res +} + +// SessionDestroy Remove all files in this save path +func (fp *FileProvider) SessionDestroy(sid string) error { + return (*session.FileProvider)(fp).SessionDestroy(context.Background(), sid) +} + +// SessionGC Recycle files in save path +func (fp *FileProvider) SessionGC() { + (*session.FileProvider)(fp).SessionGC(context.Background()) +} + +// SessionAll Get active file session number. +// it walks save path to count files. +func (fp *FileProvider) SessionAll() int { + return (*session.FileProvider)(fp).SessionAll(context.Background()) +} + +// SessionRegenerate Generate new sid for file session. +// it delete old file and create new file named from new sid. +func (fp *FileProvider) SessionRegenerate(oldsid, sid string) (Store, error) { + s, err := (*session.FileProvider)(fp).SessionRegenerate(context.Background(), oldsid, sid) + return &NewToOldStoreAdapter{ + delegate: s, + }, err +} diff --git a/adapter/session/sess_file_test.go b/adapter/session/sess_file_test.go new file mode 100644 index 00000000..4c90a3ac --- /dev/null +++ b/adapter/session/sess_file_test.go @@ -0,0 +1,336 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 session + +import ( + "fmt" + "os" + "sync" + "testing" + "time" +) + +const sid = "Session_id" +const sidNew = "Session_id_new" +const sessionPath = "./_session_runtime" + +var ( + mutex sync.Mutex +) + +func TestFileProvider_SessionExist(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(180, sessionPath) + + if fp.SessionExist(sid) { + t.Error() + } + + _, err := fp.SessionRead(sid) + if err != nil { + t.Error(err) + } + + if !fp.SessionExist(sid) { + t.Error() + } +} + +func TestFileProvider_SessionExist2(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(180, sessionPath) + + if fp.SessionExist(sid) { + t.Error() + } + + if fp.SessionExist("") { + t.Error() + } + + if fp.SessionExist("1") { + t.Error() + } +} + +func TestFileProvider_SessionRead(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(180, sessionPath) + + s, err := fp.SessionRead(sid) + if err != nil { + t.Error(err) + } + + _ = s.Set("sessionValue", 18975) + v := s.Get("sessionValue") + + if v.(int) != 18975 { + t.Error() + } +} + +func TestFileProvider_SessionRead1(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(180, sessionPath) + + _, err := fp.SessionRead("") + if err == nil { + t.Error(err) + } + + _, err = fp.SessionRead("1") + if err == nil { + t.Error(err) + } +} + +func TestFileProvider_SessionAll(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(180, sessionPath) + + sessionCount := 546 + + for i := 1; i <= sessionCount; i++ { + _, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i)) + if err != nil { + t.Error(err) + } + } + + if fp.SessionAll() != sessionCount { + t.Error() + } +} + +func TestFileProvider_SessionRegenerate(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(180, sessionPath) + + _, err := fp.SessionRead(sid) + if err != nil { + t.Error(err) + } + + if !fp.SessionExist(sid) { + t.Error() + } + + _, err = fp.SessionRegenerate(sid, sidNew) + if err != nil { + t.Error(err) + } + + if fp.SessionExist(sid) { + t.Error() + } + + if !fp.SessionExist(sidNew) { + t.Error() + } +} + +func TestFileProvider_SessionDestroy(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(180, sessionPath) + + _, err := fp.SessionRead(sid) + if err != nil { + t.Error(err) + } + + if !fp.SessionExist(sid) { + t.Error() + } + + err = fp.SessionDestroy(sid) + if err != nil { + t.Error(err) + } + + if fp.SessionExist(sid) { + t.Error() + } +} + +func TestFileProvider_SessionGC(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(1, sessionPath) + + sessionCount := 412 + + for i := 1; i <= sessionCount; i++ { + _, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i)) + if err != nil { + t.Error(err) + } + } + + time.Sleep(2 * time.Second) + + fp.SessionGC() + if fp.SessionAll() != 0 { + t.Error() + } +} + +func TestFileSessionStore_Set(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(180, sessionPath) + + sessionCount := 100 + s, _ := fp.SessionRead(sid) + for i := 1; i <= sessionCount; i++ { + err := s.Set(i, i) + if err != nil { + t.Error(err) + } + } +} + +func TestFileSessionStore_Get(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(180, sessionPath) + + sessionCount := 100 + s, _ := fp.SessionRead(sid) + for i := 1; i <= sessionCount; i++ { + _ = s.Set(i, i) + + v := s.Get(i) + if v.(int) != i { + t.Error() + } + } +} + +func TestFileSessionStore_Delete(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(180, sessionPath) + + s, _ := fp.SessionRead(sid) + s.Set("1", 1) + + if s.Get("1") == nil { + t.Error() + } + + s.Delete("1") + + if s.Get("1") != nil { + t.Error() + } +} + +func TestFileSessionStore_Flush(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(180, sessionPath) + + sessionCount := 100 + s, _ := fp.SessionRead(sid) + for i := 1; i <= sessionCount; i++ { + _ = s.Set(i, i) + } + + _ = s.Flush() + + for i := 1; i <= sessionCount; i++ { + if s.Get(i) != nil { + t.Error() + } + } +} + +func TestFileSessionStore_SessionID(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(180, sessionPath) + + sessionCount := 85 + + for i := 1; i <= sessionCount; i++ { + s, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i)) + if err != nil { + t.Error(err) + } + if s.SessionID() != fmt.Sprintf("%s_%d", sid, i) { + t.Error(err) + } + } +} diff --git a/adapter/session/sess_mem.go b/adapter/session/sess_mem.go new file mode 100644 index 00000000..febed719 --- /dev/null +++ b/adapter/session/sess_mem.go @@ -0,0 +1,106 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 session + +import ( + "context" + "net/http" + + "github.com/astaxie/beego/server/web/session" +) + +// MemSessionStore memory session store. +// it saved sessions in a map in memory. +type MemSessionStore session.MemSessionStore + +// Set value to memory session +func (st *MemSessionStore) Set(key, value interface{}) error { + return (*session.MemSessionStore)(st).Set(context.Background(), key, value) +} + +// Get value from memory session by key +func (st *MemSessionStore) Get(key interface{}) interface{} { + return (*session.MemSessionStore)(st).Get(context.Background(), key) +} + +// Delete in memory session by key +func (st *MemSessionStore) Delete(key interface{}) error { + return (*session.MemSessionStore)(st).Delete(context.Background(), key) +} + +// Flush clear all values in memory session +func (st *MemSessionStore) Flush() error { + return (*session.MemSessionStore)(st).Flush(context.Background()) +} + +// SessionID get this id of memory session store +func (st *MemSessionStore) SessionID() string { + return (*session.MemSessionStore)(st).SessionID(context.Background()) +} + +// SessionRelease Implement method, no used. +func (st *MemSessionStore) SessionRelease(w http.ResponseWriter) { + (*session.MemSessionStore)(st).SessionRelease(context.Background(), w) +} + +// MemProvider Implement the provider interface +type MemProvider session.MemProvider + +// SessionInit init memory session +func (pder *MemProvider) SessionInit(maxlifetime int64, savePath string) error { + return (*session.MemProvider)(pder).SessionInit(context.Background(), maxlifetime, savePath) +} + +// SessionRead get memory session store by sid +func (pder *MemProvider) SessionRead(sid string) (Store, error) { + s, err := (*session.MemProvider)(pder).SessionRead(context.Background(), sid) + return &NewToOldStoreAdapter{ + delegate: s, + }, err +} + +// SessionExist check session store exist in memory session by sid +func (pder *MemProvider) SessionExist(sid string) bool { + res, _ := (*session.MemProvider)(pder).SessionExist(context.Background(), sid) + return res +} + +// SessionRegenerate generate new sid for session store in memory session +func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (Store, error) { + s, err := (*session.MemProvider)(pder).SessionRegenerate(context.Background(), oldsid, sid) + return &NewToOldStoreAdapter{ + delegate: s, + }, err +} + +// SessionDestroy delete session store in memory session by id +func (pder *MemProvider) SessionDestroy(sid string) error { + return (*session.MemProvider)(pder).SessionDestroy(context.Background(), sid) +} + +// SessionGC clean expired session stores in memory session +func (pder *MemProvider) SessionGC() { + (*session.MemProvider)(pder).SessionGC(context.Background()) +} + +// SessionAll get count number of memory session +func (pder *MemProvider) SessionAll() int { + return (*session.MemProvider)(pder).SessionAll(context.Background()) +} + +// SessionUpdate expand time of session store by id in memory session +func (pder *MemProvider) SessionUpdate(sid string) error { + return (*session.MemProvider)(pder).SessionUpdate(context.Background(), sid) +} diff --git a/session/sess_mem_test.go b/adapter/session/sess_mem_test.go similarity index 100% rename from session/sess_mem_test.go rename to adapter/session/sess_mem_test.go diff --git a/adapter/session/sess_test.go b/adapter/session/sess_test.go new file mode 100644 index 00000000..aba702ca --- /dev/null +++ b/adapter/session/sess_test.go @@ -0,0 +1,51 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 session + +import ( + "testing" +) + +func Test_gob(t *testing.T) { + a := make(map[interface{}]interface{}) + a["username"] = "astaxie" + a[12] = 234 + a["user"] = User{"asta", "xie"} + b, err := EncodeGob(a) + if err != nil { + t.Error(err) + } + c, err := DecodeGob(b) + if err != nil { + t.Error(err) + } + if len(c) == 0 { + t.Error("decodeGob empty") + } + if c["username"] != "astaxie" { + t.Error("decode string error") + } + if c[12] != 234 { + t.Error("decode int error") + } + if c["user"].(User).Username != "asta" { + t.Error("decode struct error") + } +} + +type User struct { + Username string + NickName string +} diff --git a/adapter/session/sess_utils.go b/adapter/session/sess_utils.go new file mode 100644 index 00000000..4cfdc760 --- /dev/null +++ b/adapter/session/sess_utils.go @@ -0,0 +1,29 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 session + +import ( + "github.com/astaxie/beego/server/web/session" +) + +// EncodeGob encode the obj to gob +func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) { + return session.EncodeGob(obj) +} + +// DecodeGob decode data to map +func DecodeGob(encoded []byte) (map[interface{}]interface{}, error) { + return session.DecodeGob(encoded) +} diff --git a/adapter/session/session.go b/adapter/session/session.go new file mode 100644 index 00000000..d8b151b7 --- /dev/null +++ b/adapter/session/session.go @@ -0,0 +1,166 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 session provider +// +// Usage: +// import( +// "github.com/astaxie/beego/session" +// ) +// +// func init() { +// globalSessions, _ = session.NewManager("memory", `{"cookieName":"gosessionid", "enableSetCookie,omitempty": true, "gclifetime":3600, "maxLifetime": 3600, "secure": false, "cookieLifeTime": 3600, "providerConfig": ""}`) +// go globalSessions.GC() +// } +// +// more docs: http://beego.me/docs/module/session.md +package session + +import ( + "io" + "net/http" + "os" + + "github.com/astaxie/beego/server/web/session" +) + +// Store contains all data for one session process with specific id. +type Store interface { + Set(key, value interface{}) error // set session value + Get(key interface{}) interface{} // get session value + Delete(key interface{}) error // delete session value + SessionID() string // back current sessionID + SessionRelease(w http.ResponseWriter) // release the resource & save data to provider & return the data + Flush() error // delete all data +} + +// Provider contains global session methods and saved SessionStores. +// it can operate a SessionStore by its id. +type Provider interface { + SessionInit(gclifetime int64, config string) error + SessionRead(sid string) (Store, error) + SessionExist(sid string) bool + SessionRegenerate(oldsid, sid string) (Store, error) + SessionDestroy(sid string) error + SessionAll() int // get all active session + SessionGC() +} + +// SLogger a helpful variable to log information about session +var SLogger = NewSessionLog(os.Stderr) + +// Register makes a session provide available by the provided name. +// If Register is called twice with the same name or if driver is nil, +// it panics. +func Register(name string, provide Provider) { + session.Register(name, &oldToNewProviderAdapter{ + delegate: provide, + }) +} + +// GetProvider +func GetProvider(name string) (Provider, error) { + res, err := session.GetProvider(name) + if adt, ok := res.(*oldToNewProviderAdapter); err == nil && ok { + return adt.delegate, err + } + + return &newToOldProviderAdapter{ + delegate: res, + }, err +} + +// ManagerConfig define the session config +type ManagerConfig session.ManagerConfig + +// Manager contains Provider and its configuration. +type Manager session.Manager + +// NewManager Create new Manager with provider name and json config string. +// provider name: +// 1. cookie +// 2. file +// 3. memory +// 4. redis +// 5. mysql +// json config: +// 1. is https default false +// 2. hashfunc default sha1 +// 3. hashkey default beegosessionkey +// 4. maxage default is none +func NewManager(provideName string, cf *ManagerConfig) (*Manager, error) { + m, err := session.NewManager(provideName, (*session.ManagerConfig)(cf)) + return (*Manager)(m), err +} + +// GetProvider return current manager's provider +func (manager *Manager) GetProvider() Provider { + return &newToOldProviderAdapter{ + delegate: (*session.Manager)(manager).GetProvider(), + } +} + +// SessionStart generate or read the session id from http request. +// if session id exists, return SessionStore with this id. +func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (Store, error) { + s, err := (*session.Manager)(manager).SessionStart(w, r) + return &NewToOldStoreAdapter{ + delegate: s, + }, err +} + +// SessionDestroy Destroy session by its id in http request cookie. +func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) { + (*session.Manager)(manager).SessionDestroy(w, r) +} + +// GetSessionStore Get SessionStore by its id. +func (manager *Manager) GetSessionStore(sid string) (Store, error) { + s, err := (*session.Manager)(manager).GetSessionStore(sid) + return &NewToOldStoreAdapter{ + delegate: s, + }, err +} + +// GC Start session gc process. +// it can do gc in times after gc lifetime. +func (manager *Manager) GC() { + (*session.Manager)(manager).GC() +} + +// SessionRegenerateID Regenerate a session id for this SessionStore who's id is saving in http request. +func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Request) Store { + s := (*session.Manager)(manager).SessionRegenerateID(w, r) + return &NewToOldStoreAdapter{ + delegate: s, + } +} + +// GetActiveSession Get all active sessions count number. +func (manager *Manager) GetActiveSession() int { + return (*session.Manager)(manager).GetActiveSession() +} + +// SetSecure Set cookie with https. +func (manager *Manager) SetSecure(secure bool) { + (*session.Manager)(manager).SetSecure(secure) +} + +// Log implement the log.Logger +type Log session.Log + +// NewSessionLog set io.Writer to create a Logger for session. +func NewSessionLog(out io.Writer) *Log { + return (*Log)(session.NewSessionLog(out)) +} diff --git a/adapter/session/ssdb/sess_ssdb.go b/adapter/session/ssdb/sess_ssdb.go new file mode 100644 index 00000000..cd9c4a24 --- /dev/null +++ b/adapter/session/ssdb/sess_ssdb.go @@ -0,0 +1,84 @@ +package ssdb + +import ( + "context" + "net/http" + + "github.com/astaxie/beego/adapter/session" + + beeSsdb "github.com/astaxie/beego/server/web/session/ssdb" +) + +// Provider holds ssdb client and configs +type Provider beeSsdb.Provider + +// SessionInit init the ssdb with the config +func (p *Provider) SessionInit(maxLifetime int64, savePath string) error { + return (*beeSsdb.Provider)(p).SessionInit(context.Background(), maxLifetime, savePath) +} + +// SessionRead return a ssdb client session Store +func (p *Provider) SessionRead(sid string) (session.Store, error) { + s, err := (*beeSsdb.Provider)(p).SessionRead(context.Background(), sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionExist judged whether sid is exist in session +func (p *Provider) SessionExist(sid string) bool { + res, _ := (*beeSsdb.Provider)(p).SessionExist(context.Background(), sid) + return res +} + +// SessionRegenerate regenerate session with new sid and delete oldsid +func (p *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { + s, err := (*beeSsdb.Provider)(p).SessionRegenerate(context.Background(), oldsid, sid) + return session.CreateNewToOldStoreAdapter(s), err +} + +// SessionDestroy destroy the sid +func (p *Provider) SessionDestroy(sid string) error { + return (*beeSsdb.Provider)(p).SessionDestroy(context.Background(), sid) +} + +// SessionGC not implemented +func (p *Provider) SessionGC() { + (*beeSsdb.Provider)(p).SessionGC(context.Background()) +} + +// SessionAll not implemented +func (p *Provider) SessionAll() int { + return (*beeSsdb.Provider)(p).SessionAll(context.Background()) +} + +// SessionStore holds the session information which stored in ssdb +type SessionStore beeSsdb.SessionStore + +// Set the key and value +func (s *SessionStore) Set(key, value interface{}) error { + return (*beeSsdb.SessionStore)(s).Set(context.Background(), key, value) +} + +// Get return the value by the key +func (s *SessionStore) Get(key interface{}) interface{} { + return (*beeSsdb.SessionStore)(s).Get(context.Background(), key) +} + +// Delete the key in session store +func (s *SessionStore) Delete(key interface{}) error { + return (*beeSsdb.SessionStore)(s).Delete(context.Background(), key) +} + +// Flush delete all keys and values +func (s *SessionStore) Flush() error { + return (*beeSsdb.SessionStore)(s).Flush(context.Background()) +} + +// SessionID return the sessionID +func (s *SessionStore) SessionID() string { + return (*beeSsdb.SessionStore)(s).SessionID(context.Background()) +} + +// SessionRelease Store the keyvalues into ssdb +func (s *SessionStore) SessionRelease(w http.ResponseWriter) { + (*beeSsdb.SessionStore)(s).SessionRelease(context.Background(), w) +} diff --git a/adapter/session/store_adapter.go b/adapter/session/store_adapter.go new file mode 100644 index 00000000..70ad83e2 --- /dev/null +++ b/adapter/session/store_adapter.go @@ -0,0 +1,84 @@ +// Copyright 2020 +// +// Licensed 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 session + +import ( + "context" + "net/http" + + "github.com/astaxie/beego/server/web/session" +) + +type NewToOldStoreAdapter struct { + delegate session.Store +} + +func CreateNewToOldStoreAdapter(s session.Store) Store { + return &NewToOldStoreAdapter{ + delegate: s, + } +} + +func (n *NewToOldStoreAdapter) Set(key, value interface{}) error { + return n.delegate.Set(context.Background(), key, value) +} + +func (n *NewToOldStoreAdapter) Get(key interface{}) interface{} { + return n.delegate.Get(context.Background(), key) +} + +func (n *NewToOldStoreAdapter) Delete(key interface{}) error { + return n.delegate.Delete(context.Background(), key) +} + +func (n *NewToOldStoreAdapter) SessionID() string { + return n.delegate.SessionID(context.Background()) +} + +func (n *NewToOldStoreAdapter) SessionRelease(w http.ResponseWriter) { + n.delegate.SessionRelease(context.Background(), w) +} + +func (n *NewToOldStoreAdapter) Flush() error { + return n.delegate.Flush(context.Background()) +} + +type oldToNewStoreAdapter struct { + delegate Store +} + +func (o *oldToNewStoreAdapter) Set(ctx context.Context, key, value interface{}) error { + return o.delegate.Set(key, value) +} + +func (o *oldToNewStoreAdapter) Get(ctx context.Context, key interface{}) interface{} { + return o.delegate.Get(key) +} + +func (o *oldToNewStoreAdapter) Delete(ctx context.Context, key interface{}) error { + return o.delegate.Delete(key) +} + +func (o *oldToNewStoreAdapter) SessionID(ctx context.Context) string { + return o.delegate.SessionID() +} + +func (o *oldToNewStoreAdapter) SessionRelease(ctx context.Context, w http.ResponseWriter) { + o.delegate.SessionRelease(w) +} + +func (o *oldToNewStoreAdapter) Flush(ctx context.Context) error { + return o.delegate.Flush() +} diff --git a/adapter/swagger/swagger.go b/adapter/swagger/swagger.go new file mode 100644 index 00000000..7a44b770 --- /dev/null +++ b/adapter/swagger/swagger.go @@ -0,0 +1,68 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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. +// +// Swagger™ is a project used to describe and document RESTful APIs. +// +// The Swagger specification defines a set of files required to describe such an API. These files can then be used by the Swagger-UI project to display the API and Swagger-Codegen to generate clients in various languages. Additional utilities can also take advantage of the resulting files, such as testing tools. +// Now in version 2.0, Swagger is more enabling than ever. And it's 100% open source software. + +// Package swagger struct definition +package swagger + +import ( + "github.com/astaxie/beego/server/web/swagger" +) + +// Swagger list the resource +type Swagger swagger.Swagger + +// Information Provides metadata about the API. The metadata can be used by the clients if needed. +type Information swagger.Information + +// Contact information for the exposed API. +type Contact swagger.Contact + +// License information for the exposed API. +type License swagger.License + +// Item Describes the operations available on a single path. +type Item swagger.Item + +// Operation Describes a single API operation on a path. +type Operation swagger.Operation + +// Parameter Describes a single operation parameter. +type Parameter swagger.Parameter + +// ParameterItems A limited subset of JSON-Schema's items object. It is used by parameter definitions that are not located in "body". +// http://swagger.io/specification/#itemsObject +type ParameterItems swagger.ParameterItems + +// Schema Object allows the definition of input and output data types. +type Schema swagger.Schema + +// Propertie are taken from the JSON Schema definition but their definitions were adjusted to the Swagger Specification +type Propertie swagger.Propertie + +// Response as they are returned from executing this operation. +type Response swagger.Response + +// Security Allows the definition of a security scheme that can be used by the operations +type Security swagger.Security + +// Tag Allows adding meta data to a single tag that is used by the Operation Object +type Tag swagger.Tag + +// ExternalDocs include Additional external documentation +type ExternalDocs swagger.ExternalDocs diff --git a/adapter/template.go b/adapter/template.go new file mode 100644 index 00000000..67f5a33b --- /dev/null +++ b/adapter/template.go @@ -0,0 +1,108 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 adapter + +import ( + "html/template" + "io" + "net/http" + + "github.com/astaxie/beego/server/web" +) + +// ExecuteTemplate applies the template with name to the specified data object, +// writing the output to wr. +// A template will be executed safely in parallel. +func ExecuteTemplate(wr io.Writer, name string, data interface{}) error { + return web.ExecuteTemplate(wr, name, data) +} + +// ExecuteViewPathTemplate applies the template with name and from specific viewPath to the specified data object, +// writing the output to wr. +// A template will be executed safely in parallel. +func ExecuteViewPathTemplate(wr io.Writer, name string, viewPath string, data interface{}) error { + return web.ExecuteViewPathTemplate(wr, name, viewPath, data) +} + +// AddFuncMap let user to register a func in the template. +func AddFuncMap(key string, fn interface{}) error { + return web.AddFuncMap(key, fn) +} + +type templatePreProcessor func(root, path string, funcs template.FuncMap) (*template.Template, error) + +type templateFile struct { + root string + files map[string][]string +} + +// HasTemplateExt return this path contains supported template extension of beego or not. +func HasTemplateExt(paths string) bool { + return web.HasTemplateExt(paths) +} + +// AddTemplateExt add new extension for template. +func AddTemplateExt(ext string) { + web.AddTemplateExt(ext) +} + +// AddViewPath adds a new path to the supported view paths. +// Can later be used by setting a controller ViewPath to this folder +// will panic if called after beego.Run() +func AddViewPath(viewPath string) error { + return web.AddViewPath(viewPath) +} + +// BuildTemplate will build all template files in a directory. +// it makes beego can render any template file in view directory. +func BuildTemplate(dir string, files ...string) error { + return web.BuildTemplate(dir, files...) +} + +type templateFSFunc func() http.FileSystem + +func defaultFSFunc() http.FileSystem { + return FileSystem{} +} + +// SetTemplateFSFunc set default filesystem function +func SetTemplateFSFunc(fnt templateFSFunc) { + web.SetTemplateFSFunc(func() http.FileSystem { + return fnt() + }) +} + +// SetViewsPath sets view directory path in beego application. +func SetViewsPath(path string) *App { + return (*App)(web.SetViewsPath(path)) +} + +// SetStaticPath sets static directory path and proper url pattern in beego application. +// if beego.SetStaticPath("static","public"), visit /static/* to load static file in folder "public". +func SetStaticPath(url string, path string) *App { + return (*App)(web.SetStaticPath(url, path)) +} + +// DelStaticPath removes the static folder setting in this url pattern in beego application. +func DelStaticPath(url string) *App { + return (*App)(web.DelStaticPath(url)) +} + +// AddTemplateEngine add a new templatePreProcessor which support extension +func AddTemplateEngine(extension string, fn templatePreProcessor) *App { + return (*App)(web.AddTemplateEngine(extension, func(root, path string, funcs template.FuncMap) (*template.Template, error) { + return fn(root, path, funcs) + })) +} diff --git a/adapter/templatefunc.go b/adapter/templatefunc.go new file mode 100644 index 00000000..0c805393 --- /dev/null +++ b/adapter/templatefunc.go @@ -0,0 +1,151 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 adapter + +import ( + "html/template" + "net/url" + "time" + + "github.com/astaxie/beego/server/web" +) + +const ( + formatTime = "15:04:05" + formatDate = "2006-01-02" + formatDateTime = "2006-01-02 15:04:05" + formatDateTimeT = "2006-01-02T15:04:05" +) + +// Substr returns the substr from start to length. +func Substr(s string, start, length int) string { + return web.Substr(s, start, length) +} + +// HTML2str returns escaping text convert from html. +func HTML2str(html string) string { + return web.HTML2str(html) +} + +// DateFormat takes a time and a layout string and returns a string with the formatted date. Used by the template parser as "dateformat" +func DateFormat(t time.Time, layout string) (datestring string) { + return web.DateFormat(t, layout) +} + +// DateParse Parse Date use PHP time format. +func DateParse(dateString, format string) (time.Time, error) { + return web.DateParse(dateString, format) +} + +// Date takes a PHP like date func to Go's time format. +func Date(t time.Time, format string) string { + return web.Date(t, format) +} + +// Compare is a quick and dirty comparison function. It will convert whatever you give it to strings and see if the two values are equal. +// Whitespace is trimmed. Used by the template parser as "eq". +func Compare(a, b interface{}) (equal bool) { + return web.Compare(a, b) +} + +// CompareNot !Compare +func CompareNot(a, b interface{}) (equal bool) { + return web.CompareNot(a, b) +} + +// NotNil the same as CompareNot +func NotNil(a interface{}) (isNil bool) { + return web.NotNil(a) +} + +// GetConfig get the Appconfig +func GetConfig(returnType, key string, defaultVal interface{}) (interface{}, error) { + return web.GetConfig(returnType, key, defaultVal) +} + +// Str2html Convert string to template.HTML type. +func Str2html(raw string) template.HTML { + return web.Str2html(raw) +} + +// Htmlquote returns quoted html string. +func Htmlquote(text string) string { + return web.Htmlquote(text) +} + +// Htmlunquote returns unquoted html string. +func Htmlunquote(text string) string { + return web.Htmlunquote(text) +} + +// URLFor returns url string with another registered controller handler with params. +// usage: +// +// URLFor(".index") +// print URLFor("index") +// router /login +// print URLFor("login") +// print URLFor("login", "next","/"") +// router /profile/:username +// print UrlFor("profile", ":username","John Doe") +// result: +// / +// /login +// /login?next=/ +// /user/John%20Doe +// +// more detail http://beego.me/docs/mvc/controller/urlbuilding.md +func URLFor(endpoint string, values ...interface{}) string { + return web.URLFor(endpoint, values...) +} + +// AssetsJs returns script tag with src string. +func AssetsJs(text string) template.HTML { + return web.AssetsJs(text) +} + +// AssetsCSS returns stylesheet link tag with src string. +func AssetsCSS(text string) template.HTML { + + text = "" + + return template.HTML(text) +} + +// ParseForm will parse form values to struct via tag. +func ParseForm(form url.Values, obj interface{}) error { + return web.ParseForm(form, obj) +} + +// RenderForm will render object to form html. +// obj must be a struct pointer. +func RenderForm(obj interface{}) template.HTML { + return web.RenderForm(obj) +} + +// MapGet getting value from map by keys +// usage: +// Data["m"] = M{ +// "a": 1, +// "1": map[string]float64{ +// "c": 4, +// }, +// } +// +// {{ map_get m "a" }} // return 1 +// {{ map_get m 1 "c" }} // return 4 +func MapGet(arg1 interface{}, arg2 ...interface{}) (interface{}, error) { + return web.MapGet(arg1, arg2...) +} diff --git a/adapter/templatefunc_test.go b/adapter/templatefunc_test.go new file mode 100644 index 00000000..f5113606 --- /dev/null +++ b/adapter/templatefunc_test.go @@ -0,0 +1,304 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 adapter + +import ( + "html/template" + "net/url" + "testing" + "time" +) + +func TestSubstr(t *testing.T) { + s := `012345` + if Substr(s, 0, 2) != "01" { + t.Error("should be equal") + } + if Substr(s, 0, 100) != "012345" { + t.Error("should be equal") + } + if Substr(s, 12, 100) != "012345" { + t.Error("should be equal") + } +} + +func TestHtml2str(t *testing.T) { + h := `<123> 123\n + + + \n` + if HTML2str(h) != "123\\n\n\\n" { + t.Error("should be equal") + } +} + +func TestDateFormat(t *testing.T) { + ts := "Mon, 01 Jul 2013 13:27:42 CST" + tt, _ := time.Parse(time.RFC1123, ts) + + if ss := DateFormat(tt, "2006-01-02 15:04:05"); ss != "2013-07-01 13:27:42" { + t.Errorf("2013-07-01 13:27:42 does not equal %v", ss) + } +} + +func TestDate(t *testing.T) { + ts := "Mon, 01 Jul 2013 13:27:42 CST" + tt, _ := time.Parse(time.RFC1123, ts) + + if ss := Date(tt, "Y-m-d H:i:s"); ss != "2013-07-01 13:27:42" { + t.Errorf("2013-07-01 13:27:42 does not equal %v", ss) + } + if ss := Date(tt, "y-n-j h:i:s A"); ss != "13-7-1 01:27:42 PM" { + t.Errorf("13-7-1 01:27:42 PM does not equal %v", ss) + } + if ss := Date(tt, "D, d M Y g:i:s a"); ss != "Mon, 01 Jul 2013 1:27:42 pm" { + t.Errorf("Mon, 01 Jul 2013 1:27:42 pm does not equal %v", ss) + } + if ss := Date(tt, "l, d F Y G:i:s"); ss != "Monday, 01 July 2013 13:27:42" { + t.Errorf("Monday, 01 July 2013 13:27:42 does not equal %v", ss) + } +} + +func TestCompareRelated(t *testing.T) { + if !Compare("abc", "abc") { + t.Error("should be equal") + } + if Compare("abc", "aBc") { + t.Error("should be not equal") + } + if !Compare("1", 1) { + t.Error("should be equal") + } + if CompareNot("abc", "abc") { + t.Error("should be equal") + } + if !CompareNot("abc", "aBc") { + t.Error("should be not equal") + } + if !NotNil("a string") { + t.Error("should not be nil") + } +} + +func TestHtmlquote(t *testing.T) { + h := `<' ”“&">` + s := `<' ”“&">` + if Htmlquote(s) != h { + t.Error("should be equal") + } +} + +func TestHtmlunquote(t *testing.T) { + h := `<' ”“&">` + s := `<' ”“&">` + if Htmlunquote(h) != s { + t.Error("should be equal") + } +} + +func TestParseForm(t *testing.T) { + type ExtendInfo struct { + Hobby []string `form:"hobby"` + Memo string + } + + type OtherInfo struct { + Organization string `form:"organization"` + Title string `form:"title"` + ExtendInfo + } + + type user struct { + ID int `form:"-"` + tag string `form:"tag"` + Name interface{} `form:"username"` + Age int `form:"age,text"` + Email string + Intro string `form:",textarea"` + StrBool bool `form:"strbool"` + Date time.Time `form:"date,2006-01-02"` + OtherInfo + } + + u := user{} + form := url.Values{ + "ID": []string{"1"}, + "-": []string{"1"}, + "tag": []string{"no"}, + "username": []string{"test"}, + "age": []string{"40"}, + "Email": []string{"test@gmail.com"}, + "Intro": []string{"I am an engineer!"}, + "strbool": []string{"yes"}, + "date": []string{"2014-11-12"}, + "organization": []string{"beego"}, + "title": []string{"CXO"}, + "hobby": []string{"", "Basketball", "Football"}, + "memo": []string{"nothing"}, + } + if err := ParseForm(form, u); err == nil { + t.Fatal("nothing will be changed") + } + if err := ParseForm(form, &u); err != nil { + t.Fatal(err) + } + if u.ID != 0 { + t.Errorf("ID should equal 0 but got %v", u.ID) + } + if len(u.tag) != 0 { + t.Errorf("tag's length should equal 0 but got %v", len(u.tag)) + } + if u.Name.(string) != "test" { + t.Errorf("Name should equal `test` but got `%v`", u.Name.(string)) + } + if u.Age != 40 { + t.Errorf("Age should equal 40 but got %v", u.Age) + } + if u.Email != "test@gmail.com" { + t.Errorf("Email should equal `test@gmail.com` but got `%v`", u.Email) + } + if u.Intro != "I am an engineer!" { + t.Errorf("Intro should equal `I am an engineer!` but got `%v`", u.Intro) + } + if !u.StrBool { + t.Errorf("strboll should equal `true`, but got `%v`", u.StrBool) + } + y, m, d := u.Date.Date() + if y != 2014 || m.String() != "November" || d != 12 { + t.Errorf("Date should equal `2014-11-12`, but got `%v`", u.Date.String()) + } + if u.Organization != "beego" { + t.Errorf("Organization should equal `beego`, but got `%v`", u.Organization) + } + if u.Title != "CXO" { + t.Errorf("Title should equal `CXO`, but got `%v`", u.Title) + } + if u.Hobby[0] != "" { + t.Errorf("Hobby should equal ``, but got `%v`", u.Hobby[0]) + } + if u.Hobby[1] != "Basketball" { + t.Errorf("Hobby should equal `Basketball`, but got `%v`", u.Hobby[1]) + } + if u.Hobby[2] != "Football" { + t.Errorf("Hobby should equal `Football`, but got `%v`", u.Hobby[2]) + } + if len(u.Memo) != 0 { + t.Errorf("Memo's length should equal 0 but got %v", len(u.Memo)) + } +} + +func TestRenderForm(t *testing.T) { + type user struct { + ID int `form:"-"` + Name interface{} `form:"username"` + Age int `form:"age,text,年龄:"` + Sex string + Email []string + Intro string `form:",textarea"` + Ignored string `form:"-"` + } + + u := user{Name: "test", Intro: "Some Text"} + output := RenderForm(u) + if output != template.HTML("") { + t.Errorf("output should be empty but got %v", output) + } + output = RenderForm(&u) + result := template.HTML( + `Name:
` + + `年龄:
` + + `Sex:
` + + `Intro: `) + if output != result { + t.Errorf("output should equal `%v` but got `%v`", result, output) + } +} + +func TestMapGet(t *testing.T) { + // test one level map + m1 := map[string]int64{ + "a": 1, + "1": 2, + } + + if res, err := MapGet(m1, "a"); err == nil { + if res.(int64) != 1 { + t.Errorf("Should return 1, but return %v", res) + } + } else { + t.Errorf("Error happens %v", err) + } + + if res, err := MapGet(m1, "1"); err == nil { + if res.(int64) != 2 { + t.Errorf("Should return 2, but return %v", res) + } + } else { + t.Errorf("Error happens %v", err) + } + + if res, err := MapGet(m1, 1); err == nil { + if res.(int64) != 2 { + t.Errorf("Should return 2, but return %v", res) + } + } else { + t.Errorf("Error happens %v", err) + } + + // test 2 level map + m2 := M{ + "1": map[string]float64{ + "2": 3.5, + }, + } + + if res, err := MapGet(m2, 1, 2); err == nil { + if res.(float64) != 3.5 { + t.Errorf("Should return 3.5, but return %v", res) + } + } else { + t.Errorf("Error happens %v", err) + } + + // test 5 level map + m5 := M{ + "1": M{ + "2": M{ + "3": M{ + "4": M{ + "5": 1.2, + }, + }, + }, + }, + } + + if res, err := MapGet(m5, 1, 2, 3, 4, 5); err == nil { + if res.(float64) != 1.2 { + t.Errorf("Should return 1.2, but return %v", res) + } + } else { + t.Errorf("Error happens %v", err) + } + + // check whether element not exists in map + if res, err := MapGet(m5, 5, 4, 3, 2, 1); err == nil { + if res != nil { + t.Errorf("Should return nil, but return %v", res) + } + } else { + t.Errorf("Error happens %v", err) + } +} diff --git a/adapter/testing/client.go b/adapter/testing/client.go new file mode 100644 index 00000000..5c138167 --- /dev/null +++ b/adapter/testing/client.go @@ -0,0 +1,50 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 testing + +import ( + "github.com/astaxie/beego/client/httplib/testing" +) + +var port = "" +var baseURL = "http://localhost:" + +// TestHTTPRequest beego test request client +type TestHTTPRequest testing.TestHTTPRequest + +// Get returns test client in GET method +func Get(path string) *TestHTTPRequest { + return (*TestHTTPRequest)(testing.Get(path)) +} + +// Post returns test client in POST method +func Post(path string) *TestHTTPRequest { + return (*TestHTTPRequest)(testing.Post(path)) +} + +// Put returns test client in PUT method +func Put(path string) *TestHTTPRequest { + return (*TestHTTPRequest)(testing.Put(path)) +} + +// Delete returns test client in DELETE method +func Delete(path string) *TestHTTPRequest { + return (*TestHTTPRequest)(testing.Delete(path)) +} + +// Head returns test client in HEAD method +func Head(path string) *TestHTTPRequest { + return (*TestHTTPRequest)(testing.Head(path)) +} diff --git a/adapter/toolbox/healthcheck.go b/adapter/toolbox/healthcheck.go new file mode 100644 index 00000000..7d89c2fb --- /dev/null +++ b/adapter/toolbox/healthcheck.go @@ -0,0 +1,52 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 toolbox healthcheck +// +// type DatabaseCheck struct { +// } +// +// func (dc *DatabaseCheck) Check() error { +// if dc.isConnected() { +// return nil +// } else { +// return errors.New("can't connect database") +// } +// } +// +// AddHealthCheck("database",&DatabaseCheck{}) +// +// more docs: http://beego.me/docs/module/toolbox.md +package toolbox + +import ( + "github.com/astaxie/beego/core/governor" +) + +// AdminCheckList holds health checker map +// Deprecated using governor.AdminCheckList +var AdminCheckList map[string]HealthChecker + +// HealthChecker health checker interface +type HealthChecker governor.HealthChecker + +// AddHealthCheck add health checker with name string +func AddHealthCheck(name string, hc HealthChecker) { + governor.AddHealthCheck(name, hc) + AdminCheckList[name] = hc +} + +func init() { + AdminCheckList = make(map[string]HealthChecker) +} diff --git a/adapter/toolbox/profile.go b/adapter/toolbox/profile.go new file mode 100644 index 00000000..a5434360 --- /dev/null +++ b/adapter/toolbox/profile.go @@ -0,0 +1,50 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 toolbox + +import ( + "io" + "os" + "time" + + "github.com/astaxie/beego/core/governor" +) + +var startTime = time.Now() +var pid int + +func init() { + pid = os.Getpid() +} + +// ProcessInput parse input command string +func ProcessInput(input string, w io.Writer) { + governor.ProcessInput(input, w) +} + +// MemProf record memory profile in pprof +func MemProf(w io.Writer) { + governor.MemProf(w) +} + +// GetCPUProfile start cpu profile monitor +func GetCPUProfile(w io.Writer) { + governor.GetCPUProfile(w) +} + +// PrintGCSummary print gc information to io.Writer +func PrintGCSummary(w io.Writer) { + governor.PrintGCSummary(w) +} diff --git a/toolbox/profile_test.go b/adapter/toolbox/profile_test.go similarity index 100% rename from toolbox/profile_test.go rename to adapter/toolbox/profile_test.go diff --git a/adapter/toolbox/statistics.go b/adapter/toolbox/statistics.go new file mode 100644 index 00000000..7c8cd75e --- /dev/null +++ b/adapter/toolbox/statistics.go @@ -0,0 +1,50 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 toolbox + +import ( + "time" + + "github.com/astaxie/beego/server/web" +) + +// Statistics struct +type Statistics web.Statistics + +// URLMap contains several statistics struct to log different data +type URLMap web.URLMap + +// AddStatistics add statistics task. +// it needs request method, request url, request controller and statistics time duration +func (m *URLMap) AddStatistics(requestMethod, requestURL, requestController string, requesttime time.Duration) { + (*web.URLMap)(m).AddStatistics(requestMethod, requestURL, requestController, requesttime) +} + +// GetMap put url statistics result in io.Writer +func (m *URLMap) GetMap() map[string]interface{} { + return (*web.URLMap)(m).GetMap() +} + +// GetMapData return all mapdata +func (m *URLMap) GetMapData() []map[string]interface{} { + return (*web.URLMap)(m).GetMapData() +} + +// StatisticsMap hosld global statistics data map +var StatisticsMap *URLMap + +func init() { + StatisticsMap = (*URLMap)(web.StatisticsMap) +} diff --git a/toolbox/statistics_test.go b/adapter/toolbox/statistics_test.go similarity index 100% rename from toolbox/statistics_test.go rename to adapter/toolbox/statistics_test.go diff --git a/adapter/toolbox/task.go b/adapter/toolbox/task.go new file mode 100644 index 00000000..7f1bfc45 --- /dev/null +++ b/adapter/toolbox/task.go @@ -0,0 +1,291 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 toolbox + +import ( + "context" + "sort" + "time" + + "github.com/astaxie/beego/task" +) + +// The bounds for each field. +var ( + AdminTaskList map[string]Tasker +) + +const ( + // Set the top bit if a star was included in the expression. + starBit = 1 << 63 +) + +// Schedule time taks schedule +type Schedule task.Schedule + +// TaskFunc task func type +type TaskFunc func() error + +// Tasker task interface +type Tasker interface { + GetSpec() string + GetStatus() string + Run() error + SetNext(time.Time) + GetNext() time.Time + SetPrev(time.Time) + GetPrev() time.Time +} + +// task error +type taskerr struct { + t time.Time + errinfo string +} + +// Task task struct +// Deprecated +type Task struct { + // Deprecated + Taskname string + // Deprecated + Spec *Schedule + // Deprecated + SpecStr string + // Deprecated + DoFunc TaskFunc + // Deprecated + Prev time.Time + // Deprecated + Next time.Time + // Deprecated + Errlist []*taskerr // like errtime:errinfo + // Deprecated + ErrLimit int // max length for the errlist, 0 stand for no limit + + delegate *task.Task +} + +// NewTask add new task with name, time and func +func NewTask(tname string, spec string, f TaskFunc) *Task { + + task := task.NewTask(tname, spec, func(ctx context.Context) error { + return f() + }) + return &Task{ + delegate: task, + } +} + +// GetSpec get spec string +func (t *Task) GetSpec() string { + t.initDelegate() + + return t.delegate.GetSpec(context.Background()) +} + +// GetStatus get current task status +func (t *Task) GetStatus() string { + + t.initDelegate() + + return t.delegate.GetStatus(context.Background()) +} + +// Run run all tasks +func (t *Task) Run() error { + t.initDelegate() + return t.delegate.Run(context.Background()) +} + +// SetNext set next time for this task +func (t *Task) SetNext(now time.Time) { + t.initDelegate() + t.delegate.SetNext(context.Background(), now) +} + +// GetNext get the next call time of this task +func (t *Task) GetNext() time.Time { + t.initDelegate() + return t.delegate.GetNext(context.Background()) +} + +// SetPrev set prev time of this task +func (t *Task) SetPrev(now time.Time) { + t.initDelegate() + t.delegate.SetPrev(context.Background(), now) +} + +// GetPrev get prev time of this task +func (t *Task) GetPrev() time.Time { + t.initDelegate() + return t.delegate.GetPrev(context.Background()) +} + +// six columns mean: +// second:0-59 +// minute:0-59 +// hour:1-23 +// day:1-31 +// month:1-12 +// week:0-6(0 means Sunday) + +// SetCron some signals: +// *: any time +// ,:  separate signal +//    -:duration +// /n : do as n times of time duration +// /////////////////////////////////////////////////////// +// 0/30 * * * * * every 30s +// 0 43 21 * * * 21:43 +// 0 15 05 * * *    05:15 +// 0 0 17 * * * 17:00 +// 0 0 17 * * 1 17:00 in every Monday +// 0 0,10 17 * * 0,2,3 17:00 and 17:10 in every Sunday, Tuesday and Wednesday +// 0 0-10 17 1 * * 17:00 to 17:10 in 1 min duration each time on the first day of month +// 0 0 0 1,15 * 1 0:00 on the 1st day and 15th day of month +// 0 42 4 1 * *     4:42 on the 1st day of month +// 0 0 21 * * 1-6   21:00 from Monday to Saturday +// 0 0,10,20,30,40,50 * * * *  every 10 min duration +// 0 */10 * * * *        every 10 min duration +// 0 * 1 * * *         1:00 to 1:59 in 1 min duration each time +// 0 0 1 * * *         1:00 +// 0 0 */1 * * *        0 min of hour in 1 hour duration +// 0 0 * * * *         0 min of hour in 1 hour duration +// 0 2 8-20/3 * * *       8:02, 11:02, 14:02, 17:02, 20:02 +// 0 30 5 1,15 * *       5:30 on the 1st day and 15th day of month +func (t *Task) SetCron(spec string) { + t.initDelegate() + t.delegate.SetCron(spec) +} + +func (t *Task) initDelegate() { + if t.delegate == nil { + t.delegate = &task.Task{ + Taskname: t.Taskname, + Spec: (*task.Schedule)(t.Spec), + SpecStr: t.SpecStr, + DoFunc: func(ctx context.Context) error { + return t.DoFunc() + }, + Prev: t.Prev, + Next: t.Next, + ErrLimit: t.ErrLimit, + } + } +} + +// Next set schedule to next time +func (s *Schedule) Next(t time.Time) time.Time { + return (*task.Schedule)(s).Next(t) +} + +// StartTask start all tasks +func StartTask() { + task.StartTask() +} + +// StopTask stop all tasks +func StopTask() { + task.StopTask() +} + +// AddTask add task with name +func AddTask(taskname string, t Tasker) { + task.AddTask(taskname, &oldToNewAdapter{delegate: t}) +} + +// DeleteTask delete task with name +func DeleteTask(taskname string) { + task.DeleteTask(taskname) +} + +// ClearTask clear all tasks +func ClearTask() { + task.ClearTask() +} + +// MapSorter sort map for tasker +type MapSorter task.MapSorter + +// NewMapSorter create new tasker map +func NewMapSorter(m map[string]Tasker) *MapSorter { + + newTaskerMap := make(map[string]task.Tasker, len(m)) + + for key, value := range m { + newTaskerMap[key] = &oldToNewAdapter{ + delegate: value, + } + } + + return (*MapSorter)(task.NewMapSorter(newTaskerMap)) +} + +// Sort sort tasker map +func (ms *MapSorter) Sort() { + sort.Sort(ms) +} + +func (ms *MapSorter) Len() int { return len(ms.Keys) } +func (ms *MapSorter) Less(i, j int) bool { + if ms.Vals[i].GetNext(context.Background()).IsZero() { + return false + } + if ms.Vals[j].GetNext(context.Background()).IsZero() { + return true + } + return ms.Vals[i].GetNext(context.Background()).Before(ms.Vals[j].GetNext(context.Background())) +} +func (ms *MapSorter) Swap(i, j int) { + ms.Vals[i], ms.Vals[j] = ms.Vals[j], ms.Vals[i] + ms.Keys[i], ms.Keys[j] = ms.Keys[j], ms.Keys[i] +} + +func init() { + AdminTaskList = make(map[string]Tasker) +} + +type oldToNewAdapter struct { + delegate Tasker +} + +func (o *oldToNewAdapter) GetSpec(ctx context.Context) string { + return o.delegate.GetSpec() +} + +func (o *oldToNewAdapter) GetStatus(ctx context.Context) string { + return o.delegate.GetStatus() +} + +func (o *oldToNewAdapter) Run(ctx context.Context) error { + return o.delegate.Run() +} + +func (o *oldToNewAdapter) SetNext(ctx context.Context, t time.Time) { + o.delegate.SetNext(t) +} + +func (o *oldToNewAdapter) GetNext(ctx context.Context) time.Time { + return o.delegate.GetNext() +} + +func (o *oldToNewAdapter) SetPrev(ctx context.Context, t time.Time) { + o.delegate.SetPrev(t) +} + +func (o *oldToNewAdapter) GetPrev(ctx context.Context) time.Time { + return o.delegate.GetPrev() +} diff --git a/toolbox/task_test.go b/adapter/toolbox/task_test.go similarity index 97% rename from toolbox/task_test.go rename to adapter/toolbox/task_test.go index 596bc9c5..994c4976 100644 --- a/toolbox/task_test.go +++ b/adapter/toolbox/task_test.go @@ -22,6 +22,8 @@ import ( ) func TestParse(t *testing.T) { + defer ClearTask() + tk := NewTask("taska", "0/30 * * * * *", func() error { fmt.Println("hello world"); return nil }) err := tk.Run() if err != nil { @@ -34,6 +36,8 @@ func TestParse(t *testing.T) { } func TestSpec(t *testing.T) { + defer ClearTask() + wg := &sync.WaitGroup{} wg.Add(2) tk1 := NewTask("tk1", "0 12 * * * *", func() error { fmt.Println("tk1"); return nil }) diff --git a/adapter/tree.go b/adapter/tree.go new file mode 100644 index 00000000..36f763ea --- /dev/null +++ b/adapter/tree.go @@ -0,0 +1,49 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 adapter + +import ( + "github.com/astaxie/beego/adapter/context" + beecontext "github.com/astaxie/beego/server/web/context" + + "github.com/astaxie/beego/server/web" +) + +// Tree has three elements: FixRouter/wildcard/leaves +// fixRouter stores Fixed Router +// wildcard stores params +// leaves store the endpoint information +type Tree web.Tree + +// NewTree return a new Tree +func NewTree() *Tree { + return (*Tree)(web.NewTree()) +} + +// AddTree will add tree to the exist Tree +// prefix should has no params +func (t *Tree) AddTree(prefix string, tree *Tree) { + (*web.Tree)(t).AddTree(prefix, (*web.Tree)(tree)) +} + +// AddRouter call addseg function +func (t *Tree) AddRouter(pattern string, runObject interface{}) { + (*web.Tree)(t).AddRouter(pattern, runObject) +} + +// Match router to runObject & params +func (t *Tree) Match(pattern string, ctx *context.Context) (runObject interface{}) { + return (*web.Tree)(t).Match(pattern, (*beecontext.Context)(ctx)) +} diff --git a/adapter/tree_test.go b/adapter/tree_test.go new file mode 100644 index 00000000..2315d829 --- /dev/null +++ b/adapter/tree_test.go @@ -0,0 +1,249 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 adapter + +import ( + "testing" + + "github.com/astaxie/beego/adapter/context" + beecontext "github.com/astaxie/beego/server/web/context" +) + +type testinfo struct { + url string + requesturl string + params map[string]string +} + +var routers []testinfo + +func init() { + routers = make([]testinfo, 0) + routers = append(routers, testinfo{"/topic/?:auth:int", "/topic", nil}) + routers = append(routers, testinfo{"/topic/?:auth:int", "/topic/123", map[string]string{":auth": "123"}}) + routers = append(routers, testinfo{"/topic/:id/?:auth", "/topic/1", map[string]string{":id": "1"}}) + routers = append(routers, testinfo{"/topic/:id/?:auth", "/topic/1/2", map[string]string{":id": "1", ":auth": "2"}}) + routers = append(routers, testinfo{"/topic/:id/?:auth:int", "/topic/1", map[string]string{":id": "1"}}) + routers = append(routers, testinfo{"/topic/:id/?:auth:int", "/topic/1/123", map[string]string{":id": "1", ":auth": "123"}}) + routers = append(routers, testinfo{"/:id", "/123", map[string]string{":id": "123"}}) + routers = append(routers, testinfo{"/hello/?:id", "/hello", map[string]string{":id": ""}}) + routers = append(routers, testinfo{"/", "/", nil}) + routers = append(routers, testinfo{"/customer/login", "/customer/login", nil}) + routers = append(routers, testinfo{"/customer/login", "/customer/login.json", map[string]string{":ext": "json"}}) + routers = append(routers, testinfo{"/*", "/http://customer/123/", map[string]string{":splat": "http://customer/123/"}}) + routers = append(routers, testinfo{"/*", "/customer/2009/12/11", map[string]string{":splat": "customer/2009/12/11"}}) + routers = append(routers, testinfo{"/aa/*/bb", "/aa/2009/bb", map[string]string{":splat": "2009"}}) + routers = append(routers, testinfo{"/cc/*/dd", "/cc/2009/11/dd", map[string]string{":splat": "2009/11"}}) + routers = append(routers, testinfo{"/cc/:id/*", "/cc/2009/11/dd", map[string]string{":id": "2009", ":splat": "11/dd"}}) + routers = append(routers, testinfo{"/ee/:year/*/ff", "/ee/2009/11/ff", map[string]string{":year": "2009", ":splat": "11"}}) + routers = append(routers, testinfo{"/thumbnail/:size/uploads/*", + "/thumbnail/100x100/uploads/items/2014/04/20/dPRCdChkUd651t1Hvs18.jpg", + map[string]string{":size": "100x100", ":splat": "items/2014/04/20/dPRCdChkUd651t1Hvs18.jpg"}}) + routers = append(routers, testinfo{"/*.*", "/nice/api.json", map[string]string{":path": "nice/api", ":ext": "json"}}) + routers = append(routers, testinfo{"/:name/*.*", "/nice/api.json", map[string]string{":name": "nice", ":path": "api", ":ext": "json"}}) + routers = append(routers, testinfo{"/:name/test/*.*", "/nice/test/api.json", map[string]string{":name": "nice", ":path": "api", ":ext": "json"}}) + routers = append(routers, testinfo{"/dl/:width:int/:height:int/*.*", + "/dl/48/48/05ac66d9bda00a3acf948c43e306fc9a.jpg", + map[string]string{":width": "48", ":height": "48", ":ext": "jpg", ":path": "05ac66d9bda00a3acf948c43e306fc9a"}}) + routers = append(routers, testinfo{"/v1/shop/:id:int", "/v1/shop/123", map[string]string{":id": "123"}}) + routers = append(routers, testinfo{"/v1/shop/:id\\((a|b|c)\\)", "/v1/shop/123(a)", map[string]string{":id": "123"}}) + routers = append(routers, testinfo{"/v1/shop/:id\\((a|b|c)\\)", "/v1/shop/123(b)", map[string]string{":id": "123"}}) + routers = append(routers, testinfo{"/v1/shop/:id\\((a|b|c)\\)", "/v1/shop/123(c)", map[string]string{":id": "123"}}) + routers = append(routers, testinfo{"/:year:int/:month:int/:id/:endid", "/1111/111/aaa/aaa", map[string]string{":year": "1111", ":month": "111", ":id": "aaa", ":endid": "aaa"}}) + routers = append(routers, testinfo{"/v1/shop/:id/:name", "/v1/shop/123/nike", map[string]string{":id": "123", ":name": "nike"}}) + routers = append(routers, testinfo{"/v1/shop/:id/account", "/v1/shop/123/account", map[string]string{":id": "123"}}) + routers = append(routers, testinfo{"/v1/shop/:name:string", "/v1/shop/nike", map[string]string{":name": "nike"}}) + routers = append(routers, testinfo{"/v1/shop/:id([0-9]+)", "/v1/shop//123", map[string]string{":id": "123"}}) + routers = append(routers, testinfo{"/v1/shop/:id([0-9]+)_:name", "/v1/shop/123_nike", map[string]string{":id": "123", ":name": "nike"}}) + routers = append(routers, testinfo{"/v1/shop/:id(.+)_cms.html", "/v1/shop/123_cms.html", map[string]string{":id": "123"}}) + routers = append(routers, testinfo{"/v1/shop/cms_:id(.+)_:page(.+).html", "/v1/shop/cms_123_1.html", map[string]string{":id": "123", ":page": "1"}}) + routers = append(routers, testinfo{"/v1/:v/cms/aaa_:id(.+)_:page(.+).html", "/v1/2/cms/aaa_123_1.html", map[string]string{":v": "2", ":id": "123", ":page": "1"}}) + routers = append(routers, testinfo{"/v1/:v/cms_:id(.+)_:page(.+).html", "/v1/2/cms_123_1.html", map[string]string{":v": "2", ":id": "123", ":page": "1"}}) + routers = append(routers, testinfo{"/v1/:v(.+)_cms/ttt_:id(.+)_:page(.+).html", "/v1/2_cms/ttt_123_1.html", map[string]string{":v": "2", ":id": "123", ":page": "1"}}) + routers = append(routers, testinfo{"/api/projects/:pid/members/?:mid", "/api/projects/1/members", map[string]string{":pid": "1"}}) + routers = append(routers, testinfo{"/api/projects/:pid/members/?:mid", "/api/projects/1/members/2", map[string]string{":pid": "1", ":mid": "2"}}) +} + +func TestTreeRouters(t *testing.T) { + for _, r := range routers { + tr := NewTree() + tr.AddRouter(r.url, "astaxie") + ctx := context.NewContext() + obj := tr.Match(r.requesturl, ctx) + if obj == nil || obj.(string) != "astaxie" { + t.Fatal(r.url+" can't get obj, Expect ", r.requesturl) + } + if r.params != nil { + for k, v := range r.params { + if vv := ctx.Input.Param(k); vv != v { + t.Fatal("The Rule: " + r.url + "\nThe RequestURL:" + r.requesturl + "\nThe Key is " + k + ", The Value should be: " + v + ", but get: " + vv) + } else if vv == "" && v != "" { + t.Fatal(r.url + " " + r.requesturl + " get param empty:" + k) + } + } + } + } +} + +func TestStaticPath(t *testing.T) { + tr := NewTree() + tr.AddRouter("/topic/:id", "wildcard") + tr.AddRouter("/topic", "static") + ctx := context.NewContext() + obj := tr.Match("/topic", ctx) + if obj == nil || obj.(string) != "static" { + t.Fatal("/topic is a static route") + } + obj = tr.Match("/topic/1", ctx) + if obj == nil || obj.(string) != "wildcard" { + t.Fatal("/topic/1 is a wildcard route") + } +} + +func TestAddTree(t *testing.T) { + tr := NewTree() + tr.AddRouter("/shop/:id/account", "astaxie") + tr.AddRouter("/shop/:sd/ttt_:id(.+)_:page(.+).html", "astaxie") + t1 := NewTree() + t1.AddTree("/v1/zl", tr) + ctx := context.NewContext() + obj := t1.Match("/v1/zl/shop/123/account", ctx) + if obj == nil || obj.(string) != "astaxie" { + t.Fatal("/v1/zl/shop/:id/account can't get obj ") + } + if ctx.Input.ParamsLen() == 0 { + t.Fatal("get param error") + } + if ctx.Input.Param(":id") != "123" { + t.Fatal("get :id param error") + } + ctx.Input.Reset((*beecontext.Context)(ctx)) + obj = t1.Match("/v1/zl/shop/123/ttt_1_12.html", ctx) + if obj == nil || obj.(string) != "astaxie" { + t.Fatal("/v1/zl//shop/:sd/ttt_:id(.+)_:page(.+).html can't get obj ") + } + if ctx.Input.ParamsLen() == 0 { + t.Fatal("get param error") + } + if ctx.Input.Param(":sd") != "123" || ctx.Input.Param(":id") != "1" || ctx.Input.Param(":page") != "12" { + t.Fatal("get :sd :id :page param error") + } + + t2 := NewTree() + t2.AddTree("/v1/:shopid", tr) + ctx.Input.Reset((*beecontext.Context)(ctx)) + obj = t2.Match("/v1/zl/shop/123/account", ctx) + if obj == nil || obj.(string) != "astaxie" { + t.Fatal("/v1/:shopid/shop/:id/account can't get obj ") + } + if ctx.Input.ParamsLen() == 0 { + t.Fatal("get param error") + } + if ctx.Input.Param(":id") != "123" || ctx.Input.Param(":shopid") != "zl" { + t.Fatal("get :id :shopid param error") + } + ctx.Input.Reset((*beecontext.Context)(ctx)) + obj = t2.Match("/v1/zl/shop/123/ttt_1_12.html", ctx) + if obj == nil || obj.(string) != "astaxie" { + t.Fatal("/v1/:shopid/shop/:sd/ttt_:id(.+)_:page(.+).html can't get obj ") + } + if ctx.Input.ParamsLen() == 0 { + t.Fatal("get :shopid param error") + } + if ctx.Input.Param(":sd") != "123" || ctx.Input.Param(":id") != "1" || ctx.Input.Param(":page") != "12" || ctx.Input.Param(":shopid") != "zl" { + t.Fatal("get :sd :id :page :shopid param error") + } +} + +func TestAddTree2(t *testing.T) { + tr := NewTree() + tr.AddRouter("/shop/:id/account", "astaxie") + tr.AddRouter("/shop/:sd/ttt_:id(.+)_:page(.+).html", "astaxie") + t3 := NewTree() + t3.AddTree("/:version(v1|v2)/:prefix", tr) + ctx := context.NewContext() + obj := t3.Match("/v1/zl/shop/123/account", ctx) + if obj == nil || obj.(string) != "astaxie" { + t.Fatal("/:version(v1|v2)/:prefix/shop/:id/account can't get obj ") + } + if ctx.Input.ParamsLen() == 0 { + t.Fatal("get param error") + } + if ctx.Input.Param(":id") != "123" || ctx.Input.Param(":prefix") != "zl" || ctx.Input.Param(":version") != "v1" { + t.Fatal("get :id :prefix :version param error") + } +} + +func TestAddTree3(t *testing.T) { + tr := NewTree() + tr.AddRouter("/create", "astaxie") + tr.AddRouter("/shop/:sd/account", "astaxie") + t3 := NewTree() + t3.AddTree("/table/:num", tr) + ctx := context.NewContext() + obj := t3.Match("/table/123/shop/123/account", ctx) + if obj == nil || obj.(string) != "astaxie" { + t.Fatal("/table/:num/shop/:sd/account can't get obj ") + } + if ctx.Input.ParamsLen() == 0 { + t.Fatal("get param error") + } + if ctx.Input.Param(":num") != "123" || ctx.Input.Param(":sd") != "123" { + t.Fatal("get :num :sd param error") + } + ctx.Input.Reset((*beecontext.Context)(ctx)) + obj = t3.Match("/table/123/create", ctx) + if obj == nil || obj.(string) != "astaxie" { + t.Fatal("/table/:num/create can't get obj ") + } +} + +func TestAddTree4(t *testing.T) { + tr := NewTree() + tr.AddRouter("/create", "astaxie") + tr.AddRouter("/shop/:sd/:account", "astaxie") + t4 := NewTree() + t4.AddTree("/:info:int/:num/:id", tr) + ctx := context.NewContext() + obj := t4.Match("/12/123/456/shop/123/account", ctx) + if obj == nil || obj.(string) != "astaxie" { + t.Fatal("/:info:int/:num/:id/shop/:sd/:account can't get obj ") + } + if ctx.Input.ParamsLen() == 0 { + t.Fatal("get param error") + } + if ctx.Input.Param(":info") != "12" || ctx.Input.Param(":num") != "123" || + ctx.Input.Param(":id") != "456" || ctx.Input.Param(":sd") != "123" || + ctx.Input.Param(":account") != "account" { + t.Fatal("get :info :num :id :sd :account param error") + } + ctx.Input.Reset((*beecontext.Context)(ctx)) + obj = t4.Match("/12/123/456/create", ctx) + if obj == nil || obj.(string) != "astaxie" { + t.Fatal("/:info:int/:num/:id/create can't get obj ") + } +} + +// Test for issue #1595 +func TestAddTree5(t *testing.T) { + tr := NewTree() + tr.AddRouter("/v1/shop/:id", "shopdetail") + tr.AddRouter("/v1/shop/", "shophome") + ctx := context.NewContext() + obj := tr.Match("/v1/shop/", ctx) + if obj == nil || obj.(string) != "shophome" { + t.Fatal("url /v1/shop/ need match router /v1/shop/ ") + } +} diff --git a/logs/conn_test.go b/adapter/utils/caller.go similarity index 78% rename from logs/conn_test.go rename to adapter/utils/caller.go index 747fb890..419f11d6 100644 --- a/logs/conn_test.go +++ b/adapter/utils/caller.go @@ -12,14 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package logs +package utils import ( - "testing" + "github.com/astaxie/beego/core/utils" ) -func TestConn(t *testing.T) { - log := NewLogger(1000) - log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`) - log.Informational("informational") +// GetFuncName get function name +func GetFuncName(i interface{}) string { + return utils.GetFuncName(i) } diff --git a/utils/caller_test.go b/adapter/utils/caller_test.go similarity index 100% rename from utils/caller_test.go rename to adapter/utils/caller_test.go diff --git a/utils/captcha/LICENSE b/adapter/utils/captcha/LICENSE similarity index 100% rename from utils/captcha/LICENSE rename to adapter/utils/captcha/LICENSE diff --git a/utils/captcha/README.md b/adapter/utils/captcha/README.md similarity index 100% rename from utils/captcha/README.md rename to adapter/utils/captcha/README.md diff --git a/adapter/utils/captcha/captcha.go b/adapter/utils/captcha/captcha.go new file mode 100644 index 00000000..71aad0f2 --- /dev/null +++ b/adapter/utils/captcha/captcha.go @@ -0,0 +1,124 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 captcha implements generation and verification of image CAPTCHAs. +// an example for use captcha +// +// ``` +// package controllers +// +// import ( +// "github.com/astaxie/beego" +// "github.com/astaxie/beego/cache" +// "github.com/astaxie/beego/utils/captcha" +// ) +// +// var cpt *captcha.Captcha +// +// func init() { +// // use beego cache system store the captcha data +// store := cache.NewMemoryCache() +// cpt = captcha.NewWithFilter("/captcha/", store) +// } +// +// type MainController struct { +// beego.Controller +// } +// +// func (this *MainController) Get() { +// this.TplName = "index.tpl" +// } +// +// func (this *MainController) Post() { +// this.TplName = "index.tpl" +// +// this.Data["Success"] = cpt.VerifyReq(this.Ctx.Request) +// } +// ``` +// +// template usage +// +// ``` +// {{.Success}} +//
+// {{create_captcha}} +// +//
+// ``` +package captcha + +import ( + "html/template" + "net/http" + "time" + + "github.com/astaxie/beego/server/web/captcha" + beecontext "github.com/astaxie/beego/server/web/context" + + "github.com/astaxie/beego/adapter/cache" + "github.com/astaxie/beego/adapter/context" +) + +var ( + defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} +) + +const ( + // default captcha attributes + challengeNums = 6 + expiration = 600 * time.Second + fieldIDName = "captcha_id" + fieldCaptchaName = "captcha" + cachePrefix = "captcha_" + defaultURLPrefix = "/captcha/" +) + +// Captcha struct +type Captcha captcha.Captcha + +// Handler beego filter handler for serve captcha image +func (c *Captcha) Handler(ctx *context.Context) { + (*captcha.Captcha)(c).Handler((*beecontext.Context)(ctx)) +} + +// CreateCaptchaHTML template func for output html +func (c *Captcha) CreateCaptchaHTML() template.HTML { + return (*captcha.Captcha)(c).CreateCaptchaHTML() +} + +// CreateCaptcha create a new captcha id +func (c *Captcha) CreateCaptcha() (string, error) { + return (*captcha.Captcha)(c).CreateCaptcha() +} + +// VerifyReq verify from a request +func (c *Captcha) VerifyReq(req *http.Request) bool { + return (*captcha.Captcha)(c).VerifyReq(req) +} + +// Verify direct verify id and challenge string +func (c *Captcha) Verify(id string, challenge string) (success bool) { + return (*captcha.Captcha)(c).Verify(id, challenge) +} + +// NewCaptcha create a new captcha.Captcha +func NewCaptcha(urlPrefix string, store cache.Cache) *Captcha { + return (*Captcha)(captcha.NewCaptcha(urlPrefix, cache.CreateOldToNewAdapter(store))) +} + +// NewWithFilter create a new captcha.Captcha and auto AddFilter for serve captacha image +// and add a template func for output html +func NewWithFilter(urlPrefix string, store cache.Cache) *Captcha { + return (*Captcha)(captcha.NewWithFilter(urlPrefix, cache.CreateOldToNewAdapter(store))) +} diff --git a/adapter/utils/captcha/image.go b/adapter/utils/captcha/image.go new file mode 100644 index 00000000..6a1b696b --- /dev/null +++ b/adapter/utils/captcha/image.go @@ -0,0 +1,35 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 captcha + +import ( + "io" + + "github.com/astaxie/beego/server/web/captcha" +) + +// Image struct +type Image captcha.Image + +// NewImage returns a new captcha image of the given width and height with the +// given digits, where each digit must be in range 0-9. +func NewImage(digits []byte, width, height int) *Image { + return (*Image)(captcha.NewImage(digits, width, height)) +} + +// WriteTo writes captcha image in PNG format into the given writer. +func (m *Image) WriteTo(w io.Writer) (int64, error) { + return (*captcha.Image)(m).WriteTo(w) +} diff --git a/adapter/utils/captcha/image_test.go b/adapter/utils/captcha/image_test.go new file mode 100644 index 00000000..5d298573 --- /dev/null +++ b/adapter/utils/captcha/image_test.go @@ -0,0 +1,58 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 captcha + +import ( + "testing" + + "github.com/astaxie/beego/adapter/utils" +) + +const ( + // Standard width and height of a captcha image. + stdWidth = 240 + stdHeight = 80 +) + +type byteCounter struct { + n int64 +} + +func (bc *byteCounter) Write(b []byte) (int, error) { + bc.n += int64(len(b)) + return len(b), nil +} + +func BenchmarkNewImage(b *testing.B) { + b.StopTimer() + d := utils.RandomCreateBytes(challengeNums, defaultChars...) + b.StartTimer() + for i := 0; i < b.N; i++ { + NewImage(d, stdWidth, stdHeight) + } +} + +func BenchmarkImageWriteTo(b *testing.B) { + b.StopTimer() + d := utils.RandomCreateBytes(challengeNums, defaultChars...) + b.StartTimer() + counter := &byteCounter{} + for i := 0; i < b.N; i++ { + img := NewImage(d, stdWidth, stdHeight) + img.WriteTo(counter) + b.SetBytes(counter.n) + counter.n = 0 + } +} diff --git a/adapter/utils/debug.go b/adapter/utils/debug.go new file mode 100644 index 00000000..3f4d2759 --- /dev/null +++ b/adapter/utils/debug.go @@ -0,0 +1,34 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 utils + +import ( + "github.com/astaxie/beego/core/utils" +) + +// Display print the data in console +func Display(data ...interface{}) { + utils.Display(data...) +} + +// GetDisplayString return data print string +func GetDisplayString(data ...interface{}) string { + return utils.GetDisplayString(data...) +} + +// Stack get stack bytes +func Stack(skip int, indent string) []byte { + return utils.Stack(skip, indent) +} diff --git a/utils/debug_test.go b/adapter/utils/debug_test.go similarity index 100% rename from utils/debug_test.go rename to adapter/utils/debug_test.go diff --git a/adapter/utils/file.go b/adapter/utils/file.go new file mode 100644 index 00000000..aa9ac316 --- /dev/null +++ b/adapter/utils/file.go @@ -0,0 +1,47 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 utils + +import ( + "github.com/astaxie/beego/core/utils" +) + +// SelfPath gets compiled executable file absolute path +func SelfPath() string { + return utils.SelfPath() +} + +// SelfDir gets compiled executable file directory +func SelfDir() string { + return utils.SelfDir() +} + +// FileExists reports whether the named file or directory exists. +func FileExists(name string) bool { + return utils.FileExists(name) +} + +// SearchFile Search a file in paths. +// this is often used in search config file in /etc ~/ +func SearchFile(filename string, paths ...string) (fullpath string, err error) { + return utils.SearchFile(filename, paths...) +} + +// GrepFile like command grep -E +// for example: GrepFile(`^hello`, "hello.txt") +// \n is striped while read +func GrepFile(patten string, filename string) (lines []string, err error) { + return utils.GrepFile(patten, filename) +} diff --git a/adapter/utils/mail.go b/adapter/utils/mail.go new file mode 100644 index 00000000..74a8f403 --- /dev/null +++ b/adapter/utils/mail.go @@ -0,0 +1,63 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 utils + +import ( + "io" + + "github.com/astaxie/beego/core/utils" +) + +// Email is the type used for email messages +type Email utils.Email + +// Attachment is a struct representing an email attachment. +// Based on the mime/multipart.FileHeader struct, Attachment contains the name, MIMEHeader, and content of the attachment in question +type Attachment utils.Attachment + +// NewEMail create new Email struct with config json. +// config json is followed from Email struct fields. +func NewEMail(config string) *Email { + return (*Email)(utils.NewEMail(config)) +} + +// Bytes Make all send information to byte +func (e *Email) Bytes() ([]byte, error) { + return (*utils.Email)(e).Bytes() +} + +// AttachFile Add attach file to the send mail +func (e *Email) AttachFile(args ...string) (*Attachment, error) { + a, err := (*utils.Email)(e).AttachFile(args...) + if err != nil { + return nil, err + } + return (*Attachment)(a), err +} + +// Attach is used to attach content from an io.Reader to the email. +// Parameters include an io.Reader, the desired filename for the attachment, and the Content-Type. +func (e *Email) Attach(r io.Reader, filename string, args ...string) (*Attachment, error) { + a, err := (*utils.Email)(e).Attach(r, filename, args...) + if err != nil { + return nil, err + } + return (*Attachment)(a), err +} + +// Send will send out the mail +func (e *Email) Send() error { + return (*utils.Email)(e).Send() +} diff --git a/utils/mail_test.go b/adapter/utils/mail_test.go similarity index 100% rename from utils/mail_test.go rename to adapter/utils/mail_test.go diff --git a/adapter/utils/pagination/controller.go b/adapter/utils/pagination/controller.go new file mode 100644 index 00000000..c82c54f9 --- /dev/null +++ b/adapter/utils/pagination/controller.go @@ -0,0 +1,26 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 pagination + +import ( + "github.com/astaxie/beego/adapter/context" + beecontext "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/server/web/pagination" +) + +// SetPaginator Instantiates a Paginator and assigns it to context.Input.Data("paginator"). +func SetPaginator(ctx *context.Context, per int, nums int64) (paginator *Paginator) { + return (*Paginator)(pagination.SetPaginator((*beecontext.Context)(ctx), per, nums)) +} diff --git a/utils/pagination/doc.go b/adapter/utils/pagination/doc.go similarity index 100% rename from utils/pagination/doc.go rename to adapter/utils/pagination/doc.go diff --git a/adapter/utils/pagination/paginator.go b/adapter/utils/pagination/paginator.go new file mode 100644 index 00000000..73d9157f --- /dev/null +++ b/adapter/utils/pagination/paginator.go @@ -0,0 +1,112 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 pagination + +import ( + "net/http" + + "github.com/astaxie/beego/core/utils/pagination" +) + +// Paginator within the state of a http request. +type Paginator pagination.Paginator + +// PageNums Returns the total number of pages. +func (p *Paginator) PageNums() int { + return (*pagination.Paginator)(p).PageNums() +} + +// Nums Returns the total number of items (e.g. from doing SQL count). +func (p *Paginator) Nums() int64 { + return (*pagination.Paginator)(p).Nums() +} + +// SetNums Sets the total number of items. +func (p *Paginator) SetNums(nums interface{}) { + (*pagination.Paginator)(p).SetNums(nums) +} + +// Page Returns the current page. +func (p *Paginator) Page() int { + return (*pagination.Paginator)(p).Page() +} + +// Pages Returns a list of all pages. +// +// Usage (in a view template): +// +// {{range $index, $page := .paginator.Pages}} +// +// {{$page}} +// +// {{end}} +func (p *Paginator) Pages() []int { + return (*pagination.Paginator)(p).Pages() +} + +// PageLink Returns URL for a given page index. +func (p *Paginator) PageLink(page int) string { + return (*pagination.Paginator)(p).PageLink(page) +} + +// PageLinkPrev Returns URL to the previous page. +func (p *Paginator) PageLinkPrev() (link string) { + return (*pagination.Paginator)(p).PageLinkPrev() +} + +// PageLinkNext Returns URL to the next page. +func (p *Paginator) PageLinkNext() (link string) { + return (*pagination.Paginator)(p).PageLinkNext() +} + +// PageLinkFirst Returns URL to the first page. +func (p *Paginator) PageLinkFirst() (link string) { + return (*pagination.Paginator)(p).PageLinkFirst() +} + +// PageLinkLast Returns URL to the last page. +func (p *Paginator) PageLinkLast() (link string) { + return (*pagination.Paginator)(p).PageLinkLast() +} + +// HasPrev Returns true if the current page has a predecessor. +func (p *Paginator) HasPrev() bool { + return (*pagination.Paginator)(p).HasPrev() +} + +// HasNext Returns true if the current page has a successor. +func (p *Paginator) HasNext() bool { + return (*pagination.Paginator)(p).HasNext() +} + +// IsActive Returns true if the given page index points to the current page. +func (p *Paginator) IsActive(page int) bool { + return (*pagination.Paginator)(p).IsActive(page) +} + +// Offset Returns the current offset. +func (p *Paginator) Offset() int { + return (*pagination.Paginator)(p).Offset() +} + +// HasPages Returns true if there is more than one page. +func (p *Paginator) HasPages() bool { + return (*pagination.Paginator)(p).HasPages() +} + +// NewPaginator Instantiates a paginator struct for the current http request. +func NewPaginator(req *http.Request, per int, nums interface{}) *Paginator { + return (*Paginator)(pagination.NewPaginator(req, per, nums)) +} diff --git a/adapter/utils/rand.go b/adapter/utils/rand.go new file mode 100644 index 00000000..0fcca580 --- /dev/null +++ b/adapter/utils/rand.go @@ -0,0 +1,24 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 utils + +import ( + "github.com/astaxie/beego/core/utils" +) + +// RandomCreateBytes generate random []byte by specify chars. +func RandomCreateBytes(n int, alphabets ...byte) []byte { + return utils.RandomCreateBytes(n, alphabets...) +} diff --git a/utils/rand_test.go b/adapter/utils/rand_test.go similarity index 100% rename from utils/rand_test.go rename to adapter/utils/rand_test.go diff --git a/adapter/utils/safemap.go b/adapter/utils/safemap.go new file mode 100644 index 00000000..bb50f3cd --- /dev/null +++ b/adapter/utils/safemap.go @@ -0,0 +1,58 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 utils + +import ( + "github.com/astaxie/beego/core/utils" +) + +// BeeMap is a map with lock +type BeeMap utils.BeeMap + +// NewBeeMap return new safemap +func NewBeeMap() *BeeMap { + return (*BeeMap)(utils.NewBeeMap()) +} + +// Get from maps return the k's value +func (m *BeeMap) Get(k interface{}) interface{} { + return (*utils.BeeMap)(m).Get(k) +} + +// Set Maps the given key and value. Returns false +// if the key is already in the map and changes nothing. +func (m *BeeMap) Set(k interface{}, v interface{}) bool { + return (*utils.BeeMap)(m).Set(k, v) +} + +// Check Returns true if k is exist in the map. +func (m *BeeMap) Check(k interface{}) bool { + return (*utils.BeeMap)(m).Check(k) +} + +// Delete the given key and value. +func (m *BeeMap) Delete(k interface{}) { + (*utils.BeeMap)(m).Delete(k) +} + +// Items returns all items in safemap. +func (m *BeeMap) Items() map[interface{}]interface{} { + return (*utils.BeeMap)(m).Items() +} + +// Count returns the number of items within the map. +func (m *BeeMap) Count() int { + return (*utils.BeeMap)(m).Count() +} diff --git a/utils/safemap_test.go b/adapter/utils/safemap_test.go similarity index 100% rename from utils/safemap_test.go rename to adapter/utils/safemap_test.go diff --git a/adapter/utils/slice.go b/adapter/utils/slice.go new file mode 100644 index 00000000..44b782b4 --- /dev/null +++ b/adapter/utils/slice.go @@ -0,0 +1,101 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 utils + +import ( + "github.com/astaxie/beego/core/utils" +) + +type reducetype func(interface{}) interface{} +type filtertype func(interface{}) bool + +// InSlice checks given string in string slice or not. +func InSlice(v string, sl []string) bool { + return utils.InSlice(v, sl) +} + +// InSliceIface checks given interface in interface slice. +func InSliceIface(v interface{}, sl []interface{}) bool { + return utils.InSliceIface(v, sl) +} + +// SliceRandList generate an int slice from min to max. +func SliceRandList(min, max int) []int { + return utils.SliceRandList(min, max) +} + +// SliceMerge merges interface slices to one slice. +func SliceMerge(slice1, slice2 []interface{}) (c []interface{}) { + return utils.SliceMerge(slice1, slice2) +} + +// SliceReduce generates a new slice after parsing every value by reduce function +func SliceReduce(slice []interface{}, a reducetype) (dslice []interface{}) { + return utils.SliceReduce(slice, func(i interface{}) interface{} { + return a(i) + }) +} + +// SliceRand returns random one from slice. +func SliceRand(a []interface{}) (b interface{}) { + return utils.SliceRand(a) +} + +// SliceSum sums all values in int64 slice. +func SliceSum(intslice []int64) (sum int64) { + return utils.SliceSum(intslice) +} + +// SliceFilter generates a new slice after filter function. +func SliceFilter(slice []interface{}, a filtertype) (ftslice []interface{}) { + return utils.SliceFilter(slice, func(i interface{}) bool { + return a(i) + }) +} + +// SliceDiff returns diff slice of slice1 - slice2. +func SliceDiff(slice1, slice2 []interface{}) (diffslice []interface{}) { + return utils.SliceDiff(slice1, slice2) +} + +// SliceIntersect returns slice that are present in all the slice1 and slice2. +func SliceIntersect(slice1, slice2 []interface{}) (diffslice []interface{}) { + return utils.SliceIntersect(slice1, slice2) +} + +// SliceChunk separates one slice to some sized slice. +func SliceChunk(slice []interface{}, size int) (chunkslice [][]interface{}) { + return utils.SliceChunk(slice, size) +} + +// SliceRange generates a new slice from begin to end with step duration of int64 number. +func SliceRange(start, end, step int64) (intslice []int64) { + return utils.SliceRange(start, end, step) +} + +// SlicePad prepends size number of val into slice. +func SlicePad(slice []interface{}, size int, val interface{}) []interface{} { + return utils.SlicePad(slice, size, val) +} + +// SliceUnique cleans repeated values in slice. +func SliceUnique(slice []interface{}) (uniqueslice []interface{}) { + return utils.SliceUnique(slice) +} + +// SliceShuffle shuffles a slice. +func SliceShuffle(slice []interface{}) []interface{} { + return utils.SliceShuffle(slice) +} diff --git a/utils/slice_test.go b/adapter/utils/slice_test.go similarity index 100% rename from utils/slice_test.go rename to adapter/utils/slice_test.go diff --git a/adapter/utils/utils.go b/adapter/utils/utils.go new file mode 100644 index 00000000..8ba21bc4 --- /dev/null +++ b/adapter/utils/utils.go @@ -0,0 +1,10 @@ +package utils + +import ( + "github.com/astaxie/beego/core/utils" +) + +// GetGOPATHs returns all paths in GOPATH variable. +func GetGOPATHs() []string { + return utils.GetGOPATHs() +} diff --git a/adapter/validation/util.go b/adapter/validation/util.go new file mode 100644 index 00000000..431ce80d --- /dev/null +++ b/adapter/validation/util.go @@ -0,0 +1,62 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 validation + +import ( + "reflect" + + "github.com/astaxie/beego/core/validation" +) + +const ( + // ValidTag struct tag + ValidTag = validation.ValidTag + + LabelTag = validation.LabelTag +) + +var ( + ErrInt64On32 = validation.ErrInt64On32 +) + +// CustomFunc is for custom validate function +type CustomFunc func(v *Validation, obj interface{}, key string) + +// AddCustomFunc Add a custom function to validation +// The name can not be: +// Clear +// HasErrors +// ErrorMap +// Error +// Check +// Valid +// NoMatch +// If the name is same with exists function, it will replace the origin valid function +func AddCustomFunc(name string, f CustomFunc) error { + return validation.AddCustomFunc(name, func(v *validation.Validation, obj interface{}, key string) { + f((*Validation)(v), obj, key) + }) +} + +// ValidFunc Valid function type +type ValidFunc validation.ValidFunc + +// Funcs Validate function map +type Funcs validation.Funcs + +// Call validate values with named type string +func (f Funcs) Call(name string, params ...interface{}) (result []reflect.Value, err error) { + return (validation.Funcs(f)).Call(name, params...) +} diff --git a/adapter/validation/validation.go b/adapter/validation/validation.go new file mode 100644 index 00000000..e90c9f5b --- /dev/null +++ b/adapter/validation/validation.go @@ -0,0 +1,274 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 validation for validations +// +// import ( +// "github.com/astaxie/beego/validation" +// "log" +// ) +// +// type User struct { +// Name string +// Age int +// } +// +// func main() { +// u := User{"man", 40} +// valid := validation.Validation{} +// valid.Required(u.Name, "name") +// valid.MaxSize(u.Name, 15, "nameMax") +// valid.Range(u.Age, 0, 140, "age") +// if valid.HasErrors() { +// // validation does not pass +// // print invalid message +// for _, err := range valid.Errors { +// log.Println(err.Key, err.Message) +// } +// } +// // or use like this +// if v := valid.Max(u.Age, 140, "ageMax"); !v.Ok { +// log.Println(v.Error.Key, v.Error.Message) +// } +// } +// +// more info: http://beego.me/docs/mvc/controller/validation.md +package validation + +import ( + "fmt" + "regexp" + + "github.com/astaxie/beego/core/validation" +) + +// ValidFormer valid interface +type ValidFormer interface { + Valid(*Validation) +} + +// Error show the error +type Error validation.Error + +// String Returns the Message. +func (e *Error) String() string { + if e == nil { + return "" + } + return e.Message +} + +// Implement Error interface. +// Return e.String() +func (e *Error) Error() string { return e.String() } + +// Result is returned from every validation method. +// It provides an indication of success, and a pointer to the Error (if any). +type Result validation.Result + +// Key Get Result by given key string. +func (r *Result) Key(key string) *Result { + if r.Error != nil { + r.Error.Key = key + } + return r +} + +// Message Set Result message by string or format string with args +func (r *Result) Message(message string, args ...interface{}) *Result { + if r.Error != nil { + if len(args) == 0 { + r.Error.Message = message + } else { + r.Error.Message = fmt.Sprintf(message, args...) + } + } + return r +} + +// A Validation context manages data validation and error messages. +type Validation validation.Validation + +// Clear Clean all ValidationError. +func (v *Validation) Clear() { + (*validation.Validation)(v).Clear() +} + +// HasErrors Has ValidationError nor not. +func (v *Validation) HasErrors() bool { + return (*validation.Validation)(v).HasErrors() +} + +// ErrorMap Return the errors mapped by key. +// If there are multiple validation errors associated with a single key, the +// first one "wins". (Typically the first validation will be the more basic). +func (v *Validation) ErrorMap() map[string][]*Error { + newErrors := (*validation.Validation)(v).ErrorMap() + res := make(map[string][]*Error, len(newErrors)) + for n, es := range newErrors { + errs := make([]*Error, 0, len(es)) + + for _, e := range es { + errs = append(errs, (*Error)(e)) + } + + res[n] = errs + } + return res +} + +// Error Add an error to the validation context. +func (v *Validation) Error(message string, args ...interface{}) *Result { + return (*Result)((*validation.Validation)(v).Error(message, args...)) +} + +// Required Test that the argument is non-nil and non-empty (if string or list) +func (v *Validation) Required(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Required(obj, key)) +} + +// Min Test that the obj is greater than min if obj's type is int +func (v *Validation) Min(obj interface{}, min int, key string) *Result { + return (*Result)((*validation.Validation)(v).Min(obj, min, key)) +} + +// Max Test that the obj is less than max if obj's type is int +func (v *Validation) Max(obj interface{}, max int, key string) *Result { + return (*Result)((*validation.Validation)(v).Max(obj, max, key)) +} + +// Range Test that the obj is between mni and max if obj's type is int +func (v *Validation) Range(obj interface{}, min, max int, key string) *Result { + return (*Result)((*validation.Validation)(v).Range(obj, min, max, key)) +} + +// MinSize Test that the obj is longer than min size if type is string or slice +func (v *Validation) MinSize(obj interface{}, min int, key string) *Result { + return (*Result)((*validation.Validation)(v).MinSize(obj, min, key)) +} + +// MaxSize Test that the obj is shorter than max size if type is string or slice +func (v *Validation) MaxSize(obj interface{}, max int, key string) *Result { + return (*Result)((*validation.Validation)(v).MaxSize(obj, max, key)) +} + +// Length Test that the obj is same length to n if type is string or slice +func (v *Validation) Length(obj interface{}, n int, key string) *Result { + return (*Result)((*validation.Validation)(v).Length(obj, n, key)) +} + +// Alpha Test that the obj is [a-zA-Z] if type is string +func (v *Validation) Alpha(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Alpha(obj, key)) +} + +// Numeric Test that the obj is [0-9] if type is string +func (v *Validation) Numeric(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Numeric(obj, key)) +} + +// AlphaNumeric Test that the obj is [0-9a-zA-Z] if type is string +func (v *Validation) AlphaNumeric(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).AlphaNumeric(obj, key)) +} + +// Match Test that the obj matches regexp if type is string +func (v *Validation) Match(obj interface{}, regex *regexp.Regexp, key string) *Result { + return (*Result)((*validation.Validation)(v).Match(obj, regex, key)) +} + +// NoMatch Test that the obj doesn't match regexp if type is string +func (v *Validation) NoMatch(obj interface{}, regex *regexp.Regexp, key string) *Result { + return (*Result)((*validation.Validation)(v).NoMatch(obj, regex, key)) +} + +// AlphaDash Test that the obj is [0-9a-zA-Z_-] if type is string +func (v *Validation) AlphaDash(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).AlphaDash(obj, key)) +} + +// Email Test that the obj is email address if type is string +func (v *Validation) Email(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Email(obj, key)) +} + +// IP Test that the obj is IP address if type is string +func (v *Validation) IP(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).IP(obj, key)) +} + +// Base64 Test that the obj is base64 encoded if type is string +func (v *Validation) Base64(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Base64(obj, key)) +} + +// Mobile Test that the obj is chinese mobile number if type is string +func (v *Validation) Mobile(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Mobile(obj, key)) +} + +// Tel Test that the obj is chinese telephone number if type is string +func (v *Validation) Tel(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Tel(obj, key)) +} + +// Phone Test that the obj is chinese mobile or telephone number if type is string +func (v *Validation) Phone(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).Phone(obj, key)) +} + +// ZipCode Test that the obj is chinese zip code if type is string +func (v *Validation) ZipCode(obj interface{}, key string) *Result { + return (*Result)((*validation.Validation)(v).ZipCode(obj, key)) +} + +// key must like aa.bb.cc or aa.bb. +// AddError adds independent error message for the provided key +func (v *Validation) AddError(key, message string) { + (*validation.Validation)(v).AddError(key, message) +} + +// SetError Set error message for one field in ValidationError +func (v *Validation) SetError(fieldName string, errMsg string) *Error { + return (*Error)((*validation.Validation)(v).SetError(fieldName, errMsg)) +} + +// Check Apply a group of validators to a field, in order, and return the +// ValidationResult from the first one that fails, or the last one that +// succeeds. +func (v *Validation) Check(obj interface{}, checks ...Validator) *Result { + vldts := make([]validation.Validator, 0, len(checks)) + for _, v := range checks { + vldts = append(vldts, validation.Validator(v)) + } + return (*Result)((*validation.Validation)(v).Check(obj, vldts...)) +} + +// Valid Validate a struct. +// the obj parameter must be a struct or a struct pointer +func (v *Validation) Valid(obj interface{}) (b bool, err error) { + return (*validation.Validation)(v).Valid(obj) +} + +// RecursiveValid Recursively validate a struct. +// Step1: Validate by v.Valid +// Step2: If pass on step1, then reflect obj's fields +// Step3: Do the Recursively validation to all struct or struct pointer fields +func (v *Validation) RecursiveValid(objc interface{}) (bool, error) { + return (*validation.Validation)(v).RecursiveValid(objc) +} + +func (v *Validation) CanSkipAlso(skipFunc string) { + (*validation.Validation)(v).CanSkipAlso(skipFunc) +} diff --git a/validation/validation_test.go b/adapter/validation/validation_test.go similarity index 100% rename from validation/validation_test.go rename to adapter/validation/validation_test.go diff --git a/adapter/validation/validators.go b/adapter/validation/validators.go new file mode 100644 index 00000000..5cd5d286 --- /dev/null +++ b/adapter/validation/validators.go @@ -0,0 +1,512 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 validation + +import ( + "sync" + + "github.com/astaxie/beego/core/validation" +) + +// CanSkipFuncs will skip valid if RequiredFirst is true and the struct field's value is empty +var CanSkipFuncs = validation.CanSkipFuncs + +// MessageTmpls store commond validate template +var MessageTmpls = map[string]string{ + "Required": "Can not be empty", + "Min": "Minimum is %d", + "Max": "Maximum is %d", + "Range": "Range is %d to %d", + "MinSize": "Minimum size is %d", + "MaxSize": "Maximum size is %d", + "Length": "Required length is %d", + "Alpha": "Must be valid alpha characters", + "Numeric": "Must be valid numeric characters", + "AlphaNumeric": "Must be valid alpha or numeric characters", + "Match": "Must match %s", + "NoMatch": "Must not match %s", + "AlphaDash": "Must be valid alpha or numeric or dash(-_) characters", + "Email": "Must be a valid email address", + "IP": "Must be a valid ip address", + "Base64": "Must be valid base64 characters", + "Mobile": "Must be valid mobile number", + "Tel": "Must be valid telephone number", + "Phone": "Must be valid telephone or mobile phone number", + "ZipCode": "Must be valid zipcode", +} + +var once sync.Once + +// SetDefaultMessage set default messages +// if not set, the default messages are +// "Required": "Can not be empty", +// "Min": "Minimum is %d", +// "Max": "Maximum is %d", +// "Range": "Range is %d to %d", +// "MinSize": "Minimum size is %d", +// "MaxSize": "Maximum size is %d", +// "Length": "Required length is %d", +// "Alpha": "Must be valid alpha characters", +// "Numeric": "Must be valid numeric characters", +// "AlphaNumeric": "Must be valid alpha or numeric characters", +// "Match": "Must match %s", +// "NoMatch": "Must not match %s", +// "AlphaDash": "Must be valid alpha or numeric or dash(-_) characters", +// "Email": "Must be a valid email address", +// "IP": "Must be a valid ip address", +// "Base64": "Must be valid base64 characters", +// "Mobile": "Must be valid mobile number", +// "Tel": "Must be valid telephone number", +// "Phone": "Must be valid telephone or mobile phone number", +// "ZipCode": "Must be valid zipcode", +func SetDefaultMessage(msg map[string]string) { + validation.SetDefaultMessage(msg) +} + +// Validator interface +type Validator interface { + IsSatisfied(interface{}) bool + DefaultMessage() string + GetKey() string + GetLimitValue() interface{} +} + +// Required struct +type Required validation.Required + +// IsSatisfied judge whether obj has value +func (r Required) IsSatisfied(obj interface{}) bool { + return validation.Required(r).IsSatisfied(obj) +} + +// DefaultMessage return the default error message +func (r Required) DefaultMessage() string { + return validation.Required(r).DefaultMessage() +} + +// GetKey return the r.Key +func (r Required) GetKey() string { + return validation.Required(r).GetKey() +} + +// GetLimitValue return nil now +func (r Required) GetLimitValue() interface{} { + return validation.Required(r).GetLimitValue() +} + +// Min check struct +type Min validation.Min + +// IsSatisfied judge whether obj is valid +// not support int64 on 32-bit platform +func (m Min) IsSatisfied(obj interface{}) bool { + return validation.Min(m).IsSatisfied(obj) +} + +// DefaultMessage return the default min error message +func (m Min) DefaultMessage() string { + return validation.Min(m).DefaultMessage() +} + +// GetKey return the m.Key +func (m Min) GetKey() string { + return validation.Min(m).GetKey() +} + +// GetLimitValue return the limit value, Min +func (m Min) GetLimitValue() interface{} { + return validation.Min(m).GetLimitValue() +} + +// Max validate struct +type Max validation.Max + +// IsSatisfied judge whether obj is valid +// not support int64 on 32-bit platform +func (m Max) IsSatisfied(obj interface{}) bool { + return validation.Max(m).IsSatisfied(obj) +} + +// DefaultMessage return the default max error message +func (m Max) DefaultMessage() string { + return validation.Max(m).DefaultMessage() +} + +// GetKey return the m.Key +func (m Max) GetKey() string { + return validation.Max(m).GetKey() +} + +// GetLimitValue return the limit value, Max +func (m Max) GetLimitValue() interface{} { + return validation.Max(m).GetLimitValue() +} + +// Range Requires an integer to be within Min, Max inclusive. +type Range validation.Range + +// IsSatisfied judge whether obj is valid +// not support int64 on 32-bit platform +func (r Range) IsSatisfied(obj interface{}) bool { + return validation.Range(r).IsSatisfied(obj) +} + +// DefaultMessage return the default Range error message +func (r Range) DefaultMessage() string { + return validation.Range(r).DefaultMessage() +} + +// GetKey return the m.Key +func (r Range) GetKey() string { + return validation.Range(r).GetKey() +} + +// GetLimitValue return the limit value, Max +func (r Range) GetLimitValue() interface{} { + return validation.Range(r).GetLimitValue() +} + +// MinSize Requires an array or string to be at least a given length. +type MinSize validation.MinSize + +// IsSatisfied judge whether obj is valid +func (m MinSize) IsSatisfied(obj interface{}) bool { + return validation.MinSize(m).IsSatisfied(obj) +} + +// DefaultMessage return the default MinSize error message +func (m MinSize) DefaultMessage() string { + return validation.MinSize(m).DefaultMessage() +} + +// GetKey return the m.Key +func (m MinSize) GetKey() string { + return validation.MinSize(m).GetKey() +} + +// GetLimitValue return the limit value +func (m MinSize) GetLimitValue() interface{} { + return validation.MinSize(m).GetLimitValue() +} + +// MaxSize Requires an array or string to be at most a given length. +type MaxSize validation.MaxSize + +// IsSatisfied judge whether obj is valid +func (m MaxSize) IsSatisfied(obj interface{}) bool { + return validation.MaxSize(m).IsSatisfied(obj) +} + +// DefaultMessage return the default MaxSize error message +func (m MaxSize) DefaultMessage() string { + return validation.MaxSize(m).DefaultMessage() +} + +// GetKey return the m.Key +func (m MaxSize) GetKey() string { + return validation.MaxSize(m).GetKey() +} + +// GetLimitValue return the limit value +func (m MaxSize) GetLimitValue() interface{} { + return validation.MaxSize(m).GetLimitValue() +} + +// Length Requires an array or string to be exactly a given length. +type Length validation.Length + +// IsSatisfied judge whether obj is valid +func (l Length) IsSatisfied(obj interface{}) bool { + return validation.Length(l).IsSatisfied(obj) +} + +// DefaultMessage return the default Length error message +func (l Length) DefaultMessage() string { + return validation.Length(l).DefaultMessage() +} + +// GetKey return the m.Key +func (l Length) GetKey() string { + return validation.Length(l).GetKey() +} + +// GetLimitValue return the limit value +func (l Length) GetLimitValue() interface{} { + return validation.Length(l).GetLimitValue() +} + +// Alpha check the alpha +type Alpha validation.Alpha + +// IsSatisfied judge whether obj is valid +func (a Alpha) IsSatisfied(obj interface{}) bool { + return validation.Alpha(a).IsSatisfied(obj) +} + +// DefaultMessage return the default Length error message +func (a Alpha) DefaultMessage() string { + return validation.Alpha(a).DefaultMessage() +} + +// GetKey return the m.Key +func (a Alpha) GetKey() string { + return validation.Alpha(a).GetKey() +} + +// GetLimitValue return the limit value +func (a Alpha) GetLimitValue() interface{} { + return validation.Alpha(a).GetLimitValue() +} + +// Numeric check number +type Numeric validation.Numeric + +// IsSatisfied judge whether obj is valid +func (n Numeric) IsSatisfied(obj interface{}) bool { + return validation.Numeric(n).IsSatisfied(obj) +} + +// DefaultMessage return the default Length error message +func (n Numeric) DefaultMessage() string { + return validation.Numeric(n).DefaultMessage() +} + +// GetKey return the n.Key +func (n Numeric) GetKey() string { + return validation.Numeric(n).GetKey() +} + +// GetLimitValue return the limit value +func (n Numeric) GetLimitValue() interface{} { + return validation.Numeric(n).GetLimitValue() +} + +// AlphaNumeric check alpha and number +type AlphaNumeric validation.AlphaNumeric + +// IsSatisfied judge whether obj is valid +func (a AlphaNumeric) IsSatisfied(obj interface{}) bool { + return validation.AlphaNumeric(a).IsSatisfied(obj) +} + +// DefaultMessage return the default Length error message +func (a AlphaNumeric) DefaultMessage() string { + return validation.AlphaNumeric(a).DefaultMessage() +} + +// GetKey return the a.Key +func (a AlphaNumeric) GetKey() string { + return validation.AlphaNumeric(a).GetKey() +} + +// GetLimitValue return the limit value +func (a AlphaNumeric) GetLimitValue() interface{} { + return validation.AlphaNumeric(a).GetLimitValue() +} + +// Match Requires a string to match a given regex. +type Match validation.Match + +// IsSatisfied judge whether obj is valid +func (m Match) IsSatisfied(obj interface{}) bool { + return validation.Match(m).IsSatisfied(obj) +} + +// DefaultMessage return the default Match error message +func (m Match) DefaultMessage() string { + return validation.Match(m).DefaultMessage() +} + +// GetKey return the m.Key +func (m Match) GetKey() string { + return validation.Match(m).GetKey() +} + +// GetLimitValue return the limit value +func (m Match) GetLimitValue() interface{} { + return validation.Match(m).GetLimitValue() +} + +// NoMatch Requires a string to not match a given regex. +type NoMatch validation.NoMatch + +// IsSatisfied judge whether obj is valid +func (n NoMatch) IsSatisfied(obj interface{}) bool { + return validation.NoMatch(n).IsSatisfied(obj) +} + +// DefaultMessage return the default NoMatch error message +func (n NoMatch) DefaultMessage() string { + return validation.NoMatch(n).DefaultMessage() +} + +// GetKey return the n.Key +func (n NoMatch) GetKey() string { + return validation.NoMatch(n).GetKey() +} + +// GetLimitValue return the limit value +func (n NoMatch) GetLimitValue() interface{} { + return validation.NoMatch(n).GetLimitValue() +} + +// AlphaDash check not Alpha +type AlphaDash validation.AlphaDash + +// DefaultMessage return the default AlphaDash error message +func (a AlphaDash) DefaultMessage() string { + return validation.AlphaDash(a).DefaultMessage() +} + +// GetKey return the n.Key +func (a AlphaDash) GetKey() string { + return validation.AlphaDash(a).GetKey() +} + +// GetLimitValue return the limit value +func (a AlphaDash) GetLimitValue() interface{} { + return validation.AlphaDash(a).GetLimitValue() +} + +// Email check struct +type Email validation.Email + +// DefaultMessage return the default Email error message +func (e Email) DefaultMessage() string { + return validation.Email(e).DefaultMessage() +} + +// GetKey return the n.Key +func (e Email) GetKey() string { + return validation.Email(e).GetKey() +} + +// GetLimitValue return the limit value +func (e Email) GetLimitValue() interface{} { + return validation.Email(e).GetLimitValue() +} + +// IP check struct +type IP validation.IP + +// DefaultMessage return the default IP error message +func (i IP) DefaultMessage() string { + return validation.IP(i).DefaultMessage() +} + +// GetKey return the i.Key +func (i IP) GetKey() string { + return validation.IP(i).GetKey() +} + +// GetLimitValue return the limit value +func (i IP) GetLimitValue() interface{} { + return validation.IP(i).GetLimitValue() +} + +// Base64 check struct +type Base64 validation.Base64 + +// DefaultMessage return the default Base64 error message +func (b Base64) DefaultMessage() string { + return validation.Base64(b).DefaultMessage() +} + +// GetKey return the b.Key +func (b Base64) GetKey() string { + return validation.Base64(b).GetKey() +} + +// GetLimitValue return the limit value +func (b Base64) GetLimitValue() interface{} { + return validation.Base64(b).GetLimitValue() +} + +// Mobile check struct +type Mobile validation.Mobile + +// DefaultMessage return the default Mobile error message +func (m Mobile) DefaultMessage() string { + return validation.Mobile(m).DefaultMessage() +} + +// GetKey return the m.Key +func (m Mobile) GetKey() string { + return validation.Mobile(m).GetKey() +} + +// GetLimitValue return the limit value +func (m Mobile) GetLimitValue() interface{} { + return validation.Mobile(m).GetLimitValue() +} + +// Tel check telephone struct +type Tel validation.Tel + +// DefaultMessage return the default Tel error message +func (t Tel) DefaultMessage() string { + return validation.Tel(t).DefaultMessage() +} + +// GetKey return the t.Key +func (t Tel) GetKey() string { + return validation.Tel(t).GetKey() +} + +// GetLimitValue return the limit value +func (t Tel) GetLimitValue() interface{} { + return validation.Tel(t).GetLimitValue() +} + +// Phone just for chinese telephone or mobile phone number +type Phone validation.Phone + +// IsSatisfied judge whether obj is valid +func (p Phone) IsSatisfied(obj interface{}) bool { + return validation.Phone(p).IsSatisfied(obj) +} + +// DefaultMessage return the default Phone error message +func (p Phone) DefaultMessage() string { + return validation.Phone(p).DefaultMessage() +} + +// GetKey return the p.Key +func (p Phone) GetKey() string { + return validation.Phone(p).GetKey() +} + +// GetLimitValue return the limit value +func (p Phone) GetLimitValue() interface{} { + return validation.Phone(p).GetLimitValue() +} + +// ZipCode check the zip struct +type ZipCode validation.ZipCode + +// DefaultMessage return the default Zip error message +func (z ZipCode) DefaultMessage() string { + return validation.ZipCode(z).DefaultMessage() +} + +// GetKey return the z.Key +func (z ZipCode) GetKey() string { + return validation.ZipCode(z).GetKey() +} + +// GetLimitValue return the limit value +func (z ZipCode) GetLimitValue() interface{} { + return validation.ZipCode(z).GetLimitValue() +} diff --git a/admin.go b/admin.go deleted file mode 100644 index 3e538a0e..00000000 --- a/admin.go +++ /dev/null @@ -1,420 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 beego - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "os" - "reflect" - "text/template" - "time" - - "github.com/prometheus/client_golang/prometheus/promhttp" - - "github.com/astaxie/beego/grace" - "github.com/astaxie/beego/logs" - "github.com/astaxie/beego/toolbox" - "github.com/astaxie/beego/utils" -) - -// BeeAdminApp is the default adminApp used by admin module. -var beeAdminApp *adminApp - -// FilterMonitorFunc is default monitor filter when admin module is enable. -// if this func returns, admin module records qps for this request by condition of this function logic. -// usage: -// func MyFilterMonitor(method, requestPath string, t time.Duration, pattern string, statusCode int) bool { -// if method == "POST" { -// return false -// } -// if t.Nanoseconds() < 100 { -// return false -// } -// if strings.HasPrefix(requestPath, "/astaxie") { -// return false -// } -// return true -// } -// beego.FilterMonitorFunc = MyFilterMonitor. -var FilterMonitorFunc func(string, string, time.Duration, string, int) bool - -func init() { - beeAdminApp = &adminApp{ - routers: make(map[string]http.HandlerFunc), - } - // keep in mind that all data should be html escaped to avoid XSS attack - beeAdminApp.Route("/", adminIndex) - beeAdminApp.Route("/qps", qpsIndex) - beeAdminApp.Route("/prof", profIndex) - beeAdminApp.Route("/healthcheck", healthcheck) - beeAdminApp.Route("/task", taskStatus) - beeAdminApp.Route("/listconf", listConf) - beeAdminApp.Route("/metrics", promhttp.Handler().ServeHTTP) - FilterMonitorFunc = func(string, string, time.Duration, string, int) bool { return true } -} - -// AdminIndex is the default http.Handler for admin module. -// it matches url pattern "/". -func adminIndex(rw http.ResponseWriter, _ *http.Request) { - execTpl(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl) -} - -// QpsIndex is the http.Handler for writing qps statistics map result info in http.ResponseWriter. -// it's registered with url pattern "/qps" in admin module. -func qpsIndex(rw http.ResponseWriter, _ *http.Request) { - data := make(map[interface{}]interface{}) - data["Content"] = toolbox.StatisticsMap.GetMap() - - // do html escape before display path, avoid xss - if content, ok := (data["Content"]).(M); ok { - if resultLists, ok := (content["Data"]).([][]string); ok { - for i := range resultLists { - if len(resultLists[i]) > 0 { - resultLists[i][0] = template.HTMLEscapeString(resultLists[i][0]) - } - } - } - } - - execTpl(rw, data, qpsTpl, defaultScriptsTpl) -} - -// ListConf is the http.Handler of displaying all beego configuration values as key/value pair. -// it's registered with url pattern "/listconf" in admin module. -func listConf(rw http.ResponseWriter, r *http.Request) { - r.ParseForm() - command := r.Form.Get("command") - if command == "" { - rw.Write([]byte("command not support")) - return - } - - data := make(map[interface{}]interface{}) - switch command { - case "conf": - m := make(M) - list("BConfig", BConfig, m) - m["AppConfigPath"] = template.HTMLEscapeString(appConfigPath) - m["AppConfigProvider"] = template.HTMLEscapeString(appConfigProvider) - tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) - tmpl = template.Must(tmpl.Parse(configTpl)) - tmpl = template.Must(tmpl.Parse(defaultScriptsTpl)) - - data["Content"] = m - - tmpl.Execute(rw, data) - - case "router": - content := PrintTree() - content["Fields"] = []string{ - "Router Pattern", - "Methods", - "Controller", - } - data["Content"] = content - data["Title"] = "Routers" - execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl) - case "filter": - var ( - content = M{ - "Fields": []string{ - "Router Pattern", - "Filter Function", - }, - } - filterTypes = []string{} - filterTypeData = make(M) - ) - - if BeeApp.Handlers.enableFilter { - var filterType string - for k, fr := range map[int]string{ - BeforeStatic: "Before Static", - BeforeRouter: "Before Router", - BeforeExec: "Before Exec", - AfterExec: "After Exec", - FinishRouter: "Finish Router"} { - if bf := BeeApp.Handlers.filters[k]; len(bf) > 0 { - filterType = fr - filterTypes = append(filterTypes, filterType) - resultList := new([][]string) - for _, f := range bf { - var result = []string{ - // void xss - template.HTMLEscapeString(f.pattern), - template.HTMLEscapeString(utils.GetFuncName(f.filterFunc)), - } - *resultList = append(*resultList, result) - } - filterTypeData[filterType] = resultList - } - } - } - - content["Data"] = filterTypeData - content["Methods"] = filterTypes - - data["Content"] = content - data["Title"] = "Filters" - execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl) - default: - rw.Write([]byte("command not support")) - } -} - -func list(root string, p interface{}, m M) { - pt := reflect.TypeOf(p) - pv := reflect.ValueOf(p) - if pt.Kind() == reflect.Ptr { - pt = pt.Elem() - pv = pv.Elem() - } - for i := 0; i < pv.NumField(); i++ { - var key string - if root == "" { - key = pt.Field(i).Name - } else { - key = root + "." + pt.Field(i).Name - } - if pv.Field(i).Kind() == reflect.Struct { - list(key, pv.Field(i).Interface(), m) - } else { - m[key] = pv.Field(i).Interface() - } - } -} - -// PrintTree prints all registered routers. -func PrintTree() M { - var ( - content = M{} - methods = []string{} - methodsData = make(M) - ) - for method, t := range BeeApp.Handlers.routers { - - resultList := new([][]string) - - printTree(resultList, t) - - methods = append(methods, template.HTMLEscapeString(method)) - methodsData[template.HTMLEscapeString(method)] = resultList - } - - content["Data"] = methodsData - content["Methods"] = methods - return content -} - -func printTree(resultList *[][]string, t *Tree) { - for _, tr := range t.fixrouters { - printTree(resultList, tr) - } - if t.wildcard != nil { - printTree(resultList, t.wildcard) - } - for _, l := range t.leaves { - if v, ok := l.runObject.(*ControllerInfo); ok { - if v.routerType == routerTypeBeego { - var result = []string{ - template.HTMLEscapeString(v.pattern), - template.HTMLEscapeString(fmt.Sprintf("%s", v.methods)), - template.HTMLEscapeString(v.controllerType.String()), - } - *resultList = append(*resultList, result) - } else if v.routerType == routerTypeRESTFul { - var result = []string{ - template.HTMLEscapeString(v.pattern), - template.HTMLEscapeString(fmt.Sprintf("%s", v.methods)), - "", - } - *resultList = append(*resultList, result) - } else if v.routerType == routerTypeHandler { - var result = []string{ - template.HTMLEscapeString(v.pattern), - "", - "", - } - *resultList = append(*resultList, result) - } - } - } -} - -// ProfIndex is a http.Handler for showing profile command. -// it's in url pattern "/prof" in admin module. -func profIndex(rw http.ResponseWriter, r *http.Request) { - r.ParseForm() - command := r.Form.Get("command") - if command == "" { - return - } - - var ( - format = r.Form.Get("format") - data = make(map[interface{}]interface{}) - result bytes.Buffer - ) - toolbox.ProcessInput(command, &result) - data["Content"] = template.HTMLEscapeString(result.String()) - - if format == "json" && command == "gc summary" { - dataJSON, err := json.Marshal(data) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - rw.Header().Set("Content-Type", "application/json") - rw.Write(dataJSON) - return - } - - data["Title"] = template.HTMLEscapeString(command) - defaultTpl := defaultScriptsTpl - if command == "gc summary" { - defaultTpl = gcAjaxTpl - } - execTpl(rw, data, profillingTpl, defaultTpl) -} - -// Healthcheck is a http.Handler calling health checking and showing the result. -// it's in "/healthcheck" pattern in admin module. -func healthcheck(rw http.ResponseWriter, _ *http.Request) { - var ( - result []string - data = make(map[interface{}]interface{}) - resultList = new([][]string) - content = M{ - "Fields": []string{"Name", "Message", "Status"}, - } - ) - - for name, h := range toolbox.AdminCheckList { - if err := h.Check(); err != nil { - result = []string{ - "error", - template.HTMLEscapeString(name), - template.HTMLEscapeString(err.Error()), - } - } else { - result = []string{ - "success", - template.HTMLEscapeString(name), - "OK", - } - } - *resultList = append(*resultList, result) - } - - content["Data"] = resultList - data["Content"] = content - data["Title"] = "Health Check" - execTpl(rw, data, healthCheckTpl, defaultScriptsTpl) -} - -// TaskStatus is a http.Handler with running task status (task name, status and the last execution). -// it's in "/task" pattern in admin module. -func taskStatus(rw http.ResponseWriter, req *http.Request) { - data := make(map[interface{}]interface{}) - - // Run Task - req.ParseForm() - taskname := req.Form.Get("taskname") - if taskname != "" { - if t, ok := toolbox.AdminTaskList[taskname]; ok { - if err := t.Run(); err != nil { - data["Message"] = []string{"error", template.HTMLEscapeString(fmt.Sprintf("%s", err))} - } - data["Message"] = []string{"success", template.HTMLEscapeString(fmt.Sprintf("%s run success,Now the Status is
%s", taskname, t.GetStatus()))} - } else { - data["Message"] = []string{"warning", template.HTMLEscapeString(fmt.Sprintf("there's no task which named: %s", taskname))} - } - } - - // List Tasks - content := make(M) - resultList := new([][]string) - var fields = []string{ - "Task Name", - "Task Spec", - "Task Status", - "Last Time", - "", - } - for tname, tk := range toolbox.AdminTaskList { - result := []string{ - template.HTMLEscapeString(tname), - template.HTMLEscapeString(tk.GetSpec()), - template.HTMLEscapeString(tk.GetStatus()), - template.HTMLEscapeString(tk.GetPrev().String()), - } - *resultList = append(*resultList, result) - } - - content["Fields"] = fields - content["Data"] = resultList - data["Content"] = content - data["Title"] = "Tasks" - execTpl(rw, data, tasksTpl, defaultScriptsTpl) -} - -func execTpl(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) { - tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) - for _, tpl := range tpls { - tmpl = template.Must(tmpl.Parse(tpl)) - } - tmpl.Execute(rw, data) -} - -// adminApp is an http.HandlerFunc map used as beeAdminApp. -type adminApp struct { - routers map[string]http.HandlerFunc -} - -// Route adds http.HandlerFunc to adminApp with url pattern. -func (admin *adminApp) Route(pattern string, f http.HandlerFunc) { - admin.routers[pattern] = f -} - -// Run adminApp http server. -// Its addr is defined in configuration file as adminhttpaddr and adminhttpport. -func (admin *adminApp) Run() { - if len(toolbox.AdminTaskList) > 0 { - toolbox.StartTask() - } - addr := BConfig.Listen.AdminAddr - - if BConfig.Listen.AdminPort != 0 { - addr = fmt.Sprintf("%s:%d", BConfig.Listen.AdminAddr, BConfig.Listen.AdminPort) - } - for p, f := range admin.routers { - http.Handle(p, f) - } - logs.Info("Admin server Running on %s", addr) - - var err error - if BConfig.Listen.Graceful { - err = grace.ListenAndServe(addr, nil) - } else { - err = http.ListenAndServe(addr, nil) - } - if err != nil { - logs.Critical("Admin ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid())) - } -} diff --git a/admin_test.go b/admin_test.go deleted file mode 100644 index 71cc209e..00000000 --- a/admin_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package beego - -import ( - "fmt" - "testing" -) - -func TestList_01(t *testing.T) { - m := make(M) - list("BConfig", BConfig, m) - t.Log(m) - om := oldMap() - for k, v := range om { - if fmt.Sprint(m[k]) != fmt.Sprint(v) { - t.Log(k, "old-key", v, "new-key", m[k]) - t.FailNow() - } - } -} - -func oldMap() M { - m := make(M) - m["BConfig.AppName"] = BConfig.AppName - m["BConfig.RunMode"] = BConfig.RunMode - m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive - m["BConfig.ServerName"] = BConfig.ServerName - m["BConfig.RecoverPanic"] = BConfig.RecoverPanic - m["BConfig.CopyRequestBody"] = BConfig.CopyRequestBody - m["BConfig.EnableGzip"] = BConfig.EnableGzip - m["BConfig.MaxMemory"] = BConfig.MaxMemory - m["BConfig.EnableErrorsShow"] = BConfig.EnableErrorsShow - m["BConfig.Listen.Graceful"] = BConfig.Listen.Graceful - m["BConfig.Listen.ServerTimeOut"] = BConfig.Listen.ServerTimeOut - m["BConfig.Listen.ListenTCP4"] = BConfig.Listen.ListenTCP4 - m["BConfig.Listen.EnableHTTP"] = BConfig.Listen.EnableHTTP - m["BConfig.Listen.HTTPAddr"] = BConfig.Listen.HTTPAddr - m["BConfig.Listen.HTTPPort"] = BConfig.Listen.HTTPPort - m["BConfig.Listen.EnableHTTPS"] = BConfig.Listen.EnableHTTPS - m["BConfig.Listen.HTTPSAddr"] = BConfig.Listen.HTTPSAddr - m["BConfig.Listen.HTTPSPort"] = BConfig.Listen.HTTPSPort - m["BConfig.Listen.HTTPSCertFile"] = BConfig.Listen.HTTPSCertFile - m["BConfig.Listen.HTTPSKeyFile"] = BConfig.Listen.HTTPSKeyFile - m["BConfig.Listen.EnableAdmin"] = BConfig.Listen.EnableAdmin - m["BConfig.Listen.AdminAddr"] = BConfig.Listen.AdminAddr - m["BConfig.Listen.AdminPort"] = BConfig.Listen.AdminPort - m["BConfig.Listen.EnableFcgi"] = BConfig.Listen.EnableFcgi - m["BConfig.Listen.EnableStdIo"] = BConfig.Listen.EnableStdIo - m["BConfig.WebConfig.AutoRender"] = BConfig.WebConfig.AutoRender - m["BConfig.WebConfig.EnableDocs"] = BConfig.WebConfig.EnableDocs - m["BConfig.WebConfig.FlashName"] = BConfig.WebConfig.FlashName - m["BConfig.WebConfig.FlashSeparator"] = BConfig.WebConfig.FlashSeparator - m["BConfig.WebConfig.DirectoryIndex"] = BConfig.WebConfig.DirectoryIndex - m["BConfig.WebConfig.StaticDir"] = BConfig.WebConfig.StaticDir - m["BConfig.WebConfig.StaticExtensionsToGzip"] = BConfig.WebConfig.StaticExtensionsToGzip - m["BConfig.WebConfig.StaticCacheFileSize"] = BConfig.WebConfig.StaticCacheFileSize - m["BConfig.WebConfig.StaticCacheFileNum"] = BConfig.WebConfig.StaticCacheFileNum - m["BConfig.WebConfig.TemplateLeft"] = BConfig.WebConfig.TemplateLeft - m["BConfig.WebConfig.TemplateRight"] = BConfig.WebConfig.TemplateRight - m["BConfig.WebConfig.ViewsPath"] = BConfig.WebConfig.ViewsPath - m["BConfig.WebConfig.EnableXSRF"] = BConfig.WebConfig.EnableXSRF - m["BConfig.WebConfig.XSRFExpire"] = BConfig.WebConfig.XSRFExpire - m["BConfig.WebConfig.Session.SessionOn"] = BConfig.WebConfig.Session.SessionOn - m["BConfig.WebConfig.Session.SessionProvider"] = BConfig.WebConfig.Session.SessionProvider - m["BConfig.WebConfig.Session.SessionName"] = BConfig.WebConfig.Session.SessionName - m["BConfig.WebConfig.Session.SessionGCMaxLifetime"] = BConfig.WebConfig.Session.SessionGCMaxLifetime - m["BConfig.WebConfig.Session.SessionProviderConfig"] = BConfig.WebConfig.Session.SessionProviderConfig - m["BConfig.WebConfig.Session.SessionCookieLifeTime"] = BConfig.WebConfig.Session.SessionCookieLifeTime - m["BConfig.WebConfig.Session.SessionAutoSetCookie"] = BConfig.WebConfig.Session.SessionAutoSetCookie - m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain - m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly - m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs - m["BConfig.Log.EnableStaticLogs"] = BConfig.Log.EnableStaticLogs - m["BConfig.Log.AccessLogsFormat"] = BConfig.Log.AccessLogsFormat - m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum - m["BConfig.Log.Outputs"] = BConfig.Log.Outputs - return m -} diff --git a/app.go b/app.go deleted file mode 100644 index f3fe6f7b..00000000 --- a/app.go +++ /dev/null @@ -1,496 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 beego - -import ( - "crypto/tls" - "crypto/x509" - "fmt" - "io/ioutil" - "net" - "net/http" - "net/http/fcgi" - "os" - "path" - "strings" - "time" - - "github.com/astaxie/beego/grace" - "github.com/astaxie/beego/logs" - "github.com/astaxie/beego/utils" - "golang.org/x/crypto/acme/autocert" -) - -var ( - // BeeApp is an application instance - BeeApp *App -) - -func init() { - // create beego application - BeeApp = NewApp() -} - -// App defines beego application with a new PatternServeMux. -type App struct { - Handlers *ControllerRegister - Server *http.Server -} - -// NewApp returns a new beego application. -func NewApp() *App { - cr := NewControllerRegister() - app := &App{Handlers: cr, Server: &http.Server{}} - return app -} - -// MiddleWare function for http.Handler -type MiddleWare func(http.Handler) http.Handler - -// Run beego application. -func (app *App) Run(mws ...MiddleWare) { - addr := BConfig.Listen.HTTPAddr - - if BConfig.Listen.HTTPPort != 0 { - addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPAddr, BConfig.Listen.HTTPPort) - } - - var ( - err error - l net.Listener - endRunning = make(chan bool, 1) - ) - - // run cgi server - if BConfig.Listen.EnableFcgi { - if BConfig.Listen.EnableStdIo { - if err = fcgi.Serve(nil, app.Handlers); err == nil { // standard I/O - logs.Info("Use FCGI via standard I/O") - } else { - logs.Critical("Cannot use FCGI via standard I/O", err) - } - return - } - if BConfig.Listen.HTTPPort == 0 { - // remove the Socket file before start - if utils.FileExists(addr) { - os.Remove(addr) - } - l, err = net.Listen("unix", addr) - } else { - l, err = net.Listen("tcp", addr) - } - if err != nil { - logs.Critical("Listen: ", err) - } - if err = fcgi.Serve(l, app.Handlers); err != nil { - logs.Critical("fcgi.Serve: ", err) - } - return - } - - app.Server.Handler = app.Handlers - for i := len(mws) - 1; i >= 0; i-- { - if mws[i] == nil { - continue - } - app.Server.Handler = mws[i](app.Server.Handler) - } - app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second - app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second - app.Server.ErrorLog = logs.GetLogger("HTTP") - - // run graceful mode - if BConfig.Listen.Graceful { - httpsAddr := BConfig.Listen.HTTPSAddr - app.Server.Addr = httpsAddr - if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS { - go func() { - time.Sleep(1000 * time.Microsecond) - if BConfig.Listen.HTTPSPort != 0 { - httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort) - app.Server.Addr = httpsAddr - } - server := grace.NewServer(httpsAddr, app.Server.Handler) - server.Server.ReadTimeout = app.Server.ReadTimeout - server.Server.WriteTimeout = app.Server.WriteTimeout - if BConfig.Listen.EnableMutualHTTPS { - if err := server.ListenAndServeMutualTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile, BConfig.Listen.TrustCaFile); err != nil { - logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid())) - time.Sleep(100 * time.Microsecond) - } - } else { - if BConfig.Listen.AutoTLS { - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...), - Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir), - } - app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate} - BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", "" - } - if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil { - logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid())) - time.Sleep(100 * time.Microsecond) - } - } - endRunning <- true - }() - } - if BConfig.Listen.EnableHTTP { - go func() { - server := grace.NewServer(addr, app.Server.Handler) - server.Server.ReadTimeout = app.Server.ReadTimeout - server.Server.WriteTimeout = app.Server.WriteTimeout - if BConfig.Listen.ListenTCP4 { - server.Network = "tcp4" - } - if err := server.ListenAndServe(); err != nil { - logs.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid())) - time.Sleep(100 * time.Microsecond) - } - endRunning <- true - }() - } - <-endRunning - return - } - - // run normal mode - if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS { - go func() { - time.Sleep(1000 * time.Microsecond) - if BConfig.Listen.HTTPSPort != 0 { - app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort) - } else if BConfig.Listen.EnableHTTP { - logs.Info("Start https server error, conflict with http. Please reset https port") - return - } - logs.Info("https server Running on https://%s", app.Server.Addr) - if BConfig.Listen.AutoTLS { - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...), - Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir), - } - app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate} - BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", "" - } else if BConfig.Listen.EnableMutualHTTPS { - pool := x509.NewCertPool() - data, err := ioutil.ReadFile(BConfig.Listen.TrustCaFile) - if err != nil { - logs.Info("MutualHTTPS should provide TrustCaFile") - return - } - pool.AppendCertsFromPEM(data) - app.Server.TLSConfig = &tls.Config{ - ClientCAs: pool, - ClientAuth: tls.RequireAndVerifyClientCert, - } - } - if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil { - logs.Critical("ListenAndServeTLS: ", err) - time.Sleep(100 * time.Microsecond) - endRunning <- true - } - }() - - } - if BConfig.Listen.EnableHTTP { - go func() { - app.Server.Addr = addr - logs.Info("http server Running on http://%s", app.Server.Addr) - if BConfig.Listen.ListenTCP4 { - ln, err := net.Listen("tcp4", app.Server.Addr) - if err != nil { - logs.Critical("ListenAndServe: ", err) - time.Sleep(100 * time.Microsecond) - endRunning <- true - return - } - if err = app.Server.Serve(ln); err != nil { - logs.Critical("ListenAndServe: ", err) - time.Sleep(100 * time.Microsecond) - endRunning <- true - return - } - } else { - if err := app.Server.ListenAndServe(); err != nil { - logs.Critical("ListenAndServe: ", err) - time.Sleep(100 * time.Microsecond) - endRunning <- true - } - } - }() - } - <-endRunning -} - -// Router adds a patterned controller handler to BeeApp. -// it's an alias method of App.Router. -// usage: -// simple router -// beego.Router("/admin", &admin.UserController{}) -// beego.Router("/admin/index", &admin.ArticleController{}) -// -// regex router -// -// beego.Router("/api/:id([0-9]+)", &controllers.RController{}) -// -// custom rules -// beego.Router("/api/list",&RestController{},"*:ListFood") -// beego.Router("/api/create",&RestController{},"post:CreateFood") -// beego.Router("/api/update",&RestController{},"put:UpdateFood") -// beego.Router("/api/delete",&RestController{},"delete:DeleteFood") -func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App { - BeeApp.Handlers.Add(rootpath, c, mappingMethods...) - return BeeApp -} - -// UnregisterFixedRoute unregisters the route with the specified fixedRoute. It is particularly useful -// in web applications that inherit most routes from a base webapp via the underscore -// import, and aim to overwrite only certain paths. -// The method parameter can be empty or "*" for all HTTP methods, or a particular -// method type (e.g. "GET" or "POST") for selective removal. -// -// Usage (replace "GET" with "*" for all methods): -// beego.UnregisterFixedRoute("/yourpreviouspath", "GET") -// beego.Router("/yourpreviouspath", yourControllerAddress, "get:GetNewPage") -func UnregisterFixedRoute(fixedRoute string, method string) *App { - subPaths := splitPath(fixedRoute) - if method == "" || method == "*" { - for m := range HTTPMETHOD { - if _, ok := BeeApp.Handlers.routers[m]; !ok { - continue - } - if BeeApp.Handlers.routers[m].prefix == strings.Trim(fixedRoute, "/ ") { - findAndRemoveSingleTree(BeeApp.Handlers.routers[m]) - continue - } - findAndRemoveTree(subPaths, BeeApp.Handlers.routers[m], m) - } - return BeeApp - } - // Single HTTP method - um := strings.ToUpper(method) - if _, ok := BeeApp.Handlers.routers[um]; ok { - if BeeApp.Handlers.routers[um].prefix == strings.Trim(fixedRoute, "/ ") { - findAndRemoveSingleTree(BeeApp.Handlers.routers[um]) - return BeeApp - } - findAndRemoveTree(subPaths, BeeApp.Handlers.routers[um], um) - } - return BeeApp -} - -func findAndRemoveTree(paths []string, entryPointTree *Tree, method string) { - for i := range entryPointTree.fixrouters { - if entryPointTree.fixrouters[i].prefix == paths[0] { - if len(paths) == 1 { - if len(entryPointTree.fixrouters[i].fixrouters) > 0 { - // If the route had children subtrees, remove just the functional leaf, - // to allow children to function as before - if len(entryPointTree.fixrouters[i].leaves) > 0 { - entryPointTree.fixrouters[i].leaves[0] = nil - entryPointTree.fixrouters[i].leaves = entryPointTree.fixrouters[i].leaves[1:] - } - } else { - // Remove the *Tree from the fixrouters slice - entryPointTree.fixrouters[i] = nil - - if i == len(entryPointTree.fixrouters)-1 { - entryPointTree.fixrouters = entryPointTree.fixrouters[:i] - } else { - entryPointTree.fixrouters = append(entryPointTree.fixrouters[:i], entryPointTree.fixrouters[i+1:len(entryPointTree.fixrouters)]...) - } - } - return - } - findAndRemoveTree(paths[1:], entryPointTree.fixrouters[i], method) - } - } -} - -func findAndRemoveSingleTree(entryPointTree *Tree) { - if entryPointTree == nil { - return - } - if len(entryPointTree.fixrouters) > 0 { - // If the route had children subtrees, remove just the functional leaf, - // to allow children to function as before - if len(entryPointTree.leaves) > 0 { - entryPointTree.leaves[0] = nil - entryPointTree.leaves = entryPointTree.leaves[1:] - } - } -} - -// Include will generate router file in the router/xxx.go from the controller's comments -// usage: -// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) -// type BankAccount struct{ -// beego.Controller -// } -// -// register the function -// func (b *BankAccount)Mapping(){ -// b.Mapping("ShowAccount" , b.ShowAccount) -// b.Mapping("ModifyAccount", b.ModifyAccount) -//} -// -// //@router /account/:id [get] -// func (b *BankAccount) ShowAccount(){ -// //logic -// } -// -// -// //@router /account/:id [post] -// func (b *BankAccount) ModifyAccount(){ -// //logic -// } -// -// the comments @router url methodlist -// url support all the function Router's pattern -// methodlist [get post head put delete options *] -func Include(cList ...ControllerInterface) *App { - BeeApp.Handlers.Include(cList...) - return BeeApp -} - -// RESTRouter adds a restful controller handler to BeeApp. -// its' controller implements beego.ControllerInterface and -// defines a param "pattern/:objectId" to visit each resource. -func RESTRouter(rootpath string, c ControllerInterface) *App { - Router(rootpath, c) - Router(path.Join(rootpath, ":objectId"), c) - return BeeApp -} - -// AutoRouter adds defined controller handler to BeeApp. -// it's same to App.AutoRouter. -// if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page, -// visit the url /main/list to exec List function or /main/page to exec Page function. -func AutoRouter(c ControllerInterface) *App { - BeeApp.Handlers.AddAuto(c) - return BeeApp -} - -// AutoPrefix adds controller handler to BeeApp with prefix. -// it's same to App.AutoRouterWithPrefix. -// if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page, -// visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function. -func AutoPrefix(prefix string, c ControllerInterface) *App { - BeeApp.Handlers.AddAutoPrefix(prefix, c) - return BeeApp -} - -// Get used to register router for Get method -// usage: -// beego.Get("/", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Get(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Get(rootpath, f) - return BeeApp -} - -// Post used to register router for Post method -// usage: -// beego.Post("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Post(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Post(rootpath, f) - return BeeApp -} - -// Delete used to register router for Delete method -// usage: -// beego.Delete("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Delete(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Delete(rootpath, f) - return BeeApp -} - -// Put used to register router for Put method -// usage: -// beego.Put("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Put(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Put(rootpath, f) - return BeeApp -} - -// Head used to register router for Head method -// usage: -// beego.Head("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Head(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Head(rootpath, f) - return BeeApp -} - -// Options used to register router for Options method -// usage: -// beego.Options("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Options(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Options(rootpath, f) - return BeeApp -} - -// Patch used to register router for Patch method -// usage: -// beego.Patch("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Patch(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Patch(rootpath, f) - return BeeApp -} - -// Any used to register router for all methods -// usage: -// beego.Any("/api", func(ctx *context.Context){ -// ctx.Output.Body("hello world") -// }) -func Any(rootpath string, f FilterFunc) *App { - BeeApp.Handlers.Any(rootpath, f) - return BeeApp -} - -// Handler used to register a Handler router -// usage: -// beego.Handler("/api", http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { -// fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) -// })) -func Handler(rootpath string, h http.Handler, options ...interface{}) *App { - BeeApp.Handlers.Handler(rootpath, h, options...) - return BeeApp -} - -// InsertFilter adds a FilterFunc with pattern condition and action constant. -// The pos means action constant including -// beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter. -// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute) -func InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) *App { - BeeApp.Handlers.InsertFilter(pattern, pos, filter, params...) - return BeeApp -} diff --git a/build_info.go b/build_info.go index 6dc2835e..42f42c28 100644 --- a/build_info.go +++ b/build_info.go @@ -15,13 +15,18 @@ package beego var ( - BuildVersion string + BuildVersion string BuildGitRevision string - BuildStatus string - BuildTag string - BuildTime string + BuildStatus string + BuildTag string + BuildTime string GoVersion string GitBranch string ) + +const ( + // VERSION represent beego web framework version. + VERSION = "2.0.0-alpha" +) diff --git a/cache/README.md b/client/cache/README.md similarity index 100% rename from cache/README.md rename to client/cache/README.md diff --git a/client/cache/cache.go b/client/cache/cache.go new file mode 100644 index 00000000..ddf246ab --- /dev/null +++ b/client/cache/cache.go @@ -0,0 +1,104 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 cache provide a Cache interface and some implement engine +// Usage: +// +// import( +// "github.com/astaxie/beego/cache" +// ) +// +// bm, err := cache.NewCache("memory", `{"interval":60}`) +// +// Use it like this: +// +// bm.Put("astaxie", 1, 10 * time.Second) +// bm.Get("astaxie") +// bm.IsExist("astaxie") +// bm.Delete("astaxie") +// +// more docs http://beego.me/docs/module/cache.md +package cache + +import ( + "context" + "fmt" + "time" +) + +// Cache interface contains all behaviors for cache adapter. +// usage: +// cache.Register("file",cache.NewFileCache) // this operation is run in init method of file.go. +// c,err := cache.NewCache("file","{....}") +// c.Put("key",value, 3600 * time.Second) +// v := c.Get("key") +// +// c.Incr("counter") // now is 1 +// c.Incr("counter") // now is 2 +// count := c.Get("counter").(int) +type Cache interface { + // Get a cached value by key. + Get(ctx context.Context, key string) (interface{}, error) + // GetMulti is a batch version of Get. + GetMulti(ctx context.Context, keys []string) ([]interface{}, error) + // Set a cached value with key and expire time. + Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error + // Delete cached value by key. + Delete(ctx context.Context, key string) error + // Increment a cached int value by key, as a counter. + Incr(ctx context.Context, key string) error + // Decrement a cached int value by key, as a counter. + Decr(ctx context.Context, key string) error + // Check if a cached value exists or not. + IsExist(ctx context.Context, key string) (bool, error) + // Clear all cache. + ClearAll(ctx context.Context) error + // Start gc routine based on config string settings. + StartAndGC(config string) error +} + +// Instance is a function create a new Cache Instance +type Instance func() Cache + +var adapters = make(map[string]Instance) + +// Register makes a cache adapter available by the adapter name. +// If Register is called twice with the same name or if driver is nil, +// it panics. +func Register(name string, adapter Instance) { + if adapter == nil { + panic("cache: Register adapter is nil") + } + if _, ok := adapters[name]; ok { + panic("cache: Register called twice for adapter " + name) + } + adapters[name] = adapter +} + +// NewCache creates a new cache driver by adapter name and config string. +// config: must be in JSON format such as {"interval":360}. +// Starts gc automatically. +func NewCache(adapterName, config string) (adapter Cache, err error) { + instanceFunc, ok := adapters[adapterName] + if !ok { + err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName) + return + } + adapter = instanceFunc() + err = adapter.StartAndGC(config) + if err != nil { + adapter = nil + } + return +} diff --git a/client/cache/cache_test.go b/client/cache/cache_test.go new file mode 100644 index 00000000..6066b72d --- /dev/null +++ b/client/cache/cache_test.go @@ -0,0 +1,193 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 cache + +import ( + "context" + "os" + "sync" + "testing" + "time" +) + +func TestCacheIncr(t *testing.T) { + bm, err := NewCache("memory", `{"interval":20}`) + if err != nil { + t.Error("init err") + } + // timeoutDuration := 10 * time.Second + + bm.Put(context.Background(), "edwardhey", 0, time.Second*20) + wg := sync.WaitGroup{} + wg.Add(10) + for i := 0; i < 10; i++ { + go func() { + defer wg.Done() + bm.Incr(context.Background(), "edwardhey") + }() + } + wg.Wait() + val, _ := bm.Get(context.Background(), "edwardhey") + if val.(int) != 10 { + t.Error("Incr err") + } +} + +func TestCache(t *testing.T) { + bm, err := NewCache("memory", `{"interval":20}`) + if err != nil { + t.Error("init err") + } + timeoutDuration := 10 * time.Second + if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil { + t.Error("set Error", err) + } + if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { + t.Error("check err") + } + + if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 1 { + t.Error("get err") + } + + time.Sleep(30 * time.Second) + + if res, _ := bm.IsExist(context.Background(), "astaxie"); res { + t.Error("check err") + } + + if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil { + t.Error("set Error", err) + } + + if err = bm.Incr(context.Background(), "astaxie"); err != nil { + t.Error("Incr Error", err) + } + + if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 2 { + t.Error("get err") + } + + if err = bm.Decr(context.Background(), "astaxie"); err != nil { + t.Error("Decr Error", err) + } + + if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 1 { + t.Error("get err") + } + bm.Delete(context.Background(), "astaxie") + if res, _ := bm.IsExist(context.Background(), "astaxie"); res { + t.Error("delete err") + } + + // test GetMulti + if err = bm.Put(context.Background(), "astaxie", "author", timeoutDuration); err != nil { + t.Error("set Error", err) + } + if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { + t.Error("check err") + } + if v, _ := bm.Get(context.Background(), "astaxie"); v.(string) != "author" { + t.Error("get err") + } + + if err = bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration); err != nil { + t.Error("set Error", err) + } + if res, _ := bm.IsExist(context.Background(), "astaxie1"); !res { + t.Error("check err") + } + + vv, _ := bm.GetMulti(context.Background(), []string{"astaxie", "astaxie1"}) + if len(vv) != 2 { + t.Error("GetMulti ERROR") + } + if vv[0].(string) != "author" { + t.Error("GetMulti ERROR") + } + if vv[1].(string) != "author1" { + t.Error("GetMulti ERROR") + } +} + +func TestFileCache(t *testing.T) { + bm, err := NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}`) + if err != nil { + t.Error("init err") + } + timeoutDuration := 10 * time.Second + if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil { + t.Error("set Error", err) + } + if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { + t.Error("check err") + } + + if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 1 { + t.Error("get err") + } + + if err = bm.Incr(context.Background(), "astaxie"); err != nil { + t.Error("Incr Error", err) + } + + if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 2 { + t.Error("get err") + } + + if err = bm.Decr(context.Background(), "astaxie"); err != nil { + t.Error("Decr Error", err) + } + + if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 1 { + t.Error("get err") + } + bm.Delete(context.Background(), "astaxie") + if res, _ := bm.IsExist(context.Background(), "astaxie"); res { + t.Error("delete err") + } + + // test string + if err = bm.Put(context.Background(), "astaxie", "author", timeoutDuration); err != nil { + t.Error("set Error", err) + } + if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { + t.Error("check err") + } + if v, _ := bm.Get(context.Background(), "astaxie"); v.(string) != "author" { + t.Error("get err") + } + + // test GetMulti + if err = bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration); err != nil { + t.Error("set Error", err) + } + if res, _ := bm.IsExist(context.Background(), "astaxie1"); !res { + t.Error("check err") + } + + vv, _ := bm.GetMulti(context.Background(), []string{"astaxie", "astaxie1"}) + if len(vv) != 2 { + t.Error("GetMulti ERROR") + } + if vv[0].(string) != "author" { + t.Error("GetMulti ERROR") + } + if vv[1].(string) != "author1" { + t.Error("GetMulti ERROR") + } + + os.RemoveAll("cache") +} diff --git a/cache/conv.go b/client/cache/conv.go similarity index 90% rename from cache/conv.go rename to client/cache/conv.go index 87800586..158f7f41 100644 --- a/cache/conv.go +++ b/client/cache/conv.go @@ -19,7 +19,7 @@ import ( "strconv" ) -// GetString convert interface to string. +// GetString converts interface to string. func GetString(v interface{}) string { switch result := v.(type) { case string: @@ -34,7 +34,7 @@ func GetString(v interface{}) string { return "" } -// GetInt convert interface to int. +// GetInt converts interface to int. func GetInt(v interface{}) int { switch result := v.(type) { case int: @@ -52,7 +52,7 @@ func GetInt(v interface{}) int { return 0 } -// GetInt64 convert interface to int64. +// GetInt64 converts interface to int64. func GetInt64(v interface{}) int64 { switch result := v.(type) { case int: @@ -71,7 +71,7 @@ func GetInt64(v interface{}) int64 { return 0 } -// GetFloat64 convert interface to float64. +// GetFloat64 converts interface to float64. func GetFloat64(v interface{}) float64 { switch result := v.(type) { case float64: @@ -85,7 +85,7 @@ func GetFloat64(v interface{}) float64 { return 0 } -// GetBool convert interface to bool. +// GetBool converts interface to bool. func GetBool(v interface{}) bool { switch result := v.(type) { case bool: diff --git a/client/cache/conv_test.go b/client/cache/conv_test.go new file mode 100644 index 00000000..b90e224a --- /dev/null +++ b/client/cache/conv_test.go @@ -0,0 +1,143 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 cache + +import ( + "testing" +) + +func TestGetString(t *testing.T) { + var t1 = "test1" + if "test1" != GetString(t1) { + t.Error("get string from string error") + } + var t2 = []byte("test2") + if "test2" != GetString(t2) { + t.Error("get string from byte array error") + } + var t3 = 1 + if "1" != GetString(t3) { + t.Error("get string from int error") + } + var t4 int64 = 1 + if "1" != GetString(t4) { + t.Error("get string from int64 error") + } + var t5 = 1.1 + if "1.1" != GetString(t5) { + t.Error("get string from float64 error") + } + + if "" != GetString(nil) { + t.Error("get string from nil error") + } +} + +func TestGetInt(t *testing.T) { + var t1 = 1 + if 1 != GetInt(t1) { + t.Error("get int from int error") + } + var t2 int32 = 32 + if 32 != GetInt(t2) { + t.Error("get int from int32 error") + } + var t3 int64 = 64 + if 64 != GetInt(t3) { + t.Error("get int from int64 error") + } + var t4 = "128" + if 128 != GetInt(t4) { + t.Error("get int from num string error") + } + if 0 != GetInt(nil) { + t.Error("get int from nil error") + } +} + +func TestGetInt64(t *testing.T) { + var i int64 = 1 + var t1 = 1 + if i != GetInt64(t1) { + t.Error("get int64 from int error") + } + var t2 int32 = 1 + if i != GetInt64(t2) { + t.Error("get int64 from int32 error") + } + var t3 int64 = 1 + if i != GetInt64(t3) { + t.Error("get int64 from int64 error") + } + var t4 = "1" + if i != GetInt64(t4) { + t.Error("get int64 from num string error") + } + if 0 != GetInt64(nil) { + t.Error("get int64 from nil") + } +} + +func TestGetFloat64(t *testing.T) { + var f = 1.11 + var t1 float32 = 1.11 + if f != GetFloat64(t1) { + t.Error("get float64 from float32 error") + } + var t2 = 1.11 + if f != GetFloat64(t2) { + t.Error("get float64 from float64 error") + } + var t3 = "1.11" + if f != GetFloat64(t3) { + t.Error("get float64 from string error") + } + + var f2 float64 = 1 + var t4 = 1 + if f2 != GetFloat64(t4) { + t.Error("get float64 from int error") + } + + if 0 != GetFloat64(nil) { + t.Error("get float64 from nil error") + } +} + +func TestGetBool(t *testing.T) { + var t1 = true + if !GetBool(t1) { + t.Error("get bool from bool error") + } + var t2 = "true" + if !GetBool(t2) { + t.Error("get bool from string error") + } + if GetBool(nil) { + t.Error("get bool from nil error") + } +} + +func byteArrayEquals(a []byte, b []byte) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true +} diff --git a/cache/file.go b/client/cache/file.go similarity index 68% rename from cache/file.go rename to client/cache/file.go index 6f12d3ee..dc818258 100644 --- a/cache/file.go +++ b/client/cache/file.go @@ -16,6 +16,7 @@ package cache import ( "bytes" + "context" "crypto/md5" "encoding/gob" "encoding/hex" @@ -28,10 +29,12 @@ import ( "reflect" "strconv" "time" + + "github.com/pkg/errors" ) -// FileCacheItem is basic unit of file cache adapter. -// it contains data and expire time. +// FileCacheItem is basic unit of file cache adapter which +// contains data and expire time. type FileCacheItem struct { Data interface{} Lastaccess time.Time @@ -54,15 +57,15 @@ type FileCache struct { EmbedExpiry int } -// NewFileCache Create new file cache with no config. -// the level and expiry need set in method StartAndGC as config string. +// NewFileCache creates a new file cache with no config. +// The level and expiry need to be set in the method StartAndGC as config string. func NewFileCache() Cache { // return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix} return &FileCache{} } -// StartAndGC will start and begin gc for file cache. -// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"} +// StartAndGC starts gc for file cache. +// config must be in the format {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"} func (fc *FileCache) StartAndGC(config string) error { cfg := make(map[string]string) @@ -91,14 +94,14 @@ func (fc *FileCache) StartAndGC(config string) error { return nil } -// Init will make new dir for file cache if not exist. +// Init makes new a dir for file cache if it does not already exist func (fc *FileCache) Init() { if ok, _ := exists(fc.CachePath); !ok { // todo : error handle _ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle } } -// get cached file name. it's md5 encoded. +// getCachedFilename returns an md5 encoded file name. func (fc *FileCache) getCacheFileName(key string) string { m := md5.New() io.WriteString(m, key) @@ -119,34 +122,45 @@ func (fc *FileCache) getCacheFileName(key string) string { } // Get value from file cache. -// if non-exist or expired, return empty string. -func (fc *FileCache) Get(key string) interface{} { +// if nonexistent or expired return an empty string. +func (fc *FileCache) Get(ctx context.Context, key string) (interface{}, error) { fileData, err := FileGetContents(fc.getCacheFileName(key)) if err != nil { - return "" + return nil, err } + var to FileCacheItem - GobDecode(fileData, &to) - if to.Expired.Before(time.Now()) { - return "" + err = GobDecode(fileData, &to) + if err != nil { + return nil, err } - return to.Data + + if to.Expired.Before(time.Now()) { + return nil, errors.New("The key is expired") + } + return to.Data, nil } // GetMulti gets values from file cache. -// if non-exist or expired, return empty string. -func (fc *FileCache) GetMulti(keys []string) []interface{} { +// if nonexistent or expired return an empty string. +func (fc *FileCache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { var rc []interface{} for _, key := range keys { - rc = append(rc, fc.Get(key)) + val, err := fc.Get(context.Background(), key) + if err != nil { + rc = append(rc, err) + } else { + rc = append(rc, val) + } + } - return rc + return rc, nil } // Put value into file cache. -// timeout means how long to keep this file, unit of ms. +// timeout: how long this file should be kept in ms // if timeout equals fc.EmbedExpiry(default is 0), cache this item forever. -func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error { +func (fc *FileCache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { gob.Register(val) item := FileCacheItem{Data: val} @@ -164,7 +178,7 @@ func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) err } // Delete file cache value. -func (fc *FileCache) Delete(key string) error { +func (fc *FileCache) Delete(ctx context.Context, key string) error { filename := fc.getCacheFileName(key) if ok, _ := exists(filename); ok { return os.Remove(filename) @@ -172,46 +186,45 @@ func (fc *FileCache) Delete(key string) error { return nil } -// Incr will increase cached int value. -// fc value is saving forever unless Delete. -func (fc *FileCache) Incr(key string) error { - data := fc.Get(key) +// Incr increases cached int value. +// fc value is saved forever unless deleted. +func (fc *FileCache) Incr(ctx context.Context, key string) error { + data, _ := fc.Get(context.Background(), key) var incr int if reflect.TypeOf(data).Name() != "int" { incr = 0 } else { incr = data.(int) + 1 } - fc.Put(key, incr, time.Duration(fc.EmbedExpiry)) + fc.Put(context.Background(), key, incr, time.Duration(fc.EmbedExpiry)) return nil } -// Decr will decrease cached int value. -func (fc *FileCache) Decr(key string) error { - data := fc.Get(key) +// Decr decreases cached int value. +func (fc *FileCache) Decr(ctx context.Context, key string) error { + data, _ := fc.Get(context.Background(), key) var decr int if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 { decr = 0 } else { decr = data.(int) - 1 } - fc.Put(key, decr, time.Duration(fc.EmbedExpiry)) + fc.Put(context.Background(), key, decr, time.Duration(fc.EmbedExpiry)) return nil } -// IsExist check value is exist. -func (fc *FileCache) IsExist(key string) bool { +// IsExist checks if value exists. +func (fc *FileCache) IsExist(ctx context.Context, key string) (bool, error) { ret, _ := exists(fc.getCacheFileName(key)) - return ret + return ret, nil } -// ClearAll will clean cached files. -// not implemented. -func (fc *FileCache) ClearAll() error { +// ClearAll cleans cached files (not implemented) +func (fc *FileCache) ClearAll(context.Context) error { return nil } -// check file exist. +// Check if a file exists func exists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { @@ -223,19 +236,19 @@ func exists(path string) (bool, error) { return false, err } -// FileGetContents Get bytes to file. -// if non-exist, create this file. +// FileGetContents Reads bytes from a file. +// if non-existent, create this file. func FileGetContents(filename string) (data []byte, e error) { return ioutil.ReadFile(filename) } -// FilePutContents Put bytes to file. -// if non-exist, create this file. +// FilePutContents puts bytes into a file. +// if non-existent, create this file. func FilePutContents(filename string, content []byte) error { return ioutil.WriteFile(filename, content, os.ModePerm) } -// GobEncode Gob encodes file cache item. +// GobEncode Gob encodes a file cache item. func GobEncode(data interface{}) ([]byte, error) { buf := bytes.NewBuffer(nil) enc := gob.NewEncoder(buf) @@ -246,7 +259,7 @@ func GobEncode(data interface{}) ([]byte, error) { return buf.Bytes(), err } -// GobDecode Gob decodes file cache item. +// GobDecode Gob decodes a file cache item. func GobDecode(data []byte, to *FileCacheItem) error { buf := bytes.NewBuffer(data) dec := gob.NewDecoder(buf) diff --git a/cache/memcache/memcache.go b/client/cache/memcache/memcache.go similarity index 71% rename from cache/memcache/memcache.go rename to client/cache/memcache/memcache.go index 19116bfa..f3774571 100644 --- a/cache/memcache/memcache.go +++ b/client/cache/memcache/memcache.go @@ -30,13 +30,15 @@ package memcache import ( + "context" "encoding/json" "errors" "strings" "time" - "github.com/astaxie/beego/cache" "github.com/bradfitz/gomemcache/memcache" + + "github.com/astaxie/beego/client/cache" ) // Cache Memcache adapter. @@ -45,34 +47,31 @@ type Cache struct { conninfo []string } -// NewMemCache create new memcache adapter. +// NewMemCache creates a new memcache adapter. func NewMemCache() cache.Cache { return &Cache{} } // Get get value from memcache. -func (rc *Cache) Get(key string) interface{} { +func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) { if rc.conn == nil { if err := rc.connectInit(); err != nil { - return err + return nil, err } } if item, err := rc.conn.Get(key); err == nil { - return item.Value + return item.Value, nil + } else { + return nil, err } - return nil } -// GetMulti get value from memcache. -func (rc *Cache) GetMulti(keys []string) []interface{} { - size := len(keys) +// GetMulti gets a value from a key in memcache. +func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { var rv []interface{} if rc.conn == nil { if err := rc.connectInit(); err != nil { - for i := 0; i < size; i++ { - rv = append(rv, err) - } - return rv + return rv, err } } mv, err := rc.conn.GetMulti(keys) @@ -80,16 +79,12 @@ func (rc *Cache) GetMulti(keys []string) []interface{} { for _, v := range mv { rv = append(rv, v.Value) } - return rv } - for i := 0; i < size; i++ { - rv = append(rv, err) - } - return rv + return rv, err } -// Put put value to memcache. -func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error { +// Put puts a value into memcache. +func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -106,8 +101,8 @@ func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error { return rc.conn.Set(&item) } -// Delete delete value in memcache. -func (rc *Cache) Delete(key string) error { +// Delete deletes a value in memcache. +func (rc *Cache) Delete(ctx context.Context, key string) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -116,8 +111,8 @@ func (rc *Cache) Delete(key string) error { return rc.conn.Delete(key) } -// Incr increase counter. -func (rc *Cache) Incr(key string) error { +// Incr increases counter. +func (rc *Cache) Incr(ctx context.Context, key string) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -127,8 +122,8 @@ func (rc *Cache) Incr(key string) error { return err } -// Decr decrease counter. -func (rc *Cache) Decr(key string) error { +// Decr decreases counter. +func (rc *Cache) Decr(ctx context.Context, key string) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -138,19 +133,19 @@ func (rc *Cache) Decr(key string) error { return err } -// IsExist check value exists in memcache. -func (rc *Cache) IsExist(key string) bool { +// IsExist checks if a value exists in memcache. +func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) { if rc.conn == nil { if err := rc.connectInit(); err != nil { - return false + return false, err } } _, err := rc.conn.Get(key) - return err == nil + return err == nil, err } -// ClearAll clear all cached in memcache. -func (rc *Cache) ClearAll() error { +// ClearAll clears all cache in memcache. +func (rc *Cache) ClearAll(context.Context) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -159,9 +154,9 @@ func (rc *Cache) ClearAll() error { return rc.conn.FlushAll() } -// StartAndGC start memcache adapter. -// config string is like {"conn":"connection info"}. -// if connecting error, return. +// StartAndGC starts the memcache adapter. +// config: must be in the format {"conn":"connection info"}. +// If an error occurs during connecting, an error is returned func (rc *Cache) StartAndGC(config string) error { var cf map[string]string json.Unmarshal([]byte(config), &cf) diff --git a/client/cache/memcache/memcache_test.go b/client/cache/memcache/memcache_test.go new file mode 100644 index 00000000..bc8936a7 --- /dev/null +++ b/client/cache/memcache/memcache_test.go @@ -0,0 +1,121 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 memcache + +import ( + "context" + "fmt" + "os" + "strconv" + "testing" + "time" + + _ "github.com/bradfitz/gomemcache/memcache" + + "github.com/astaxie/beego/client/cache" +) + +func TestMemcacheCache(t *testing.T) { + + addr := os.Getenv("MEMCACHE_ADDR") + if addr == "" { + addr = "127.0.0.1:11211" + } + + bm, err := cache.NewCache("memcache", fmt.Sprintf(`{"conn": "%s"}`, addr)) + if err != nil { + t.Error("init err") + } + timeoutDuration := 10 * time.Second + if err = bm.Put(context.Background(), "astaxie", "1", timeoutDuration); err != nil { + t.Error("set Error", err) + } + if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { + t.Error("check err") + } + + time.Sleep(11 * time.Second) + + if res, _ := bm.IsExist(context.Background(), "astaxie"); res { + t.Error("check err") + } + if err = bm.Put(context.Background(), "astaxie", "1", timeoutDuration); err != nil { + t.Error("set Error", err) + } + + val, _ := bm.Get(context.Background(), "astaxie") + if v, err := strconv.Atoi(string(val.([]byte))); err != nil || v != 1 { + t.Error("get err") + } + + if err = bm.Incr(context.Background(), "astaxie"); err != nil { + t.Error("Incr Error", err) + } + + val, _ = bm.Get(context.Background(), "astaxie") + if v, err := strconv.Atoi(string(val.([]byte))); err != nil || v != 2 { + t.Error("get err") + } + + if err = bm.Decr(context.Background(), "astaxie"); err != nil { + t.Error("Decr Error", err) + } + + val, _ = bm.Get(context.Background(), "astaxie") + if v, err := strconv.Atoi(string(val.([]byte))); err != nil || v != 1 { + t.Error("get err") + } + bm.Delete(context.Background(), "astaxie") + if res, _ := bm.IsExist(context.Background(), "astaxie"); res { + t.Error("delete err") + } + + // test string + if err = bm.Put(context.Background(), "astaxie", "author", timeoutDuration); err != nil { + t.Error("set Error", err) + } + if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { + t.Error("check err") + } + + val, _ = bm.Get(context.Background(), "astaxie") + if v := val.([]byte); string(v) != "author" { + t.Error("get err") + } + + // test GetMulti + if err = bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration); err != nil { + t.Error("set Error", err) + } + if res, _ := bm.IsExist(context.Background(), "astaxie1"); !res { + t.Error("check err") + } + + vv, _ := bm.GetMulti(context.Background(), []string{"astaxie", "astaxie1"}) + if len(vv) != 2 { + t.Error("GetMulti ERROR") + } + if string(vv[0].([]byte)) != "author" && string(vv[0].([]byte)) != "author1" { + t.Error("GetMulti ERROR") + } + if string(vv[1].([]byte)) != "author1" && string(vv[1].([]byte)) != "author" { + t.Error("GetMulti ERROR") + } + + // test clear all + if err = bm.ClearAll(context.Background()); err != nil { + t.Error("clear all err") + } +} diff --git a/cache/memory.go b/client/cache/memory.go similarity index 65% rename from cache/memory.go rename to client/cache/memory.go index d8314e3c..6f87ec08 100644 --- a/cache/memory.go +++ b/client/cache/memory.go @@ -15,6 +15,7 @@ package cache import ( + "context" "encoding/json" "errors" "sync" @@ -22,11 +23,11 @@ import ( ) var ( - // DefaultEvery means the clock time of recycling the expired cache items in memory. + // Timer for how often to recycle the expired cache items in memory (in seconds) DefaultEvery = 60 // 1 minute ) -// MemoryItem store memory cache item. +// MemoryItem stores memory cache item. type MemoryItem struct { val interface{} createdTime time.Time @@ -41,8 +42,8 @@ func (mi *MemoryItem) isExpire() bool { return time.Now().Sub(mi.createdTime) > mi.lifespan } -// MemoryCache is Memory cache adapter. -// it contains a RW locker for safe map storage. +// MemoryCache is a memory cache adapter. +// Contains a RW locker for safe map storage. type MemoryCache struct { sync.RWMutex dur time.Duration @@ -56,60 +57,65 @@ func NewMemoryCache() Cache { return &cache } -// Get cache from memory. -// if non-existed or expired, return nil. -func (bc *MemoryCache) Get(name string) interface{} { +// Get returns cache from memory. +// If non-existent or expired, return nil. +func (bc *MemoryCache) Get(ctx context.Context, key string) (interface{}, error) { bc.RLock() defer bc.RUnlock() - if itm, ok := bc.items[name]; ok { + if itm, ok := bc.items[key]; ok { if itm.isExpire() { - return nil + return nil, errors.New("the key is expired") } - return itm.val + return itm.val, nil } - return nil + return nil, nil } // GetMulti gets caches from memory. -// if non-existed or expired, return nil. -func (bc *MemoryCache) GetMulti(names []string) []interface{} { +// If non-existent or expired, return nil. +func (bc *MemoryCache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { var rc []interface{} - for _, name := range names { - rc = append(rc, bc.Get(name)) + for _, name := range keys { + val, err := bc.Get(context.Background(), name) + if err != nil { + rc = append(rc, err) + } else { + rc = append(rc, val) + } } - return rc + return rc, nil } -// Put cache to memory. -// if lifespan is 0, it will be forever till restart. -func (bc *MemoryCache) Put(name string, value interface{}, lifespan time.Duration) error { +// Put puts cache into memory. +// If lifespan is 0, it will never overwrite this value unless restarted +func (bc *MemoryCache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { bc.Lock() defer bc.Unlock() - bc.items[name] = &MemoryItem{ - val: value, + bc.items[key] = &MemoryItem{ + val: val, createdTime: time.Now(), - lifespan: lifespan, + lifespan: timeout, } return nil } // Delete cache in memory. -func (bc *MemoryCache) Delete(name string) error { +func (bc *MemoryCache) Delete(ctx context.Context, key string) error { bc.Lock() defer bc.Unlock() - if _, ok := bc.items[name]; !ok { + if _, ok := bc.items[key]; !ok { return errors.New("key not exist") } - delete(bc.items, name) - if _, ok := bc.items[name]; ok { + delete(bc.items, key) + if _, ok := bc.items[key]; ok { return errors.New("delete key error") } return nil } -// Incr increase cache counter in memory. -// it supports int,int32,int64,uint,uint32,uint64. -func (bc *MemoryCache) Incr(key string) error { +// Incr increases cache counter in memory. +// Supports int,int32,int64,uint,uint32,uint64. +func (bc *MemoryCache) Incr(ctx context.Context, key string) error { bc.Lock() defer bc.Unlock() itm, ok := bc.items[key] @@ -135,8 +141,8 @@ func (bc *MemoryCache) Incr(key string) error { return nil } -// Decr decrease counter in memory. -func (bc *MemoryCache) Decr(key string) error { +// Decr decreases counter in memory. +func (bc *MemoryCache) Decr(ctx context.Context, key string) error { bc.Lock() defer bc.Unlock() itm, ok := bc.items[key] @@ -174,25 +180,25 @@ func (bc *MemoryCache) Decr(key string) error { return nil } -// IsExist check cache exist in memory. -func (bc *MemoryCache) IsExist(name string) bool { +// IsExist checks if cache exists in memory. +func (bc *MemoryCache) IsExist(ctx context.Context, key string) (bool, error) { bc.RLock() defer bc.RUnlock() - if v, ok := bc.items[name]; ok { - return !v.isExpire() + if v, ok := bc.items[key]; ok { + return !v.isExpire(), nil } - return false + return false, nil } -// ClearAll will delete all cache in memory. -func (bc *MemoryCache) ClearAll() error { +// ClearAll deletes all cache in memory. +func (bc *MemoryCache) ClearAll(context.Context) error { bc.Lock() defer bc.Unlock() bc.items = make(map[string]*MemoryItem) return nil } -// StartAndGC start memory cache. it will check expiration in every clock time. +// StartAndGC starts memory cache. Checks expiration in every clock time. func (bc *MemoryCache) StartAndGC(config string) error { var cf map[string]int json.Unmarshal([]byte(config), &cf) @@ -230,7 +236,7 @@ func (bc *MemoryCache) vacuum() { } } -// expiredKeys returns key list which are expired. +// expiredKeys returns keys list which are expired. func (bc *MemoryCache) expiredKeys() (keys []string) { bc.RLock() defer bc.RUnlock() @@ -242,7 +248,7 @@ func (bc *MemoryCache) expiredKeys() (keys []string) { return } -// clearItems removes all the items which key in keys. +// ClearItems removes all items who's key is in keys func (bc *MemoryCache) clearItems(keys []string) { bc.Lock() defer bc.Unlock() diff --git a/cache/redis/redis.go b/client/cache/redis/redis.go similarity index 75% rename from cache/redis/redis.go rename to client/cache/redis/redis.go index 56faf211..34059835 100644 --- a/cache/redis/redis.go +++ b/client/cache/redis/redis.go @@ -30,20 +30,21 @@ package redis import ( + "context" "encoding/json" "errors" "fmt" "strconv" + "strings" "time" "github.com/gomodule/redigo/redis" - "github.com/astaxie/beego/cache" - "strings" + "github.com/astaxie/beego/client/cache" ) var ( - // DefaultKey the collection name of redis for cache adapter. + // The collection name of redis for the cache adapter. DefaultKey = "beecacheRedis" ) @@ -56,16 +57,16 @@ type Cache struct { password string maxIdle int - //the timeout to a value less than the redis server's timeout. - timeout time.Duration + // Timeout value (less than the redis server's timeout value) + timeout time.Duration } -// NewRedisCache create new redis cache with default collection name. +// NewRedisCache creates a new redis cache with default collection name. func NewRedisCache() cache.Cache { return &Cache{key: DefaultKey} } -// actually do the redis cmds, args[0] must be the key name. +// Execute the redis commands. args[0] must be the key name func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) { if len(args) < 1 { return nil, errors.New("missing required arguments") @@ -83,63 +84,60 @@ func (rc *Cache) associate(originKey interface{}) string { } // Get cache from redis. -func (rc *Cache) Get(key string) interface{} { +func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) { if v, err := rc.do("GET", key); err == nil { - return v + return v, nil + } else { + return nil, err } - return nil } -// GetMulti get cache from redis. -func (rc *Cache) GetMulti(keys []string) []interface{} { +// GetMulti gets cache from redis. +func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { c := rc.p.Get() defer c.Close() var args []interface{} for _, key := range keys { args = append(args, rc.associate(key)) } - values, err := redis.Values(c.Do("MGET", args...)) - if err != nil { - return nil - } - return values + return redis.Values(c.Do("MGET", args...)) } -// Put put cache to redis. -func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error { +// Put puts cache into redis. +func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { _, err := rc.do("SETEX", key, int64(timeout/time.Second), val) return err } -// Delete delete cache in redis. -func (rc *Cache) Delete(key string) error { +// Delete deletes a key's cache in redis. +func (rc *Cache) Delete(ctx context.Context, key string) error { _, err := rc.do("DEL", key) return err } -// IsExist check cache's existence in redis. -func (rc *Cache) IsExist(key string) bool { +// IsExist checks cache's existence in redis. +func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) { v, err := redis.Bool(rc.do("EXISTS", key)) if err != nil { - return false + return false, err } - return v + return v, nil } -// Incr increase counter in redis. -func (rc *Cache) Incr(key string) error { +// Incr increases a key's counter in redis. +func (rc *Cache) Incr(ctx context.Context, key string) error { _, err := redis.Bool(rc.do("INCRBY", key, 1)) return err } -// Decr decrease counter in redis. -func (rc *Cache) Decr(key string) error { +// Decr decreases a key's counter in redis. +func (rc *Cache) Decr(ctx context.Context, key string) error { _, err := redis.Bool(rc.do("INCRBY", key, -1)) return err } -// ClearAll clean all cache in redis. delete this redis collection. -func (rc *Cache) ClearAll() error { +// ClearAll deletes all cache in the redis collection +func (rc *Cache) ClearAll(context.Context) error { cachedKeys, err := rc.Scan(rc.key + ":*") if err != nil { return err @@ -154,7 +152,7 @@ func (rc *Cache) ClearAll() error { return err } -// Scan scan all keys matching the pattern. a better choice than `keys` +// Scan scans all keys matching a given pattern. func (rc *Cache) Scan(pattern string) (keys []string, err error) { c := rc.p.Get() defer c.Close() @@ -183,10 +181,9 @@ func (rc *Cache) Scan(pattern string) (keys []string, err error) { } } -// StartAndGC start redis cache adapter. -// config is like {"key":"collection key","conn":"connection info","dbNum":"0"} -// the cache item in redis are stored forever, -// so no gc operation. +// StartAndGC starts the redis cache adapter. +// config: must be in this format {"key":"collection key","conn":"connection info","dbNum":"0"} +// Cached items in redis are stored forever, no garbage collection happens func (rc *Cache) StartAndGC(config string) error { var cf map[string]string json.Unmarshal([]byte(config), &cf) diff --git a/client/cache/redis/redis_test.go b/client/cache/redis/redis_test.go new file mode 100644 index 00000000..f82b2c40 --- /dev/null +++ b/client/cache/redis/redis_test.go @@ -0,0 +1,163 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 redis + +import ( + "context" + "fmt" + "os" + "testing" + "time" + + "github.com/gomodule/redigo/redis" + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/client/cache" +) + +func TestRedisCache(t *testing.T) { + + redisAddr := os.Getenv("REDIS_ADDR") + if redisAddr == "" { + redisAddr = "127.0.0.1:6379" + } + + bm, err := cache.NewCache("redis", fmt.Sprintf(`{"conn": "%s"}`, redisAddr)) + if err != nil { + t.Error("init err") + } + timeoutDuration := 10 * time.Second + if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil { + t.Error("set Error", err) + } + if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { + t.Error("check err") + } + + time.Sleep(11 * time.Second) + + if res, _ := bm.IsExist(context.Background(), "astaxie"); res { + t.Error("check err") + } + if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil { + t.Error("set Error", err) + } + + val, _ := bm.Get(context.Background(), "astaxie") + if v, _ := redis.Int(val, err); v != 1 { + t.Error("get err") + } + + if err = bm.Incr(context.Background(), "astaxie"); err != nil { + t.Error("Incr Error", err) + } + val, _ = bm.Get(context.Background(), "astaxie") + if v, _ := redis.Int(val, err); v != 2 { + t.Error("get err") + } + + if err = bm.Decr(context.Background(), "astaxie"); err != nil { + t.Error("Decr Error", err) + } + + val, _ = bm.Get(context.Background(), "astaxie") + if v, _ := redis.Int(val, err); v != 1 { + t.Error("get err") + } + bm.Delete(context.Background(), "astaxie") + if res, _ := bm.IsExist(context.Background(), "astaxie"); res { + t.Error("delete err") + } + + // test string + if err = bm.Put(context.Background(), "astaxie", "author", timeoutDuration); err != nil { + t.Error("set Error", err) + } + if res, _ := bm.IsExist(context.Background(), "astaxie"); !res { + t.Error("check err") + } + + val, _ = bm.Get(context.Background(), "astaxie") + if v, _ := redis.String(val, err); v != "author" { + t.Error("get err") + } + + // test GetMulti + if err = bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration); err != nil { + t.Error("set Error", err) + } + if res, _ := bm.IsExist(context.Background(), "astaxie1"); !res { + t.Error("check err") + } + + vv, _ := bm.GetMulti(context.Background(), []string{"astaxie", "astaxie1"}) + if len(vv) != 2 { + t.Error("GetMulti ERROR") + } + if v, _ := redis.String(vv[0], nil); v != "author" { + t.Error("GetMulti ERROR") + } + if v, _ := redis.String(vv[1], nil); v != "author1" { + t.Error("GetMulti ERROR") + } + + // test clear all + if err = bm.ClearAll(context.Background()); err != nil { + t.Error("clear all err") + } +} + +func TestCache_Scan(t *testing.T) { + timeoutDuration := 10 * time.Second + + addr := os.Getenv("REDIS_ADDR") + if addr == "" { + addr = "127.0.0.1:6379" + } + + // init + bm, err := cache.NewCache("redis", fmt.Sprintf(`{"conn": "%s"}`, addr)) + if err != nil { + t.Error("init err") + } + // insert all + for i := 0; i < 100; i++ { + if err = bm.Put(context.Background(), fmt.Sprintf("astaxie%d", i), fmt.Sprintf("author%d", i), timeoutDuration); err != nil { + t.Error("set Error", err) + } + } + time.Sleep(time.Second) + // scan all for the first time + keys, err := bm.(*Cache).Scan(DefaultKey + ":*") + if err != nil { + t.Error("scan Error", err) + } + + assert.Equal(t, 100, len(keys), "scan all error") + + // clear all + if err = bm.ClearAll(context.Background()); err != nil { + t.Error("clear all err") + } + + // scan all for the second time + keys, err = bm.(*Cache).Scan(DefaultKey + ":*") + if err != nil { + t.Error("scan Error", err) + } + if len(keys) != 0 { + t.Error("scan all err") + } +} diff --git a/cache/ssdb/ssdb.go b/client/cache/ssdb/ssdb.go similarity index 70% rename from cache/ssdb/ssdb.go rename to client/cache/ssdb/ssdb.go index fa2ce04b..1acee861 100644 --- a/cache/ssdb/ssdb.go +++ b/client/cache/ssdb/ssdb.go @@ -1,6 +1,7 @@ package ssdb import ( + "context" "encoding/json" "errors" "strconv" @@ -9,7 +10,7 @@ import ( "github.com/ssdb/gossdb/ssdb" - "github.com/astaxie/beego/cache" + "github.com/astaxie/beego/client/cache" ) // Cache SSDB adapter @@ -18,35 +19,32 @@ type Cache struct { conninfo []string } -//NewSsdbCache create new ssdb adapter. +//NewSsdbCache creates new ssdb adapter. func NewSsdbCache() cache.Cache { return &Cache{} } -// Get get value from memcache. -func (rc *Cache) Get(key string) interface{} { +// Get gets a key's value from memcache. +func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) { if rc.conn == nil { if err := rc.connectInit(); err != nil { - return nil + return nil, nil } } value, err := rc.conn.Get(key) if err == nil { - return value + return value, nil } - return nil + return nil, nil } -// GetMulti get value from memcache. -func (rc *Cache) GetMulti(keys []string) []interface{} { +// GetMulti gets one or keys values from ssdb. +func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { size := len(keys) var values []interface{} if rc.conn == nil { if err := rc.connectInit(); err != nil { - for i := 0; i < size; i++ { - values = append(values, err) - } - return values + return values, err } } res, err := rc.conn.Do("multi_get", keys) @@ -55,15 +53,15 @@ func (rc *Cache) GetMulti(keys []string) []interface{} { for i := 1; i < resSize; i += 2 { values = append(values, res[i+1]) } - return values + return values, nil } for i := 0; i < size; i++ { values = append(values, err) } - return values + return values, nil } -// DelMulti get value from memcache. +// DelMulti deletes one or more keys from memcache func (rc *Cache) DelMulti(keys []string) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { @@ -74,14 +72,15 @@ func (rc *Cache) DelMulti(keys []string) error { return err } -// Put put value to memcache. only support string. -func (rc *Cache) Put(key string, value interface{}, timeout time.Duration) error { +// Put puts value into memcache. +// value: must be of type string +func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err } } - v, ok := value.(string) + v, ok := val.(string) if !ok { return errors.New("value must string") } @@ -102,8 +101,8 @@ func (rc *Cache) Put(key string, value interface{}, timeout time.Duration) error return errors.New("bad response") } -// Delete delete value in memcache. -func (rc *Cache) Delete(key string) error { +// Delete deletes a value in memcache. +func (rc *Cache) Delete(ctx context.Context, key string) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -113,8 +112,8 @@ func (rc *Cache) Delete(key string) error { return err } -// Incr increase counter. -func (rc *Cache) Incr(key string) error { +// Incr increases a key's counter. +func (rc *Cache) Incr(ctx context.Context, key string) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -124,8 +123,8 @@ func (rc *Cache) Incr(key string) error { return err } -// Decr decrease counter. -func (rc *Cache) Decr(key string) error { +// Decr decrements a key's counter. +func (rc *Cache) Decr(ctx context.Context, key string) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -135,26 +134,26 @@ func (rc *Cache) Decr(key string) error { return err } -// IsExist check value exists in memcache. -func (rc *Cache) IsExist(key string) bool { +// IsExist checks if a key exists in memcache. +func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) { if rc.conn == nil { if err := rc.connectInit(); err != nil { - return false + return false, err } } resp, err := rc.conn.Do("exists", key) if err != nil { - return false + return false, err } if len(resp) == 2 && resp[1] == "1" { - return true + return true, nil } - return false + return false, nil } -// ClearAll clear all cached in memcache. -func (rc *Cache) ClearAll() error { +// ClearAll clears all cached items in memcache. +func (rc *Cache) ClearAll(context.Context) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err @@ -195,9 +194,9 @@ func (rc *Cache) Scan(keyStart string, keyEnd string, limit int) ([]string, erro return resp, nil } -// StartAndGC start memcache adapter. -// config string is like {"conn":"connection info"}. -// if connecting error, return. +// StartAndGC starts the memcache adapter. +// config: must be in the format {"conn":"connection info"}. +// If an error occurs during connection, an error is returned func (rc *Cache) StartAndGC(config string) error { var cf map[string]string json.Unmarshal([]byte(config), &cf) diff --git a/client/cache/ssdb/ssdb_test.go b/client/cache/ssdb/ssdb_test.go new file mode 100644 index 00000000..cebaa975 --- /dev/null +++ b/client/cache/ssdb/ssdb_test.go @@ -0,0 +1,118 @@ +package ssdb + +import ( + "context" + "fmt" + "os" + "strconv" + "testing" + "time" + + "github.com/astaxie/beego/client/cache" +) + +func TestSsdbcacheCache(t *testing.T) { + + ssdbAddr := os.Getenv("SSDB_ADDR") + if ssdbAddr == "" { + ssdbAddr = "127.0.0.1:8888" + } + + ssdb, err := cache.NewCache("ssdb", fmt.Sprintf(`{"conn": "%s"}`, ssdbAddr)) + if err != nil { + t.Error("init err") + } + + // test put and exist + if res, _ := ssdb.IsExist(context.Background(), "ssdb"); res { + t.Error("check err") + } + timeoutDuration := 10 * time.Second + // timeoutDuration := -10*time.Second if timeoutDuration is negtive,it means permanent + if err = ssdb.Put(context.Background(), "ssdb", "ssdb", timeoutDuration); err != nil { + t.Error("set Error", err) + } + if res, _ := ssdb.IsExist(context.Background(), "ssdb"); !res { + t.Error("check err") + } + + // Get test done + if err = ssdb.Put(context.Background(), "ssdb", "ssdb", timeoutDuration); err != nil { + t.Error("set Error", err) + } + + if v, _ := ssdb.Get(context.Background(), "ssdb"); v != "ssdb" { + t.Error("get Error") + } + + // inc/dec test done + if err = ssdb.Put(context.Background(), "ssdb", "2", timeoutDuration); err != nil { + t.Error("set Error", err) + } + if err = ssdb.Incr(context.Background(), "ssdb"); err != nil { + t.Error("incr Error", err) + } + + val, _ := ssdb.Get(context.Background(), "ssdb") + if v, err := strconv.Atoi(val.(string)); err != nil || v != 3 { + t.Error("get err") + } + + if err = ssdb.Decr(context.Background(), "ssdb"); err != nil { + t.Error("decr error") + } + + // test del + if err = ssdb.Put(context.Background(), "ssdb", "3", timeoutDuration); err != nil { + t.Error("set Error", err) + } + + val, _ = ssdb.Get(context.Background(), "ssdb") + if v, err := strconv.Atoi(val.(string)); err != nil || v != 3 { + t.Error("get err") + } + if err := ssdb.Delete(context.Background(), "ssdb"); err == nil { + if e, _ := ssdb.IsExist(context.Background(), "ssdb"); e { + t.Error("delete err") + } + } + + // test string + if err = ssdb.Put(context.Background(), "ssdb", "ssdb", -10*time.Second); err != nil { + t.Error("set Error", err) + } + if res, _ := ssdb.IsExist(context.Background(), "ssdb"); !res { + t.Error("check err") + } + if v, _ := ssdb.Get(context.Background(), "ssdb"); v.(string) != "ssdb" { + t.Error("get err") + } + + // test GetMulti done + if err = ssdb.Put(context.Background(), "ssdb1", "ssdb1", -10*time.Second); err != nil { + t.Error("set Error", err) + } + if res, _ := ssdb.IsExist(context.Background(), "ssdb1"); !res { + t.Error("check err") + } + vv, _ := ssdb.GetMulti(context.Background(), []string{"ssdb", "ssdb1"}) + if len(vv) != 2 { + t.Error("getmulti error") + } + if vv[0].(string) != "ssdb" { + t.Error("getmulti error") + } + if vv[1].(string) != "ssdb1" { + t.Error("getmulti error") + } + + // test clear all done + if err = ssdb.ClearAll(context.Background()); err != nil { + t.Error("clear all err") + } + e1, _ := ssdb.IsExist(context.Background(), "ssdb") + e2, _ := ssdb.IsExist(context.Background(), "ssdb1") + if e1 || e2 { + t.Error("check err") + } +} diff --git a/httplib/README.md b/client/httplib/README.md similarity index 100% rename from httplib/README.md rename to client/httplib/README.md diff --git a/client/httplib/filter.go b/client/httplib/filter.go new file mode 100644 index 00000000..5daed64c --- /dev/null +++ b/client/httplib/filter.go @@ -0,0 +1,24 @@ +// Copyright 2020 beego +// +// Licensed 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 httplib + +import ( + "context" + "net/http" +) + +type FilterChain func(next Filter) Filter + +type Filter func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) diff --git a/client/httplib/filter/opentracing/filter.go b/client/httplib/filter/opentracing/filter.go new file mode 100644 index 00000000..765a82a9 --- /dev/null +++ b/client/httplib/filter/opentracing/filter.go @@ -0,0 +1,71 @@ +// Copyright 2020 beego +// +// Licensed 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 opentracing + +import ( + "context" + "net/http" + + "github.com/astaxie/beego/client/httplib" + logKit "github.com/go-kit/kit/log" + opentracingKit "github.com/go-kit/kit/tracing/opentracing" + "github.com/opentracing/opentracing-go" +) + +type FilterChainBuilder struct { + // CustomSpanFunc users are able to custom their span + CustomSpanFunc func(span opentracing.Span, ctx context.Context, + req *httplib.BeegoHTTPRequest, resp *http.Response, err error) +} + +func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filter { + + return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { + + method := req.GetRequest().Method + + operationName := method + "#" + req.GetRequest().URL.String() + span, spanCtx := opentracing.StartSpanFromContext(ctx, operationName) + defer span.Finish() + + inject := opentracingKit.ContextToHTTP(opentracing.GlobalTracer(), logKit.NewNopLogger()) + inject(spanCtx, req.GetRequest()) + resp, err := next(spanCtx, req) + + if resp != nil { + span.SetTag("http.status_code", resp.StatusCode) + } + span.SetTag("http.method", method) + span.SetTag("peer.hostname", req.GetRequest().URL.Host) + span.SetTag("http.url", req.GetRequest().URL.String()) + span.SetTag("http.scheme", req.GetRequest().URL.Scheme) + span.SetTag("span.kind", "client") + span.SetTag("component", "beego") + if err != nil { + span.SetTag("error", true) + span.SetTag("message", err.Error()) + } else if resp != nil && !(resp.StatusCode < 300 && resp.StatusCode >= 200) { + span.SetTag("error", true) + } + + span.SetTag("peer.address", req.GetRequest().RemoteAddr) + span.SetTag("http.proto", req.GetRequest().Proto) + + if builder.CustomSpanFunc != nil { + builder.CustomSpanFunc(span, ctx, req, resp, err) + } + return resp, err + } +} diff --git a/client/httplib/filter/opentracing/filter_test.go b/client/httplib/filter/opentracing/filter_test.go new file mode 100644 index 00000000..7281f93f --- /dev/null +++ b/client/httplib/filter/opentracing/filter_test.go @@ -0,0 +1,42 @@ +// Copyright 2020 beego +// +// Licensed 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 opentracing + +import ( + "context" + "errors" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/client/httplib" +) + +func TestFilterChainBuilder_FilterChain(t *testing.T) { + next := func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { + time.Sleep(100 * time.Millisecond) + return &http.Response{ + StatusCode: 404, + }, errors.New("hello") + } + builder := &FilterChainBuilder{} + filter := builder.FilterChain(next) + req := httplib.Get("https://github.com/notifications?query=repo%3Aastaxie%2Fbeego") + resp, err := filter(context.Background(), req) + assert.NotNil(t, resp) + assert.NotNil(t, err) +} diff --git a/client/httplib/filter/prometheus/filter.go b/client/httplib/filter/prometheus/filter.go new file mode 100644 index 00000000..ce88b70e --- /dev/null +++ b/client/httplib/filter/prometheus/filter.go @@ -0,0 +1,77 @@ +// Copyright 2020 beego +// +// Licensed 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 prometheus + +import ( + "context" + "net/http" + "strconv" + "time" + + "github.com/prometheus/client_golang/prometheus" + + "github.com/astaxie/beego/client/httplib" +) + +type FilterChainBuilder struct { + summaryVec prometheus.ObserverVec + AppName string + ServerName string + RunMode string +} + +func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filter { + + builder.summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{ + Name: "beego", + Subsystem: "remote_http_request", + ConstLabels: map[string]string{ + "server": builder.ServerName, + "env": builder.RunMode, + "appname": builder.AppName, + }, + Help: "The statics info for remote http requests", + }, []string{"proto", "scheme", "method", "host", "path", "status", "duration", "isError"}) + + return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { + startTime := time.Now() + resp, err := next(ctx, req) + endTime := time.Now() + go builder.report(startTime, endTime, ctx, req, resp, err) + return resp, err + } +} + +func (builder *FilterChainBuilder) report(startTime time.Time, endTime time.Time, + ctx context.Context, req *httplib.BeegoHTTPRequest, resp *http.Response, err error) { + + proto := req.GetRequest().Proto + + scheme := req.GetRequest().URL.Scheme + method := req.GetRequest().Method + + host := req.GetRequest().URL.Host + path := req.GetRequest().URL.Path + + status := -1 + if resp != nil { + status = resp.StatusCode + } + + dur := int(endTime.Sub(startTime) / time.Millisecond) + + builder.summaryVec.WithLabelValues(proto, scheme, method, host, path, + strconv.Itoa(status), strconv.Itoa(dur), strconv.FormatBool(err == nil)) +} diff --git a/client/httplib/filter/prometheus/filter_test.go b/client/httplib/filter/prometheus/filter_test.go new file mode 100644 index 00000000..46edc3d2 --- /dev/null +++ b/client/httplib/filter/prometheus/filter_test.go @@ -0,0 +1,41 @@ +// Copyright 2020 beego +// +// Licensed 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 prometheus + +import ( + "context" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/client/httplib" +) + +func TestFilterChainBuilder_FilterChain(t *testing.T) { + next := func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { + time.Sleep(100 * time.Millisecond) + return &http.Response{ + StatusCode: 404, + }, nil + } + builder := &FilterChainBuilder{} + filter := builder.FilterChain(next) + req := httplib.Get("https://github.com/notifications?query=repo%3Aastaxie%2Fbeego") + resp, err := filter(context.Background(), req) + assert.NotNil(t, resp) + assert.Nil(t, err) +} diff --git a/httplib/httplib.go b/client/httplib/httplib.go similarity index 85% rename from httplib/httplib.go rename to client/httplib/httplib.go index e094a6a6..f8ab80a1 100644 --- a/httplib/httplib.go +++ b/client/httplib/httplib.go @@ -34,6 +34,7 @@ package httplib import ( "bytes" "compress/gzip" + "context" "crypto/tls" "encoding/json" "encoding/xml" @@ -66,6 +67,11 @@ var defaultSetting = BeegoHTTPSettings{ var defaultCookieJar http.CookieJar var settingMutex sync.Mutex +// it will be the last filter and execute request.Do +var doRequestFilter = func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) { + return req.doRequest(ctx) +} + // createDefaultCookie creates a global cookiejar to store cookies. func createDefaultCookie() { settingMutex.Lock() @@ -73,14 +79,14 @@ func createDefaultCookie() { defaultCookieJar, _ = cookiejar.New(nil) } -// SetDefaultSetting Overwrite default settings +// SetDefaultSetting overwrites default settings func SetDefaultSetting(setting BeegoHTTPSettings) { settingMutex.Lock() defer settingMutex.Unlock() defaultSetting = setting } -// NewBeegoRequest return *BeegoHttpRequest with specific method +// NewBeegoRequest returns *BeegoHttpRequest with specific method func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest { var resp http.Response u, err := url.Parse(rawurl) @@ -144,9 +150,11 @@ type BeegoHTTPSettings struct { Gzip bool DumpBody bool Retries int // if set to -1 means will retry forever + RetryDelay time.Duration + FilterChains []FilterChain } -// BeegoHTTPRequest provides more useful methods for requesting one url than http.Request. +// BeegoHTTPRequest provides more useful methods than http.Request for requesting a url. type BeegoHTTPRequest struct { url string req *http.Request @@ -158,12 +166,12 @@ type BeegoHTTPRequest struct { dump []byte } -// GetRequest return the request object +// GetRequest returns the request object func (b *BeegoHTTPRequest) GetRequest() *http.Request { return b.req } -// Setting Change request settings +// Setting changes request settings func (b *BeegoHTTPRequest) Setting(setting BeegoHTTPSettings) *BeegoHTTPRequest { b.setting = setting return b @@ -194,21 +202,27 @@ func (b *BeegoHTTPRequest) Debug(isdebug bool) *BeegoHTTPRequest { } // Retries sets Retries times. -// default is 0 means no retried. -// -1 means retried forever. -// others means retried times. +// default is 0 (never retry) +// -1 retry indefinitely (forever) +// Other numbers specify the exact retry amount func (b *BeegoHTTPRequest) Retries(times int) *BeegoHTTPRequest { b.setting.Retries = times return b } -// DumpBody setting whether need to Dump the Body. +// RetryDelay sets the time to sleep between reconnection attempts +func (b *BeegoHTTPRequest) RetryDelay(delay time.Duration) *BeegoHTTPRequest { + b.setting.RetryDelay = delay + return b +} + +// DumpBody sets the DumbBody field func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest { b.setting.DumpBody = isdump return b } -// DumpRequest return the DumpRequest +// DumpRequest returns the DumpRequest func (b *BeegoHTTPRequest) DumpRequest() []byte { return b.dump } @@ -220,13 +234,13 @@ func (b *BeegoHTTPRequest) SetTimeout(connectTimeout, readWriteTimeout time.Dura return b } -// SetTLSClientConfig sets tls connection configurations if visiting https url. +// SetTLSClientConfig sets TLS connection configuration if visiting HTTPS url. func (b *BeegoHTTPRequest) SetTLSClientConfig(config *tls.Config) *BeegoHTTPRequest { b.setting.TLSClientConfig = config return b } -// Header add header item string in request. +// Header adds header item string in request. func (b *BeegoHTTPRequest) Header(key, value string) *BeegoHTTPRequest { b.req.Header.Set(key, value) return b @@ -238,7 +252,7 @@ func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest { return b } -// SetProtocolVersion Set the protocol version for incoming requests. +// SetProtocolVersion sets the protocol version for incoming requests. // Client requests always use HTTP/1.1. func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest { if len(vers) == 0 { @@ -255,19 +269,19 @@ func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest { return b } -// SetCookie add cookie into request. +// SetCookie adds a cookie to the request. func (b *BeegoHTTPRequest) SetCookie(cookie *http.Cookie) *BeegoHTTPRequest { b.req.Header.Add("Cookie", cookie.String()) return b } -// SetTransport set the setting transport +// SetTransport sets the transport field func (b *BeegoHTTPRequest) SetTransport(transport http.RoundTripper) *BeegoHTTPRequest { b.setting.Transport = transport return b } -// SetProxy set the http proxy +// SetProxy sets the HTTP proxy // example: // // func(req *http.Request) (*url.URL, error) { @@ -288,6 +302,18 @@ func (b *BeegoHTTPRequest) SetCheckRedirect(redirect func(req *http.Request, via return b } +// SetFilters will use the filter as the invocation filters +func (b *BeegoHTTPRequest) SetFilters(fcs ...FilterChain) *BeegoHTTPRequest { + b.setting.FilterChains = fcs + return b +} + +// AddFilters adds filter +func (b *BeegoHTTPRequest) AddFilters(fcs ...FilterChain) *BeegoHTTPRequest { + b.setting.FilterChains = append(b.setting.FilterChains, fcs...) + return b +} + // Param adds query param in to request. // params build query string as ?key1=value1&key2=value2... func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest { @@ -299,14 +325,14 @@ func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest { return b } -// PostFile add a post file to the request +// PostFile adds a post file to the request func (b *BeegoHTTPRequest) PostFile(formname, filename string) *BeegoHTTPRequest { b.files[formname] = filename return b } // Body adds request raw body. -// it supports string and []byte. +// Supports string and []byte. func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest { switch t := data.(type) { case string: @@ -321,7 +347,7 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest { return b } -// XMLBody adds request raw body encoding by XML. +// XMLBody adds the request raw body encoded in XML. func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) { if b.req.Body == nil && obj != nil { byts, err := xml.Marshal(obj) @@ -335,7 +361,7 @@ func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) { return b, nil } -// YAMLBody adds request raw body encoding by YAML. +// YAMLBody adds the request raw body encoded in YAML. func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error) { if b.req.Body == nil && obj != nil { byts, err := yaml.Marshal(obj) @@ -349,7 +375,7 @@ func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error) return b, nil } -// JSONBody adds request raw body encoding by JSON. +// JSONBody adds the request raw body encoded in JSON. func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) { if b.req.Body == nil && obj != nil { byts, err := json.Marshal(obj) @@ -390,7 +416,7 @@ func (b *BeegoHTTPRequest) buildURL(paramBody string) { if err != nil { log.Println("Httplib:", err) } - //iocopy + // iocopy _, err = io.Copy(fileWriter, fh) fh.Close() if err != nil { @@ -431,8 +457,23 @@ func (b *BeegoHTTPRequest) getResponse() (*http.Response, error) { return resp, nil } -// DoRequest will do the client.Do +// DoRequest executes client.Do func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) { + return b.DoRequestWithCtx(context.Background()) +} + +func (b *BeegoHTTPRequest) DoRequestWithCtx(ctx context.Context) (resp *http.Response, err error) { + + root := doRequestFilter + if len(b.setting.FilterChains) > 0 { + for i := len(b.setting.FilterChains) - 1; i >= 0; i-- { + root = b.setting.FilterChains[i](root) + } + } + return root(ctx, b) +} + +func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response, err error) { var paramBody string if len(b.params) > 0 { var buf bytes.Buffer @@ -512,17 +553,19 @@ func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) { // retries default value is 0, it will run once. // retries equal to -1, it will run forever until success // retries is setted, it will retries fixed times. + // Sleeps for a 400ms inbetween calls to reduce spam for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ { resp, err = client.Do(b.req) if err == nil { break } + time.Sleep(b.setting.RetryDelay) } return resp, err } // String returns the body string in response. -// it calls Response inner. +// Calls Response inner. func (b *BeegoHTTPRequest) String() (string, error) { data, err := b.Bytes() if err != nil { @@ -533,7 +576,7 @@ func (b *BeegoHTTPRequest) String() (string, error) { } // Bytes returns the body []byte in response. -// it calls Response inner. +// Calls Response inner. func (b *BeegoHTTPRequest) Bytes() ([]byte, error) { if b.body != nil { return b.body, nil @@ -559,7 +602,7 @@ func (b *BeegoHTTPRequest) Bytes() ([]byte, error) { } // ToFile saves the body data in response to one file. -// it calls Response inner. +// Calls Response inner. func (b *BeegoHTTPRequest) ToFile(filename string) error { resp, err := b.getResponse() if err != nil { @@ -582,7 +625,7 @@ func (b *BeegoHTTPRequest) ToFile(filename string) error { return err } -//Check that the file directory exists, there is no automatically created +// Check if the file directory exists. If it doesn't then it's created func pathExistAndMkdir(filename string) (err error) { filename = path.Dir(filename) _, err = os.Stat(filename) @@ -598,8 +641,8 @@ func pathExistAndMkdir(filename string) (err error) { return err } -// ToJSON returns the map that marshals from the body bytes as json in response . -// it calls Response inner. +// ToJSON returns the map that marshals from the body bytes as json in response. +// Calls Response inner. func (b *BeegoHTTPRequest) ToJSON(v interface{}) error { data, err := b.Bytes() if err != nil { @@ -609,7 +652,7 @@ func (b *BeegoHTTPRequest) ToJSON(v interface{}) error { } // ToXML returns the map that marshals from the body bytes as xml in response . -// it calls Response inner. +// Calls Response inner. func (b *BeegoHTTPRequest) ToXML(v interface{}) error { data, err := b.Bytes() if err != nil { @@ -619,7 +662,7 @@ func (b *BeegoHTTPRequest) ToXML(v interface{}) error { } // ToYAML returns the map that marshals from the body bytes as yaml in response . -// it calls Response inner. +// Calls Response inner. func (b *BeegoHTTPRequest) ToYAML(v interface{}) error { data, err := b.Bytes() if err != nil { @@ -628,7 +671,7 @@ func (b *BeegoHTTPRequest) ToYAML(v interface{}) error { return yaml.Unmarshal(data, v) } -// Response executes request client gets response mannually. +// Response executes request client gets response manually. func (b *BeegoHTTPRequest) Response() (*http.Response, error) { return b.getResponse() } diff --git a/client/httplib/httplib_test.go b/client/httplib/httplib_test.go new file mode 100644 index 00000000..88935715 --- /dev/null +++ b/client/httplib/httplib_test.go @@ -0,0 +1,302 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 httplib + +import ( + "context" + "errors" + "io/ioutil" + "net" + "net/http" + "os" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestResponse(t *testing.T) { + req := Get("http://httpbin.org/get") + resp, err := req.Response() + if err != nil { + t.Fatal(err) + } + t.Log(resp) +} + +func TestDoRequest(t *testing.T) { + req := Get("https://goolnk.com/33BD2j") + retryAmount := 1 + req.Retries(1) + req.RetryDelay(1400 * time.Millisecond) + retryDelay := 1400 * time.Millisecond + + req.setting.CheckRedirect = func(redirectReq *http.Request, redirectVia []*http.Request) error { + return errors.New("Redirect triggered") + } + + startTime := time.Now().UnixNano() / int64(time.Millisecond) + + _, err := req.Response() + if err == nil { + t.Fatal("Response should have yielded an error") + } + + endTime := time.Now().UnixNano() / int64(time.Millisecond) + elapsedTime := endTime - startTime + delayedTime := int64(retryAmount) * retryDelay.Milliseconds() + + if elapsedTime < delayedTime { + t.Errorf("Not enough retries. Took %dms. Delay was meant to take %dms", elapsedTime, delayedTime) + } + +} + +func TestGet(t *testing.T) { + req := Get("http://httpbin.org/get") + b, err := req.Bytes() + if err != nil { + t.Fatal(err) + } + t.Log(b) + + s, err := req.String() + if err != nil { + t.Fatal(err) + } + t.Log(s) + + if string(b) != s { + t.Fatal("request data not match") + } +} + +func TestSimplePost(t *testing.T) { + v := "smallfish" + req := Post("http://httpbin.org/post") + req.Param("username", v) + + str, err := req.String() + if err != nil { + t.Fatal(err) + } + t.Log(str) + + n := strings.Index(str, v) + if n == -1 { + t.Fatal(v + " not found in post") + } +} + +//func TestPostFile(t *testing.T) { +// v := "smallfish" +// req := Post("http://httpbin.org/post") +// req.Debug(true) +// req.Param("username", v) +// req.PostFile("uploadfile", "httplib_test.go") + +// str, err := req.String() +// if err != nil { +// t.Fatal(err) +// } +// t.Log(str) + +// n := strings.Index(str, v) +// if n == -1 { +// t.Fatal(v + " not found in post") +// } +//} + +func TestSimplePut(t *testing.T) { + str, err := Put("http://httpbin.org/put").String() + if err != nil { + t.Fatal(err) + } + t.Log(str) +} + +func TestSimpleDelete(t *testing.T) { + str, err := Delete("http://httpbin.org/delete").String() + if err != nil { + t.Fatal(err) + } + t.Log(str) +} + +func TestSimpleDeleteParam(t *testing.T) { + str, err := Delete("http://httpbin.org/delete").Param("key", "val").String() + if err != nil { + t.Fatal(err) + } + t.Log(str) +} + +func TestWithCookie(t *testing.T) { + v := "smallfish" + str, err := Get("http://httpbin.org/cookies/set?k1=" + v).SetEnableCookie(true).String() + if err != nil { + t.Fatal(err) + } + t.Log(str) + + str, err = Get("http://httpbin.org/cookies").SetEnableCookie(true).String() + if err != nil { + t.Fatal(err) + } + t.Log(str) + + n := strings.Index(str, v) + if n == -1 { + t.Fatal(v + " not found in cookie") + } +} + +func TestWithBasicAuth(t *testing.T) { + str, err := Get("http://httpbin.org/basic-auth/user/passwd").SetBasicAuth("user", "passwd").String() + if err != nil { + t.Fatal(err) + } + t.Log(str) + n := strings.Index(str, "authenticated") + if n == -1 { + t.Fatal("authenticated not found in response") + } +} + +func TestWithUserAgent(t *testing.T) { + v := "beego" + str, err := Get("http://httpbin.org/headers").SetUserAgent(v).String() + if err != nil { + t.Fatal(err) + } + t.Log(str) + + n := strings.Index(str, v) + if n == -1 { + t.Fatal(v + " not found in user-agent") + } +} + +func TestWithSetting(t *testing.T) { + v := "beego" + var setting BeegoHTTPSettings + setting.EnableCookie = true + setting.UserAgent = v + setting.Transport = &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + }).DialContext, + MaxIdleConns: 50, + IdleConnTimeout: 90 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + setting.ReadWriteTimeout = 5 * time.Second + SetDefaultSetting(setting) + + str, err := Get("http://httpbin.org/get").String() + if err != nil { + t.Fatal(err) + } + t.Log(str) + + n := strings.Index(str, v) + if n == -1 { + t.Fatal(v + " not found in user-agent") + } +} + +func TestToJson(t *testing.T) { + req := Get("http://httpbin.org/ip") + resp, err := req.Response() + if err != nil { + t.Fatal(err) + } + t.Log(resp) + + // httpbin will return http remote addr + type IP struct { + Origin string `json:"origin"` + } + var ip IP + err = req.ToJSON(&ip) + if err != nil { + t.Fatal(err) + } + t.Log(ip.Origin) + ips := strings.Split(ip.Origin, ",") + if len(ips) == 0 { + t.Fatal("response is not valid ip") + } + for i := range ips { + if net.ParseIP(strings.TrimSpace(ips[i])).To4() == nil { + t.Fatal("response is not valid ip") + } + } + +} + +func TestToFile(t *testing.T) { + f := "beego_testfile" + req := Get("http://httpbin.org/ip") + err := req.ToFile(f) + if err != nil { + t.Fatal(err) + } + defer os.Remove(f) + b, err := ioutil.ReadFile(f) + if n := strings.Index(string(b), "origin"); n == -1 { + t.Fatal(err) + } +} + +func TestToFileDir(t *testing.T) { + f := "./files/beego_testfile" + req := Get("http://httpbin.org/ip") + err := req.ToFile(f) + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll("./files") + b, err := ioutil.ReadFile(f) + if n := strings.Index(string(b), "origin"); n == -1 { + t.Fatal(err) + } +} + +func TestHeader(t *testing.T) { + req := Get("http://httpbin.org/headers") + req.Header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36") + str, err := req.String() + if err != nil { + t.Fatal(err) + } + t.Log(str) +} + +// TestAddFilter make sure that AddFilters only work for the specific request +func TestAddFilter(t *testing.T) { + req := Get("http://beego.me") + req.AddFilters(func(next Filter) Filter { + return func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) { + return next(ctx, req) + } + }) + + r := Get("http://beego.me") + assert.Equal(t, 1, len(req.setting.FilterChains)-len(r.setting.FilterChains)) +} diff --git a/testing/client.go b/client/httplib/testing/client.go similarity index 88% rename from testing/client.go rename to client/httplib/testing/client.go index c3737e9c..34e49f2d 100644 --- a/testing/client.go +++ b/client/httplib/testing/client.go @@ -15,8 +15,7 @@ package testing import ( - "github.com/astaxie/beego/config" - "github.com/astaxie/beego/httplib" + "github.com/astaxie/beego/client/httplib" ) var port = "" @@ -27,13 +26,13 @@ type TestHTTPRequest struct { httplib.BeegoHTTPRequest } +func SetTestingPort(p string) { + port = p +} + func getPort() string { if port == "" { - config, err := config.NewConfig("ini", "../conf/app.conf") - if err != nil { - return "8080" - } - port = config.String("httpport") + port = "8080" return port } return port diff --git a/orm/README.md b/client/orm/README.md similarity index 100% rename from orm/README.md rename to client/orm/README.md diff --git a/orm/cmd.go b/client/orm/cmd.go similarity index 85% rename from orm/cmd.go rename to client/orm/cmd.go index 0ff4dc40..b0661971 100644 --- a/orm/cmd.go +++ b/client/orm/cmd.go @@ -46,7 +46,7 @@ func printHelp(errs ...string) { os.Exit(2) } -// RunCommand listen for orm command and then run it if command arguments passed. +// RunCommand listens for orm command and runs if command arguments have been passed. func RunCommand() { if len(os.Args) < 2 || os.Args[1] != "orm" { return @@ -83,7 +83,7 @@ type commandSyncDb struct { rtOnError bool } -// parse orm command line arguments. +// Parse the orm command line arguments. func (d *commandSyncDb) Parse(args []string) { var name string @@ -96,16 +96,20 @@ func (d *commandSyncDb) Parse(args []string) { d.al = getDbAlias(name) } -// run orm line command. +// Run orm line command. func (d *commandSyncDb) Run() error { var drops []string + var err error if d.force { - drops = getDbDropSQL(d.al) + drops, err = modelCache.getDbDropSQL(d.al) + if err != nil { + return err + } } db := d.al.DB - if d.force { + if d.force && len(drops) > 0 { for i, mi := range modelCache.allOrdered() { query := drops[i] if !d.noInfo { @@ -124,7 +128,10 @@ func (d *commandSyncDb) Run() error { } } - sqls, indexes := getDbCreateSQL(d.al) + createQueries, indexes, err := modelCache.getDbCreateSQL(d.al) + if err != nil { + return err + } tables, err := d.al.DbBaser.GetTables(db) if err != nil { @@ -135,6 +142,12 @@ func (d *commandSyncDb) Run() error { } for i, mi := range modelCache.allOrdered() { + + if !isApplicableTableForDB(mi.addrField, d.al.Name) { + fmt.Printf("table `%s` is not applicable to database '%s'\n", mi.table, d.al.Name) + continue + } + if tables[mi.table] { if !d.noInfo { fmt.Printf("table `%s` already exists, skip\n", mi.table) @@ -201,7 +214,7 @@ func (d *commandSyncDb) Run() error { fmt.Printf("create table `%s` \n", mi.table) } - queries := []string{sqls[i]} + queries := []string{createQueries[i]} for _, idx := range indexes[mi.table] { queries = append(queries, idx.SQL) } @@ -232,7 +245,7 @@ type commandSQLAll struct { al *alias } -// parse orm command line arguments. +// Parse orm command line arguments. func (d *commandSQLAll) Parse(args []string) { var name string @@ -243,12 +256,15 @@ func (d *commandSQLAll) Parse(args []string) { d.al = getDbAlias(name) } -// run orm line command. +// Run orm line command. func (d *commandSQLAll) Run() error { - sqls, indexes := getDbCreateSQL(d.al) + createQueries, indexes, err := modelCache.getDbCreateSQL(d.al) + if err != nil { + return err + } var all []string for i, mi := range modelCache.allOrdered() { - queries := []string{sqls[i]} + queries := []string{createQueries[i]} for _, idx := range indexes[mi.table] { queries = append(queries, idx.SQL) } @@ -266,9 +282,9 @@ func init() { } // RunSyncdb run syncdb command line. -// name means table's alias name. default is "default". -// force means run next sql if the current is error. -// verbose means show all info when running command or not. +// name: Table's alias name (default is "default") +// force: Run the next sql command even if the current gave an error +// verbose: Print all information, useful for debugging func RunSyncdb(name string, force bool, verbose bool) error { BootStrap() diff --git a/client/orm/cmd_utils.go b/client/orm/cmd_utils.go new file mode 100644 index 00000000..8d6c0c33 --- /dev/null +++ b/client/orm/cmd_utils.go @@ -0,0 +1,171 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "fmt" + "strings" +) + +type dbIndex struct { + Table string + Name string + SQL string +} + +// get database column type string. +func getColumnTyp(al *alias, fi *fieldInfo) (col string) { + T := al.DbBaser.DbTypes() + fieldType := fi.fieldType + fieldSize := fi.size + +checkColumn: + switch fieldType { + case TypeBooleanField: + col = T["bool"] + case TypeVarCharField: + if al.Driver == DRPostgres && fi.toText { + col = T["string-text"] + } else { + col = fmt.Sprintf(T["string"], fieldSize) + } + case TypeCharField: + col = fmt.Sprintf(T["string-char"], fieldSize) + case TypeTextField: + col = T["string-text"] + case TypeTimeField: + col = T["time.Time-clock"] + case TypeDateField: + col = T["time.Time-date"] + case TypeDateTimeField: + // the precision of sqlite is not implemented + if al.Driver == 2 || fi.timePrecision == nil { + col = T["time.Time"] + } else { + s := T["time.Time-precision"] + col = fmt.Sprintf(s, *fi.timePrecision) + } + + case TypeBitField: + col = T["int8"] + case TypeSmallIntegerField: + col = T["int16"] + case TypeIntegerField: + col = T["int32"] + case TypeBigIntegerField: + if al.Driver == DRSqlite { + fieldType = TypeIntegerField + goto checkColumn + } + col = T["int64"] + case TypePositiveBitField: + col = T["uint8"] + case TypePositiveSmallIntegerField: + col = T["uint16"] + case TypePositiveIntegerField: + col = T["uint32"] + case TypePositiveBigIntegerField: + col = T["uint64"] + case TypeFloatField: + col = T["float64"] + case TypeDecimalField: + s := T["float64-decimal"] + if !strings.Contains(s, "%d") { + col = s + } else { + col = fmt.Sprintf(s, fi.digits, fi.decimals) + } + case TypeJSONField: + if al.Driver != DRPostgres { + fieldType = TypeVarCharField + goto checkColumn + } + col = T["json"] + case TypeJsonbField: + if al.Driver != DRPostgres { + fieldType = TypeVarCharField + goto checkColumn + } + col = T["jsonb"] + case RelForeignKey, RelOneToOne: + fieldType = fi.relModelInfo.fields.pk.fieldType + fieldSize = fi.relModelInfo.fields.pk.size + goto checkColumn + } + + return +} + +// create alter sql string. +func getColumnAddQuery(al *alias, fi *fieldInfo) string { + Q := al.DbBaser.TableQuote() + typ := getColumnTyp(al, fi) + + if !fi.null { + typ += " " + "NOT NULL" + } + + return fmt.Sprintf("ALTER TABLE %s%s%s ADD COLUMN %s%s%s %s %s", + Q, fi.mi.table, Q, + Q, fi.column, Q, + typ, getColumnDefault(fi), + ) +} + +// Get string value for the attribute "DEFAULT" for the CREATE, ALTER commands +func getColumnDefault(fi *fieldInfo) string { + var ( + v, t, d string + ) + + // Skip default attribute if field is in relations + if fi.rel || fi.reverse { + return v + } + + t = " DEFAULT '%s' " + + // These defaults will be useful if there no config value orm:"default" and NOT NULL is on + switch fi.fieldType { + case TypeTimeField, TypeDateField, TypeDateTimeField, TypeTextField: + return v + + case TypeBitField, TypeSmallIntegerField, TypeIntegerField, + TypeBigIntegerField, TypePositiveBitField, TypePositiveSmallIntegerField, + TypePositiveIntegerField, TypePositiveBigIntegerField, TypeFloatField, + TypeDecimalField: + t = " DEFAULT %s " + d = "0" + case TypeBooleanField: + t = " DEFAULT %s " + d = "FALSE" + case TypeJSONField, TypeJsonbField: + d = "{}" + } + + if fi.colDefault { + if !fi.initial.Exist() { + v = fmt.Sprintf(t, "") + } else { + v = fmt.Sprintf(t, fi.initial.String()) + } + } else { + if !fi.null { + v = fmt.Sprintf(t, d) + } + } + + return v +} diff --git a/orm/db.go b/client/orm/db.go similarity index 94% rename from orm/db.go rename to client/orm/db.go index 9a1827e8..b103d218 100644 --- a/orm/db.go +++ b/client/orm/db.go @@ -21,6 +21,8 @@ import ( "reflect" "strings" "time" + + "github.com/astaxie/beego/client/orm/hints" ) const ( @@ -36,10 +38,11 @@ var ( var ( operators = map[string]bool{ - "exact": true, - "iexact": true, - "contains": true, - "icontains": true, + "exact": true, + "iexact": true, + "strictexact": true, + "contains": true, + "icontains": true, // "regex": true, // "iregex": true, "gt": true, @@ -484,7 +487,14 @@ func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []s if isMulti { return res.RowsAffected() } - return res.LastInsertId() + + lastInsertId, err := res.LastInsertId() + if err != nil { + DebugLog.Println(ErrLastInsertIdUnavailable, ':', err) + return lastInsertId, ErrLastInsertIdUnavailable + } else { + return lastInsertId, nil + } } return 0, err } @@ -585,7 +595,14 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a if isMulti { return res.RowsAffected() } - return res.LastInsertId() + + lastInsertId, err := res.LastInsertId() + if err != nil { + DebugLog.Println(ErrLastInsertIdUnavailable, ':', err) + return lastInsertId, ErrLastInsertIdUnavailable + } else { + return lastInsertId, nil + } } return 0, err } @@ -738,8 +755,10 @@ func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con } tables := newDbTables(mi, d.ins) + var specifyIndexes string if qs != nil { tables.parseRelated(qs.related, qs.relDepth) + specifyIndexes = tables.getIndexSql(mi.table, qs.useIndex, qs.indexes) } where, args := tables.getCondSQL(cond, false, tz) @@ -790,9 +809,12 @@ func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con sets := strings.Join(cols, ", ") + " " if d.ins.SupportUpdateJoin() { - query = fmt.Sprintf("UPDATE %s%s%s T0 %sSET %s%s", Q, mi.table, Q, join, sets, where) + query = fmt.Sprintf("UPDATE %s%s%s T0 %s%sSET %s%s", Q, mi.table, Q, specifyIndexes, join, sets, where) } else { - supQuery := fmt.Sprintf("SELECT T0.%s%s%s FROM %s%s%s T0 %s%s", Q, mi.fields.pk.column, Q, Q, mi.table, Q, join, where) + supQuery := fmt.Sprintf("SELECT T0.%s%s%s FROM %s%s%s T0 %s%s%s", + Q, mi.fields.pk.column, Q, + Q, mi.table, Q, + specifyIndexes, join, where) query = fmt.Sprintf("UPDATE %s%s%s SET %sWHERE %s%s%s IN ( %s )", Q, mi.table, Q, sets, Q, mi.fields.pk.column, Q, supQuery) } @@ -843,8 +865,10 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con tables := newDbTables(mi, d.ins) tables.skipEnd = true + var specifyIndexes string if qs != nil { tables.parseRelated(qs.related, qs.relDepth) + specifyIndexes = tables.getIndexSql(mi.table, qs.useIndex, qs.indexes) } if cond == nil || cond.IsEmpty() { @@ -857,7 +881,7 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con join := tables.getJoinSQL() cols := fmt.Sprintf("T0.%s%s%s", Q, mi.fields.pk.column, Q) - query := fmt.Sprintf("SELECT %s FROM %s%s%s T0 %s%s", cols, Q, mi.table, Q, join, where) + query := fmt.Sprintf("SELECT %s FROM %s%s%s T0 %s%s%s", cols, Q, mi.table, Q, specifyIndexes, join, where) d.ins.ReplaceMarks(&query) @@ -1002,6 +1026,7 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi orderBy := tables.getOrderSQL(qs.orders) limit := tables.getLimitSQL(mi, offset, rlimit) join := tables.getJoinSQL() + specifyIndexes := tables.getIndexSql(mi.table, qs.useIndex, qs.indexes) for _, tbl := range tables.tables { if tbl.sel { @@ -1015,9 +1040,11 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi if qs.distinct { sqlSelect += " DISTINCT" } - query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s", sqlSelect, sels, Q, mi.table, Q, join, where, groupBy, orderBy, limit) + query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s%s", + sqlSelect, sels, Q, mi.table, Q, + specifyIndexes, join, where, groupBy, orderBy, limit) - if qs.forupdate { + if qs.forUpdate { query += " FOR UPDATE" } @@ -1153,10 +1180,13 @@ func (d *dbBase) Count(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition groupBy := tables.getGroupSQL(qs.groups) tables.getOrderSQL(qs.orders) join := tables.getJoinSQL() + specifyIndexes := tables.getIndexSql(mi.table, qs.useIndex, qs.indexes) Q := d.ins.TableQuote() - query := fmt.Sprintf("SELECT COUNT(*) FROM %s%s%s T0 %s%s%s", Q, mi.table, Q, join, where, groupBy) + query := fmt.Sprintf("SELECT COUNT(*) FROM %s%s%s T0 %s%s%s%s", + Q, mi.table, Q, + specifyIndexes, join, where, groupBy) if groupBy != "" { query = fmt.Sprintf("SELECT COUNT(*) FROM (%s) AS T", query) @@ -1326,7 +1356,14 @@ setValue: t time.Time err error ) - if len(s) >= 19 { + + if fi.timePrecision != nil && len(s) >= (20+*fi.timePrecision) { + layout := formatDateTime + "." + for i := 0; i < *fi.timePrecision; i++ { + layout += "0" + } + t, err = time.ParseInLocation(layout, s[:20+*fi.timePrecision], tz) + } else if len(s) >= 19 { s = s[:19] t, err = time.ParseInLocation(formatDateTime, s, tz) } else if len(s) >= 10 { @@ -1680,6 +1717,7 @@ func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Cond orderBy := tables.getOrderSQL(qs.orders) limit := tables.getLimitSQL(mi, qs.offset, qs.limit) join := tables.getJoinSQL() + specifyIndexes := tables.getIndexSql(mi.table, qs.useIndex, qs.indexes) sels := strings.Join(cols, ", ") @@ -1687,7 +1725,10 @@ func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Cond if qs.distinct { sqlSelect += " DISTINCT" } - query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s", sqlSelect, sels, Q, mi.table, Q, join, where, groupBy, orderBy, limit) + query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s%s", + sqlSelect, sels, + Q, mi.table, Q, + specifyIndexes, join, where, groupBy, orderBy, limit) d.ins.ReplaceMarks(&query) @@ -1781,10 +1822,6 @@ func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Cond return cnt, nil } -func (d *dbBase) RowsTo(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, string, string, *time.Location) (int64, error) { - return 0, nil -} - // flag of update joined record. func (d *dbBase) SupportUpdateJoin() bool { return true @@ -1900,3 +1937,29 @@ func (d *dbBase) ShowColumnsQuery(table string) string { func (d *dbBase) IndexExists(dbQuerier, string, string) bool { panic(ErrNotImplement) } + +// GenerateSpecifyIndex return a specifying index clause +func (d *dbBase) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string { + var s []string + Q := d.TableQuote() + for _, index := range indexes { + tmp := fmt.Sprintf(`%s%s%s`, Q, index, Q) + s = append(s, tmp) + } + + var useWay string + + switch useIndex { + case hints.KeyUseIndex: + useWay = `USE` + case hints.KeyForceIndex: + useWay = `FORCE` + case hints.KeyIgnoreIndex: + useWay = `IGNORE` + default: + DebugLog.Println("[WARN] Not a valid specifying action, so that action is ignored") + return `` + } + + return fmt.Sprintf(` %s INDEX(%s) `, useWay, strings.Join(s, `,`)) +} diff --git a/orm/db_alias.go b/client/orm/db_alias.go similarity index 62% rename from orm/db_alias.go rename to client/orm/db_alias.go index cf6a5935..29e0904c 100644 --- a/orm/db_alias.go +++ b/client/orm/db_alias.go @@ -18,10 +18,10 @@ import ( "context" "database/sql" "fmt" - lru "github.com/hashicorp/golang-lru" - "reflect" "sync" "time" + + lru "github.com/hashicorp/golang-lru" ) // DriverType database driver constant int. @@ -63,7 +63,7 @@ var ( "tidb": DRTiDB, "oracle": DROracle, "oci8": DROracle, // github.com/mattn/go-oci8 - "ora": DROracle, //https://github.com/rana/ora + "ora": DROracle, // https://github.com/rana/ora } dbBasers = map[DriverType]dbBaser{ DRMySQL: newdbBaseMysql(), @@ -107,10 +107,14 @@ func (ac *_dbCache) getDefault() (al *alias) { type DB struct { *sync.RWMutex - DB *sql.DB - stmtDecorators *lru.Cache + DB *sql.DB + stmtDecorators *lru.Cache + stmtDecoratorsLimit int } +var _ dbQuerier = new(DB) +var _ txer = new(DB) + func (d *DB) Begin() (*sql.Tx, error) { return d.DB.Begin() } @@ -119,7 +123,7 @@ func (d *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) return d.DB.BeginTx(ctx, opts) } -//su must call release to release *sql.Stmt after using +// su must call release to release *sql.Stmt after using func (d *DB) getStmtDecorator(query string) (*stmtDecorator, error) { d.RLock() c, ok := d.stmtDecorators.Get(query) @@ -160,16 +164,14 @@ func (d *DB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error } func (d *DB) Exec(query string, args ...interface{}) (sql.Result, error) { - sd, err := d.getStmtDecorator(query) - if err != nil { - return nil, err - } - stmt := sd.getStmt() - defer sd.release() - return stmt.Exec(args...) + return d.ExecContext(context.Background(), query, args...) } func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + if d.stmtDecorators == nil { + return d.DB.ExecContext(ctx, query, args...) + } + sd, err := d.getStmtDecorator(query) if err != nil { return nil, err @@ -180,16 +182,14 @@ func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) } func (d *DB) Query(query string, args ...interface{}) (*sql.Rows, error) { - sd, err := d.getStmtDecorator(query) - if err != nil { - return nil, err - } - stmt := sd.getStmt() - defer sd.release() - return stmt.Query(args...) + return d.QueryContext(context.Background(), query, args...) } func (d *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { + if d.stmtDecorators == nil { + return d.DB.QueryContext(ctx, query, args...) + } + sd, err := d.getStmtDecorator(query) if err != nil { return nil, err @@ -200,37 +200,86 @@ func (d *DB) QueryContext(ctx context.Context, query string, args ...interface{} } func (d *DB) QueryRow(query string, args ...interface{}) *sql.Row { - sd, err := d.getStmtDecorator(query) - if err != nil { - panic(err) - } - stmt := sd.getStmt() - defer sd.release() - return stmt.QueryRow(args...) - + return d.QueryRowContext(context.Background(), query, args...) } func (d *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { + if d.stmtDecorators == nil { + return d.DB.QueryRowContext(ctx, query, args...) + } + sd, err := d.getStmtDecorator(query) if err != nil { panic(err) } stmt := sd.getStmt() defer sd.release() - return stmt.QueryRowContext(ctx, args) + return stmt.QueryRowContext(ctx, args...) +} + +type TxDB struct { + tx *sql.Tx +} + +var _ dbQuerier = new(TxDB) +var _ txEnder = new(TxDB) + +func (t *TxDB) Commit() error { + return t.tx.Commit() +} + +func (t *TxDB) Rollback() error { + return t.tx.Rollback() +} + +var _ dbQuerier = new(TxDB) +var _ txEnder = new(TxDB) + +func (t *TxDB) Prepare(query string) (*sql.Stmt, error) { + return t.PrepareContext(context.Background(), query) +} + +func (t *TxDB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) { + return t.tx.PrepareContext(ctx, query) +} + +func (t *TxDB) Exec(query string, args ...interface{}) (sql.Result, error) { + return t.ExecContext(context.Background(), query, args...) +} + +func (t *TxDB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + return t.tx.ExecContext(ctx, query, args...) +} + +func (t *TxDB) Query(query string, args ...interface{}) (*sql.Rows, error) { + return t.QueryContext(context.Background(), query, args...) +} + +func (t *TxDB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { + return t.tx.QueryContext(ctx, query, args...) +} + +func (t *TxDB) QueryRow(query string, args ...interface{}) *sql.Row { + return t.QueryRowContext(context.Background(), query, args...) +} + +func (t *TxDB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { + return t.tx.QueryRowContext(ctx, query, args...) } type alias struct { - Name string - Driver DriverType - DriverName string - DataSource string - MaxIdleConns int - MaxOpenConns int - DB *DB - DbBaser dbBaser - TZ *time.Location - Engine string + Name string + Driver DriverType + DriverName string + DataSource string + MaxIdleConns int + MaxOpenConns int + ConnMaxLifetime time.Duration + StmtCacheSize int + DB *DB + DbBaser dbBaser + TZ *time.Location + Engine string } func detectTZ(al *alias) { @@ -289,15 +338,53 @@ func detectTZ(al *alias) { } } -func addAliasWthDB(aliasName, driverName string, db *sql.DB) (*alias, error) { - al := new(alias) +func addAliasWthDB(aliasName, driverName string, db *sql.DB, params ...DBOption) (*alias, error) { + existErr := fmt.Errorf("DataBase alias name `%s` already registered, cannot reuse", aliasName) + if _, ok := dataBaseCache.get(aliasName); ok { + return nil, existErr + } + + al, err := newAliasWithDb(aliasName, driverName, db, params...) + if err != nil { + return nil, err + } + + if !dataBaseCache.add(aliasName, al) { + return nil, existErr + } + + return al, nil +} + +func newAliasWithDb(aliasName, driverName string, db *sql.DB, params ...DBOption) (*alias, error) { + + al := &alias{} + al.DB = &DB{ + RWMutex: new(sync.RWMutex), + DB: db, + } + + for _, p := range params { + p(al) + } + + var stmtCache *lru.Cache + var stmtCacheSize int + + if al.StmtCacheSize > 0 { + _stmtCache, errC := newStmtDecoratorLruWithEvict(al.StmtCacheSize) + if errC != nil { + return nil, errC + } else { + stmtCache = _stmtCache + stmtCacheSize = al.StmtCacheSize + } + } + al.Name = aliasName al.DriverName = driverName - al.DB = &DB{ - RWMutex: new(sync.RWMutex), - DB: db, - stmtDecorators: newStmtDecoratorLruWithEvict(), - } + al.DB.stmtDecorators = stmtCache + al.DB.stmtDecoratorsLimit = stmtCacheSize if dr, ok := drivers[driverName]; ok { al.DbBaser = dbBasers[dr] @@ -311,21 +398,50 @@ func addAliasWthDB(aliasName, driverName string, db *sql.DB) (*alias, error) { return nil, fmt.Errorf("register db Ping `%s`, %s", aliasName, err.Error()) } - if !dataBaseCache.add(aliasName, al) { - return nil, fmt.Errorf("DataBase alias name `%s` already registered, cannot reuse", aliasName) - } + detectTZ(al) return al, nil } +// SetMaxIdleConns Change the max idle conns for *sql.DB, use specify database alias name +// Deprecated you should not use this, we will remove it in the future +func SetMaxIdleConns(aliasName string, maxIdleConns int) { + al := getDbAlias(aliasName) + al.SetMaxIdleConns(maxIdleConns) +} + +// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name +// Deprecated you should not use this, we will remove it in the future +func SetMaxOpenConns(aliasName string, maxOpenConns int) { + al := getDbAlias(aliasName) + al.SetMaxIdleConns(maxOpenConns) +} + +// SetMaxIdleConns Change the max idle conns for *sql.DB, use specify database alias name +func (al *alias) SetMaxIdleConns(maxIdleConns int) { + al.MaxIdleConns = maxIdleConns + al.DB.DB.SetMaxIdleConns(maxIdleConns) +} + +// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name +func (al *alias) SetMaxOpenConns(maxOpenConns int) { + al.MaxOpenConns = maxOpenConns + al.DB.DB.SetMaxOpenConns(maxOpenConns) +} + +func (al *alias) SetConnMaxLifetime(lifeTime time.Duration) { + al.ConnMaxLifetime = lifeTime + al.DB.DB.SetConnMaxLifetime(lifeTime) +} + // AddAliasWthDB add a aliasName for the drivename -func AddAliasWthDB(aliasName, driverName string, db *sql.DB) error { - _, err := addAliasWthDB(aliasName, driverName, db) +func AddAliasWthDB(aliasName, driverName string, db *sql.DB, params ...DBOption) error { + _, err := addAliasWthDB(aliasName, driverName, db, params...) return err } // RegisterDataBase Setting the database connect params. Use the database driver self dataSource args. -func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) error { +func RegisterDataBase(aliasName, driverName, dataSource string, params ...DBOption) error { var ( err error db *sql.DB @@ -338,24 +454,13 @@ func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) e goto end } - al, err = addAliasWthDB(aliasName, driverName, db) + al, err = addAliasWthDB(aliasName, driverName, db, params...) if err != nil { goto end } al.DataSource = dataSource - detectTZ(al) - - for i, v := range params { - switch i { - case 0: - SetMaxIdleConns(al.Name, v) - case 1: - SetMaxOpenConns(al.Name, v) - } - } - end: if err != nil { if db != nil { @@ -389,24 +494,6 @@ func SetDataBaseTZ(aliasName string, tz *time.Location) error { return nil } -// SetMaxIdleConns Change the max idle conns for *sql.DB, use specify database alias name -func SetMaxIdleConns(aliasName string, maxIdleConns int) { - al := getDbAlias(aliasName) - al.MaxIdleConns = maxIdleConns - al.DB.DB.SetMaxIdleConns(maxIdleConns) -} - -// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name -func SetMaxOpenConns(aliasName string, maxOpenConns int) { - al := getDbAlias(aliasName) - al.MaxOpenConns = maxOpenConns - al.DB.DB.SetMaxOpenConns(maxOpenConns) - // for tip go 1.2 - if fun := reflect.ValueOf(al.DB).MethodByName("SetMaxOpenConns"); fun.IsValid() { - fun.Call([]reflect.Value{reflect.ValueOf(maxOpenConns)}) - } -} - // GetDB Get *sql.DB from registered database by db alias name. // Use "default" as alias name if you not set. func GetDB(aliasNames ...string) (*sql.DB, error) { @@ -424,8 +511,7 @@ func GetDB(aliasNames ...string) (*sql.DB, error) { } type stmtDecorator struct { - wg sync.WaitGroup - lastUse int64 + wg sync.WaitGroup stmt *sql.Stmt } @@ -433,16 +519,19 @@ func (s *stmtDecorator) getStmt() *sql.Stmt { return s.stmt } +// acquire will add one +// since this method will be used inside read lock scope, +// so we can not do more things here +// we should think about refactor this func (s *stmtDecorator) acquire() { s.wg.Add(1) - s.lastUse = time.Now().Unix() } func (s *stmtDecorator) release() { s.wg.Done() } -//garbage recycle for stmt +// garbage recycle for stmt func (s *stmtDecorator) destroy() { go func() { s.wg.Wait() @@ -453,13 +542,45 @@ func (s *stmtDecorator) destroy() { func newStmtDecorator(sqlStmt *sql.Stmt) *stmtDecorator { return &stmtDecorator{ stmt: sqlStmt, - lastUse: time.Now().Unix(), } } -func newStmtDecoratorLruWithEvict() *lru.Cache { - cache, _ := lru.NewWithEvict(1000, func(key interface{}, value interface{}) { +func newStmtDecoratorLruWithEvict(cacheSize int) (*lru.Cache, error) { + cache, err := lru.NewWithEvict(cacheSize, func(key interface{}, value interface{}) { value.(*stmtDecorator).destroy() }) - return cache + if err != nil { + return nil, err + } + return cache, nil +} + +type DBOption func(al *alias) + +// MaxIdleConnections return a hint about MaxIdleConnections +func MaxIdleConnections(maxIdleConn int) DBOption { + return func(al *alias) { + al.SetMaxIdleConns(maxIdleConn) + } +} + +// MaxOpenConnections return a hint about MaxOpenConnections +func MaxOpenConnections(maxOpenConn int) DBOption { + return func(al *alias) { + al.SetMaxOpenConns(maxOpenConn) + } +} + +// ConnMaxLifetime return a hint about ConnMaxLifetime +func ConnMaxLifetime(v time.Duration) DBOption { + return func(al *alias) { + al.SetConnMaxLifetime(v) + } +} + +// MaxStmtCacheSize return a hint about MaxStmtCacheSize +func MaxStmtCacheSize(v int) DBOption { + return func(al *alias) { + al.StmtCacheSize = v + } } diff --git a/client/orm/db_alias_test.go b/client/orm/db_alias_test.go new file mode 100644 index 00000000..6275cb2a --- /dev/null +++ b/client/orm/db_alias_test.go @@ -0,0 +1,86 @@ +// Copyright 2020 beego-dev +// +// Licensed 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 orm + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestRegisterDataBase(t *testing.T) { + err := RegisterDataBase("test-params", DBARGS.Driver, DBARGS.Source, + MaxIdleConnections(20), + MaxOpenConnections(300), + ConnMaxLifetime(time.Minute)) + assert.Nil(t, err) + + al := getDbAlias("test-params") + assert.NotNil(t, al) + assert.Equal(t, al.MaxIdleConns, 20) + assert.Equal(t, al.MaxOpenConns, 300) + assert.Equal(t, al.ConnMaxLifetime, time.Minute) +} + +func TestRegisterDataBase_MaxStmtCacheSizeNegative1(t *testing.T) { + aliasName := "TestRegisterDataBase_MaxStmtCacheSizeNegative1" + err := RegisterDataBase(aliasName, DBARGS.Driver, DBARGS.Source, MaxStmtCacheSize(-1)) + assert.Nil(t, err) + + al := getDbAlias(aliasName) + assert.NotNil(t, al) + assert.Equal(t, al.DB.stmtDecoratorsLimit, 0) +} + +func TestRegisterDataBase_MaxStmtCacheSize0(t *testing.T) { + aliasName := "TestRegisterDataBase_MaxStmtCacheSize0" + err := RegisterDataBase(aliasName, DBARGS.Driver, DBARGS.Source, MaxStmtCacheSize(0)) + assert.Nil(t, err) + + al := getDbAlias(aliasName) + assert.NotNil(t, al) + assert.Equal(t, al.DB.stmtDecoratorsLimit, 0) +} + +func TestRegisterDataBase_MaxStmtCacheSize1(t *testing.T) { + aliasName := "TestRegisterDataBase_MaxStmtCacheSize1" + err := RegisterDataBase(aliasName, DBARGS.Driver, DBARGS.Source, MaxStmtCacheSize(1)) + assert.Nil(t, err) + + al := getDbAlias(aliasName) + assert.NotNil(t, al) + assert.Equal(t, al.DB.stmtDecoratorsLimit, 1) +} + +func TestRegisterDataBase_MaxStmtCacheSize841(t *testing.T) { + aliasName := "TestRegisterDataBase_MaxStmtCacheSize841" + err := RegisterDataBase(aliasName, DBARGS.Driver, DBARGS.Source, MaxStmtCacheSize(841)) + assert.Nil(t, err) + + al := getDbAlias(aliasName) + assert.NotNil(t, al) + assert.Equal(t, al.DB.stmtDecoratorsLimit, 841) +} + +func TestDBCache(t *testing.T) { + dataBaseCache.add("test1", &alias{}) + dataBaseCache.add("default", &alias{}) + al := dataBaseCache.getDefault() + assert.NotNil(t, al) + al, ok := dataBaseCache.get("test1") + assert.NotNil(t, al) + assert.True(t, ok) +} diff --git a/orm/db_mysql.go b/client/orm/db_mysql.go similarity index 79% rename from orm/db_mysql.go rename to client/orm/db_mysql.go index 6e99058e..f674ab2b 100644 --- a/orm/db_mysql.go +++ b/client/orm/db_mysql.go @@ -22,10 +22,11 @@ import ( // mysql operators. var mysqlOperators = map[string]string{ - "exact": "= ?", - "iexact": "LIKE ?", - "contains": "LIKE BINARY ?", - "icontains": "LIKE ?", + "exact": "= ?", + "iexact": "LIKE ?", + "strictexact": "= BINARY ?", + "contains": "LIKE BINARY ?", + "icontains": "LIKE ?", // "regex": "REGEXP BINARY ?", // "iregex": "REGEXP ?", "gt": "> ?", @@ -42,24 +43,25 @@ var mysqlOperators = map[string]string{ // mysql column field types. var mysqlTypes = map[string]string{ - "auto": "AUTO_INCREMENT NOT NULL PRIMARY KEY", - "pk": "NOT NULL PRIMARY KEY", - "bool": "bool", - "string": "varchar(%d)", - "string-char": "char(%d)", - "string-text": "longtext", - "time.Time-date": "date", - "time.Time": "datetime", - "int8": "tinyint", - "int16": "smallint", - "int32": "integer", - "int64": "bigint", - "uint8": "tinyint unsigned", - "uint16": "smallint unsigned", - "uint32": "integer unsigned", - "uint64": "bigint unsigned", - "float64": "double precision", - "float64-decimal": "numeric(%d, %d)", + "auto": "AUTO_INCREMENT NOT NULL PRIMARY KEY", + "pk": "NOT NULL PRIMARY KEY", + "bool": "bool", + "string": "varchar(%d)", + "string-char": "char(%d)", + "string-text": "longtext", + "time.Time-date": "date", + "time.Time": "datetime", + "int8": "tinyint", + "int16": "smallint", + "int32": "integer", + "int64": "bigint", + "uint8": "tinyint unsigned", + "uint16": "smallint unsigned", + "uint32": "integer unsigned", + "uint64": "bigint unsigned", + "float64": "double precision", + "float64-decimal": "numeric(%d, %d)", + "time.Time-precision": "datetime(%d)", } // mysql dbBaser implementation. @@ -164,7 +166,14 @@ func (d *dbBaseMysql) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Val if isMulti { return res.RowsAffected() } - return res.LastInsertId() + + lastInsertId, err := res.LastInsertId() + if err != nil { + DebugLog.Println(ErrLastInsertIdUnavailable, ':', err) + return lastInsertId, ErrLastInsertIdUnavailable + } else { + return lastInsertId, nil + } } return 0, err } diff --git a/orm/db_oracle.go b/client/orm/db_oracle.go similarity index 67% rename from orm/db_oracle.go rename to client/orm/db_oracle.go index 5d121f83..cb0d5052 100644 --- a/orm/db_oracle.go +++ b/client/orm/db_oracle.go @@ -17,6 +17,8 @@ package orm import ( "fmt" "strings" + + "github.com/astaxie/beego/client/orm/hints" ) // oracle operators. @@ -31,23 +33,24 @@ var oracleOperators = map[string]string{ // oracle column field types. var oracleTypes = map[string]string{ - "pk": "NOT NULL PRIMARY KEY", - "bool": "bool", - "string": "VARCHAR2(%d)", - "string-char": "CHAR(%d)", - "string-text": "VARCHAR2(%d)", - "time.Time-date": "DATE", - "time.Time": "TIMESTAMP", - "int8": "INTEGER", - "int16": "INTEGER", - "int32": "INTEGER", - "int64": "INTEGER", - "uint8": "INTEGER", - "uint16": "INTEGER", - "uint32": "INTEGER", - "uint64": "INTEGER", - "float64": "NUMBER", - "float64-decimal": "NUMBER(%d, %d)", + "pk": "NOT NULL PRIMARY KEY", + "bool": "bool", + "string": "VARCHAR2(%d)", + "string-char": "CHAR(%d)", + "string-text": "VARCHAR2(%d)", + "time.Time-date": "DATE", + "time.Time": "TIMESTAMP", + "int8": "INTEGER", + "int16": "INTEGER", + "int32": "INTEGER", + "int64": "INTEGER", + "uint8": "INTEGER", + "uint16": "INTEGER", + "uint32": "INTEGER", + "uint64": "INTEGER", + "float64": "NUMBER", + "float64-decimal": "NUMBER(%d, %d)", + "time.Time-precision": "TIMESTAMP(%d)", } // oracle dbBaser @@ -96,6 +99,29 @@ func (d *dbBaseOracle) IndexExists(db dbQuerier, table string, name string) bool return cnt > 0 } +func (d *dbBaseOracle) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string { + var s []string + Q := d.TableQuote() + for _, index := range indexes { + tmp := fmt.Sprintf(`%s%s%s`, Q, index, Q) + s = append(s, tmp) + } + + var hint string + + switch useIndex { + case hints.KeyUseIndex, hints.KeyForceIndex: + hint = `INDEX` + case hints.KeyIgnoreIndex: + hint = `NO_INDEX` + default: + DebugLog.Println("[WARN] Not a valid specifying action, so that action is ignored") + return `` + } + + return fmt.Sprintf(` /*+ %s(%s %s)*/ `, hint, tableName, strings.Join(s, `,`)) +} + // execute insert sql with given struct and given values. // insert the given values, not the field values in struct. func (d *dbBaseOracle) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []string, values []interface{}) (int64, error) { @@ -126,7 +152,14 @@ func (d *dbBaseOracle) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, nam if isMulti { return res.RowsAffected() } - return res.LastInsertId() + + lastInsertId, err := res.LastInsertId() + if err != nil { + DebugLog.Println(ErrLastInsertIdUnavailable, ':', err) + return lastInsertId, ErrLastInsertIdUnavailable + } else { + return lastInsertId, nil + } } return 0, err } diff --git a/orm/db_postgres.go b/client/orm/db_postgres.go similarity index 78% rename from orm/db_postgres.go rename to client/orm/db_postgres.go index c488fb38..12431d6e 100644 --- a/orm/db_postgres.go +++ b/client/orm/db_postgres.go @@ -39,26 +39,27 @@ var postgresOperators = map[string]string{ // postgresql column field types. var postgresTypes = map[string]string{ - "auto": "serial NOT NULL PRIMARY KEY", - "pk": "NOT NULL PRIMARY KEY", - "bool": "bool", - "string": "varchar(%d)", - "string-char": "char(%d)", - "string-text": "text", - "time.Time-date": "date", - "time.Time": "timestamp with time zone", - "int8": `smallint CHECK("%COL%" >= -127 AND "%COL%" <= 128)`, - "int16": "smallint", - "int32": "integer", - "int64": "bigint", - "uint8": `smallint CHECK("%COL%" >= 0 AND "%COL%" <= 255)`, - "uint16": `integer CHECK("%COL%" >= 0)`, - "uint32": `bigint CHECK("%COL%" >= 0)`, - "uint64": `bigint CHECK("%COL%" >= 0)`, - "float64": "double precision", - "float64-decimal": "numeric(%d, %d)", - "json": "json", - "jsonb": "jsonb", + "auto": "serial NOT NULL PRIMARY KEY", + "pk": "NOT NULL PRIMARY KEY", + "bool": "bool", + "string": "varchar(%d)", + "string-char": "char(%d)", + "string-text": "text", + "time.Time-date": "date", + "time.Time": "timestamp with time zone", + "int8": `smallint CHECK("%COL%" >= -127 AND "%COL%" <= 128)`, + "int16": "smallint", + "int32": "integer", + "int64": "bigint", + "uint8": `smallint CHECK("%COL%" >= 0 AND "%COL%" <= 255)`, + "uint16": `integer CHECK("%COL%" >= 0)`, + "uint32": `bigint CHECK("%COL%" >= 0)`, + "uint64": `bigint CHECK("%COL%" >= 0)`, + "float64": "double precision", + "float64-decimal": "numeric(%d, %d)", + "json": "json", + "jsonb": "jsonb", + "time.Time-precision": "timestamp(%d) with time zone", } // postgresql dbBaser. @@ -181,6 +182,12 @@ func (d *dbBasePostgres) IndexExists(db dbQuerier, table string, name string) bo return cnt > 0 } +// GenerateSpecifyIndex return a specifying index clause +func (d *dbBasePostgres) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string { + DebugLog.Println("[WARN] Not support any specifying index action, so that action is ignored") + return `` +} + // create new postgresql dbBaser. func newdbBasePostgres() dbBaser { b := new(dbBasePostgres) diff --git a/orm/db_sqlite.go b/client/orm/db_sqlite.go similarity index 73% rename from orm/db_sqlite.go rename to client/orm/db_sqlite.go index 1d62ee34..961f2535 100644 --- a/orm/db_sqlite.go +++ b/client/orm/db_sqlite.go @@ -18,7 +18,10 @@ import ( "database/sql" "fmt" "reflect" + "strings" "time" + + "github.com/astaxie/beego/client/orm/hints" ) // sqlite operators. @@ -41,24 +44,25 @@ var sqliteOperators = map[string]string{ // sqlite column types. var sqliteTypes = map[string]string{ - "auto": "integer NOT NULL PRIMARY KEY AUTOINCREMENT", - "pk": "NOT NULL PRIMARY KEY", - "bool": "bool", - "string": "varchar(%d)", - "string-char": "character(%d)", - "string-text": "text", - "time.Time-date": "date", - "time.Time": "datetime", - "int8": "tinyint", - "int16": "smallint", - "int32": "integer", - "int64": "bigint", - "uint8": "tinyint unsigned", - "uint16": "smallint unsigned", - "uint32": "integer unsigned", - "uint64": "bigint unsigned", - "float64": "real", - "float64-decimal": "decimal", + "auto": "integer NOT NULL PRIMARY KEY AUTOINCREMENT", + "pk": "NOT NULL PRIMARY KEY", + "bool": "bool", + "string": "varchar(%d)", + "string-char": "character(%d)", + "string-text": "text", + "time.Time-date": "date", + "time.Time": "datetime", + "time.Time-precision": "datetime(%d)", + "int8": "tinyint", + "int16": "smallint", + "int32": "integer", + "int64": "bigint", + "uint8": "tinyint unsigned", + "uint16": "smallint unsigned", + "uint32": "integer unsigned", + "uint64": "bigint unsigned", + "float64": "real", + "float64-decimal": "decimal", } // sqlite dbBaser. @@ -153,6 +157,24 @@ func (d *dbBaseSqlite) IndexExists(db dbQuerier, table string, name string) bool return false } +// GenerateSpecifyIndex return a specifying index clause +func (d *dbBaseSqlite) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string { + var s []string + Q := d.TableQuote() + for _, index := range indexes { + tmp := fmt.Sprintf(`%s%s%s`, Q, index, Q) + s = append(s, tmp) + } + + switch useIndex { + case hints.KeyUseIndex, hints.KeyForceIndex: + return fmt.Sprintf(` INDEXED BY %s `, strings.Join(s, `,`)) + default: + DebugLog.Println("[WARN] Not a valid specifying action, so that action is ignored") + return `` + } +} + // create new sqlite dbBaser. func newdbBaseSqlite() dbBaser { b := new(dbBaseSqlite) diff --git a/orm/db_tables.go b/client/orm/db_tables.go similarity index 97% rename from orm/db_tables.go rename to client/orm/db_tables.go index 4b21a6fc..5fd472d1 100644 --- a/orm/db_tables.go +++ b/client/orm/db_tables.go @@ -472,6 +472,15 @@ func (t *dbTables) getLimitSQL(mi *modelInfo, offset int64, limit int64) (limits return } +// getIndexSql generate index sql. +func (t *dbTables) getIndexSql(tableName string, useIndex int, indexes []string) (clause string) { + if len(indexes) == 0 { + return + } + + return t.base.GenerateSpecifyIndex(tableName, useIndex, indexes) +} + // crete new tables collection. func newDbTables(mi *modelInfo, base dbBaser) *dbTables { tables := &dbTables{} diff --git a/orm/db_tidb.go b/client/orm/db_tidb.go similarity index 100% rename from orm/db_tidb.go rename to client/orm/db_tidb.go diff --git a/orm/db_utils.go b/client/orm/db_utils.go similarity index 100% rename from orm/db_utils.go rename to client/orm/db_utils.go diff --git a/client/orm/do_nothing_orm.go b/client/orm/do_nothing_orm.go new file mode 100644 index 00000000..fc5b2159 --- /dev/null +++ b/client/orm/do_nothing_orm.go @@ -0,0 +1,180 @@ +// Copyright 2020 beego +// +// Licensed 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 orm + +import ( + "context" + "database/sql" + + "github.com/astaxie/beego/core/utils" +) + +// DoNothingOrm won't do anything, usually you use this to custom your mock Ormer implementation +// I think golang mocking interface is hard to use +// this may help you to integrate with Ormer + +var _ Ormer = new(DoNothingOrm) + +type DoNothingOrm struct { +} + +func (d *DoNothingOrm) Read(md interface{}, cols ...string) error { + return nil +} + +func (d *DoNothingOrm) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error { + return nil +} + +func (d *DoNothingOrm) ReadForUpdate(md interface{}, cols ...string) error { + return nil +} + +func (d *DoNothingOrm) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error { + return nil +} + +func (d *DoNothingOrm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) { + return false, 0, nil +} + +func (d *DoNothingOrm) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) { + return false, 0, nil +} + +func (d *DoNothingOrm) LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) { + return 0, nil +} + +func (d *DoNothingOrm) LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) { + return 0, nil +} + +func (d *DoNothingOrm) QueryM2M(md interface{}, name string) QueryM2Mer { + return nil +} + +func (d *DoNothingOrm) QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer { + return nil +} + +func (d *DoNothingOrm) QueryTable(ptrStructOrTableName interface{}) QuerySeter { + return nil +} + +func (d *DoNothingOrm) QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) QuerySeter { + return nil +} + +func (d *DoNothingOrm) DBStats() *sql.DBStats { + return nil +} + +func (d *DoNothingOrm) Insert(md interface{}) (int64, error) { + return 0, nil +} + +func (d *DoNothingOrm) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) { + return 0, nil +} + +func (d *DoNothingOrm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) { + return 0, nil +} + +func (d *DoNothingOrm) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) { + return 0, nil +} + +func (d *DoNothingOrm) InsertMulti(bulk int, mds interface{}) (int64, error) { + return 0, nil +} + +func (d *DoNothingOrm) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) { + return 0, nil +} + +func (d *DoNothingOrm) Update(md interface{}, cols ...string) (int64, error) { + return 0, nil +} + +func (d *DoNothingOrm) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) { + return 0, nil +} + +func (d *DoNothingOrm) Delete(md interface{}, cols ...string) (int64, error) { + return 0, nil +} + +func (d *DoNothingOrm) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) { + return 0, nil +} + +func (d *DoNothingOrm) Raw(query string, args ...interface{}) RawSeter { + return nil +} + +func (d *DoNothingOrm) RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter { + return nil +} + +func (d *DoNothingOrm) Driver() Driver { + return nil +} + +func (d *DoNothingOrm) Begin() (TxOrmer, error) { + return nil, nil +} + +func (d *DoNothingOrm) BeginWithCtx(ctx context.Context) (TxOrmer, error) { + return nil, nil +} + +func (d *DoNothingOrm) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) { + return nil, nil +} + +func (d *DoNothingOrm) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) { + return nil, nil +} + +func (d *DoNothingOrm) DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error { + return nil +} + +func (d *DoNothingOrm) DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error { + return nil +} + +func (d *DoNothingOrm) DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error { + return nil +} + +func (d *DoNothingOrm) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error { + return nil +} + +// DoNothingTxOrm is similar with DoNothingOrm, usually you use it to test +type DoNothingTxOrm struct { + DoNothingOrm +} + +func (d *DoNothingTxOrm) Commit() error { + return nil +} + +func (d *DoNothingTxOrm) Rollback() error { + return nil +} diff --git a/client/orm/do_nothing_orm_test.go b/client/orm/do_nothing_orm_test.go new file mode 100644 index 00000000..4d477353 --- /dev/null +++ b/client/orm/do_nothing_orm_test.go @@ -0,0 +1,134 @@ +// Copyright 2020 beego +// +// Licensed 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 orm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDoNothingOrm(t *testing.T) { + o := &DoNothingOrm{} + err := o.DoTxWithCtxAndOpts(nil, nil, nil) + assert.Nil(t, err) + + err = o.DoTxWithCtx(nil, nil) + assert.Nil(t, err) + + err = o.DoTx(nil) + assert.Nil(t, err) + + err = o.DoTxWithOpts(nil, nil) + assert.Nil(t, err) + + assert.Nil(t, o.Driver()) + + assert.Nil(t, o.QueryM2MWithCtx(nil, nil, "")) + assert.Nil(t, o.QueryM2M(nil, "")) + assert.Nil(t, o.ReadWithCtx(nil, nil)) + assert.Nil(t, o.Read(nil)) + + txOrm, err := o.BeginWithCtxAndOpts(nil, nil) + assert.Nil(t, err) + assert.Nil(t, txOrm) + + txOrm, err = o.BeginWithCtx(nil) + assert.Nil(t, err) + assert.Nil(t, txOrm) + + txOrm, err = o.BeginWithOpts(nil) + assert.Nil(t, err) + assert.Nil(t, txOrm) + + txOrm, err = o.Begin() + assert.Nil(t, err) + assert.Nil(t, txOrm) + + assert.Nil(t, o.RawWithCtx(nil, "")) + assert.Nil(t, o.Raw("")) + + i, err := o.InsertMulti(0, nil) + assert.Nil(t, err) + assert.Equal(t, int64(0), i) + + i, err = o.Insert(nil) + assert.Nil(t, err) + assert.Equal(t, int64(0), i) + + i, err = o.InsertWithCtx(nil, nil) + assert.Nil(t, err) + assert.Equal(t, int64(0), i) + + i, err = o.InsertOrUpdateWithCtx(nil, nil) + assert.Nil(t, err) + assert.Equal(t, int64(0), i) + + i, err = o.InsertOrUpdate(nil) + assert.Nil(t, err) + assert.Equal(t, int64(0), i) + + i, err = o.InsertMultiWithCtx(nil, 0, nil) + assert.Nil(t, err) + assert.Equal(t, int64(0), i) + + i, err = o.LoadRelatedWithCtx(nil, nil, "") + assert.Nil(t, err) + assert.Equal(t, int64(0), i) + + i, err = o.LoadRelated(nil, "") + assert.Nil(t, err) + assert.Equal(t, int64(0), i) + + assert.Nil(t, o.QueryTableWithCtx(nil, nil)) + assert.Nil(t, o.QueryTable(nil)) + + assert.Nil(t, o.Read(nil)) + assert.Nil(t, o.ReadWithCtx(nil, nil)) + assert.Nil(t, o.ReadForUpdateWithCtx(nil, nil)) + assert.Nil(t, o.ReadForUpdate(nil)) + + ok, i, err := o.ReadOrCreate(nil, "") + assert.Nil(t, err) + assert.Equal(t, int64(0), i) + assert.False(t, ok) + + ok, i, err = o.ReadOrCreateWithCtx(nil, nil, "") + assert.Nil(t, err) + assert.Equal(t, int64(0), i) + assert.False(t, ok) + + i, err = o.Delete(nil) + assert.Nil(t, err) + assert.Equal(t, int64(0), i) + + i, err = o.DeleteWithCtx(nil, nil) + assert.Nil(t, err) + assert.Equal(t, int64(0), i) + + i, err = o.Update(nil) + assert.Nil(t, err) + assert.Equal(t, int64(0), i) + + i, err = o.UpdateWithCtx(nil, nil) + assert.Nil(t, err) + assert.Equal(t, int64(0), i) + + assert.Nil(t, o.DBStats()) + + to := &DoNothingTxOrm{} + assert.Nil(t, to.Commit()) + assert.Nil(t, to.Rollback()) +} diff --git a/client/orm/filter.go b/client/orm/filter.go new file mode 100644 index 00000000..bc13c3fa --- /dev/null +++ b/client/orm/filter.go @@ -0,0 +1,40 @@ +// Copyright 2020 beego +// +// Licensed 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 orm + +import ( + "context" +) + +// FilterChain is used to build a Filter +// don't forget to call next(...) inside your Filter +type FilterChain func(next Filter) Filter + +// Filter's behavior is a little big strange. +// it's only be called when users call methods of Ormer +// return value is an array. it's a little bit hard to understand, +// for example, the Ormer's Read method only return error +// so the filter processing this method should return an array whose first element is error +// and, Ormer's ReadOrCreateWithCtx return three values, so the Filter's result should contains three values +type Filter func(ctx context.Context, inv *Invocation) []interface{} + +var globalFilterChains = make([]FilterChain, 0, 4) + +// AddGlobalFilterChain adds a new FilterChain +// All orm instances built after this invocation will use this filterChain, +// but instances built before this invocation will not be affected +func AddGlobalFilterChain(filterChain ...FilterChain) { + globalFilterChains = append(globalFilterChains, filterChain...) +} diff --git a/client/orm/filter/bean/default_value_filter.go b/client/orm/filter/bean/default_value_filter.go new file mode 100644 index 00000000..3dac5c74 --- /dev/null +++ b/client/orm/filter/bean/default_value_filter.go @@ -0,0 +1,137 @@ +// Copyright 2020 +// +// Licensed 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 bean + +import ( + "context" + "reflect" + "strings" + + "github.com/astaxie/beego/core/logs" + + "github.com/astaxie/beego/client/orm" + "github.com/astaxie/beego/core/bean" +) + +// DefaultValueFilterChainBuilder only works for InsertXXX method, +// But InsertOrUpdate and InsertOrUpdateWithCtx is more dangerous than other methods. +// so we won't handle those two methods unless you set includeInsertOrUpdate to true +// And if the element is not pointer, this filter doesn't work +type DefaultValueFilterChainBuilder struct { + factory bean.AutoWireBeanFactory + compatibleWithOldStyle bool + + // only the includeInsertOrUpdate is true, this filter will handle those two methods + includeInsertOrUpdate bool +} + +// NewDefaultValueFilterChainBuilder will create an instance of DefaultValueFilterChainBuilder +// In beego v1.x, the default value config looks like orm:default(xxxx) +// But the default value in 2.x is default:xxx +// so if you want to be compatible with v1.x, please pass true as compatibleWithOldStyle +func NewDefaultValueFilterChainBuilder(typeAdapters map[string]bean.TypeAdapter, + includeInsertOrUpdate bool, + compatibleWithOldStyle bool) *DefaultValueFilterChainBuilder { + factory := bean.NewTagAutoWireBeanFactory() + + if compatibleWithOldStyle { + newParser := factory.FieldTagParser + factory.FieldTagParser = func(field reflect.StructField) *bean.FieldMetadata { + if newParser != nil && field.Tag.Get(bean.DefaultValueTagKey) != "" { + return newParser(field) + } else { + res := &bean.FieldMetadata{} + ormMeta := field.Tag.Get("orm") + ormMetaParts := strings.Split(ormMeta, ";") + for _, p := range ormMetaParts { + if strings.HasPrefix(p, "default(") && strings.HasSuffix(p, ")") { + res.DftValue = p[8 : len(p)-1] + } + } + return res + } + } + } + + for k, v := range typeAdapters { + factory.Adapters[k] = v + } + + return &DefaultValueFilterChainBuilder{ + factory: factory, + compatibleWithOldStyle: compatibleWithOldStyle, + includeInsertOrUpdate: includeInsertOrUpdate, + } +} + +func (d *DefaultValueFilterChainBuilder) FilterChain(next orm.Filter) orm.Filter { + return func(ctx context.Context, inv *orm.Invocation) []interface{} { + switch inv.Method { + case "Insert", "InsertWithCtx": + d.handleInsert(ctx, inv) + break + case "InsertOrUpdate", "InsertOrUpdateWithCtx": + d.handleInsertOrUpdate(ctx, inv) + break + case "InsertMulti", "InsertMultiWithCtx": + d.handleInsertMulti(ctx, inv) + break + } + return next(ctx, inv) + } +} + +func (d *DefaultValueFilterChainBuilder) handleInsert(ctx context.Context, inv *orm.Invocation) { + d.setDefaultValue(ctx, inv.Args[0]) +} + +func (d *DefaultValueFilterChainBuilder) handleInsertOrUpdate(ctx context.Context, inv *orm.Invocation) { + if d.includeInsertOrUpdate { + ins := inv.Args[0] + if ins == nil { + return + } + + pkName := inv.GetPkFieldName() + pkField := reflect.Indirect(reflect.ValueOf(ins)).FieldByName(pkName) + + if pkField.IsZero() { + d.setDefaultValue(ctx, ins) + } + } +} + +func (d *DefaultValueFilterChainBuilder) handleInsertMulti(ctx context.Context, inv *orm.Invocation) { + mds := inv.Args[1] + + if t := reflect.TypeOf(mds).Kind(); t != reflect.Array && t != reflect.Slice { + // do nothing + return + } + + mdsArr := reflect.Indirect(reflect.ValueOf(mds)) + for i := 0; i < mdsArr.Len(); i++ { + d.setDefaultValue(ctx, mdsArr.Index(i).Interface()) + } + logs.Warn("%v", mdsArr.Index(0).Interface()) +} + +func (d *DefaultValueFilterChainBuilder) setDefaultValue(ctx context.Context, ins interface{}) { + err := d.factory.AutoWire(ctx, nil, ins) + if err != nil { + logs.Error("try to wire the bean for orm.Insert failed. "+ + "the default value is not set: %v, ", err) + } +} diff --git a/client/orm/filter/bean/default_value_filter_test.go b/client/orm/filter/bean/default_value_filter_test.go new file mode 100644 index 00000000..2a6ed1f4 --- /dev/null +++ b/client/orm/filter/bean/default_value_filter_test.go @@ -0,0 +1,72 @@ +// Copyright 2020 +// +// Licensed 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 bean + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/client/orm" +) + +func TestDefaultValueFilterChainBuilder_FilterChain(t *testing.T) { + builder := NewDefaultValueFilterChainBuilder(nil, true, true) + o := orm.NewFilterOrmDecorator(&defaultValueTestOrm{}, builder.FilterChain) + + // test insert + entity := &DefaultValueTestEntity{} + _, _ = o.Insert(entity) + assert.Equal(t, 12, entity.Age) + assert.Equal(t, 13, entity.AgeInOldStyle) + assert.Equal(t, 0, entity.AgeIgnore) + + // test InsertOrUpdate + entity = &DefaultValueTestEntity{} + orm.RegisterModel(entity) + + _, _ = o.InsertOrUpdate(entity) + assert.Equal(t, 12, entity.Age) + assert.Equal(t, 13, entity.AgeInOldStyle) + + // we won't set the default value because we find the pk is not Zero value + entity.Id = 3 + entity.AgeInOldStyle = 0 + _, _ = o.InsertOrUpdate(entity) + assert.Equal(t, 0, entity.AgeInOldStyle) + + entity = &DefaultValueTestEntity{} + + // the entity is not array, it will be ignored + _, _ = o.InsertMulti(3, entity) + assert.Equal(t, 0, entity.Age) + assert.Equal(t, 0, entity.AgeInOldStyle) + + _, _ = o.InsertMulti(3, []*DefaultValueTestEntity{entity}) + assert.Equal(t, 12, entity.Age) + assert.Equal(t, 13, entity.AgeInOldStyle) + +} + +type defaultValueTestOrm struct { + orm.DoNothingOrm +} + +type DefaultValueTestEntity struct { + Id int + Age int `default:"12"` + AgeInOldStyle int `orm:"default(13);bee()"` + AgeIgnore int +} diff --git a/client/orm/filter/opentracing/filter.go b/client/orm/filter/opentracing/filter.go new file mode 100644 index 00000000..7f9658b4 --- /dev/null +++ b/client/orm/filter/opentracing/filter.go @@ -0,0 +1,71 @@ +// Copyright 2020 beego +// +// Licensed 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 opentracing + +import ( + "context" + "strings" + + "github.com/opentracing/opentracing-go" + + "github.com/astaxie/beego/client/orm" +) + +// FilterChainBuilder provides an extension point +// this Filter's behavior looks a little bit strange +// for example: +// if we want to trace QuerySetter +// actually we trace invoking "QueryTable" and "QueryTableWithCtx" +// the method Begin*, Commit and Rollback are ignored. +// When use using those methods, it means that they want to manager their transaction manually, so we won't handle them. +type FilterChainBuilder struct { + // CustomSpanFunc users are able to custom their span + CustomSpanFunc func(span opentracing.Span, ctx context.Context, inv *orm.Invocation) +} + +func (builder *FilterChainBuilder) FilterChain(next orm.Filter) orm.Filter { + return func(ctx context.Context, inv *orm.Invocation) []interface{} { + operationName := builder.operationName(ctx, inv) + if strings.HasPrefix(inv.Method, "Begin") || inv.Method == "Commit" || inv.Method == "Rollback" { + return next(ctx, inv) + } + + span, spanCtx := opentracing.StartSpanFromContext(ctx, operationName) + defer span.Finish() + res := next(spanCtx, inv) + builder.buildSpan(span, spanCtx, inv) + return res + } +} + +func (builder *FilterChainBuilder) buildSpan(span opentracing.Span, ctx context.Context, inv *orm.Invocation) { + span.SetTag("orm.method", inv.Method) + span.SetTag("orm.table", inv.GetTableName()) + span.SetTag("orm.insideTx", inv.InsideTx) + span.SetTag("orm.txName", ctx.Value(orm.TxNameKey)) + span.SetTag("span.kind", "client") + span.SetTag("component", "beego") + + if builder.CustomSpanFunc != nil { + builder.CustomSpanFunc(span, ctx, inv) + } +} + +func (builder *FilterChainBuilder) operationName(ctx context.Context, inv *orm.Invocation) string { + if n, ok := ctx.Value(orm.TxNameKey).(string); ok { + return inv.Method + "#tx(" + n + ")" + } + return inv.Method + "#" + inv.GetTableName() +} diff --git a/client/orm/filter/opentracing/filter_test.go b/client/orm/filter/opentracing/filter_test.go new file mode 100644 index 00000000..428dacda --- /dev/null +++ b/client/orm/filter/opentracing/filter_test.go @@ -0,0 +1,44 @@ +// Copyright 2020 beego +// +// Licensed 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 opentracing + +import ( + "context" + "testing" + "time" + + "github.com/opentracing/opentracing-go" + + "github.com/astaxie/beego/client/orm" +) + +func TestFilterChainBuilder_FilterChain(t *testing.T) { + next := func(ctx context.Context, inv *orm.Invocation) []interface{} { + inv.TxName = "Hello" + return []interface{}{} + } + + builder := &FilterChainBuilder{ + CustomSpanFunc: func(span opentracing.Span, ctx context.Context, inv *orm.Invocation) { + span.SetTag("hello", "hell") + }, + } + + inv := &orm.Invocation{ + Method: "Hello", + TxStartTime: time.Now(), + } + builder.FilterChain(next)(context.Background(), inv) +} diff --git a/client/orm/filter/prometheus/filter.go b/client/orm/filter/prometheus/filter.go new file mode 100644 index 00000000..e74e946a --- /dev/null +++ b/client/orm/filter/prometheus/filter.go @@ -0,0 +1,85 @@ +// Copyright 2020 beego +// +// Licensed 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 prometheus + +import ( + "context" + "strconv" + "strings" + "time" + + "github.com/prometheus/client_golang/prometheus" + + "github.com/astaxie/beego/client/orm" +) + +// FilterChainBuilder is an extension point, +// when we want to support some configuration, +// please use this structure +// this Filter's behavior looks a little bit strange +// for example: +// if we want to records the metrics of QuerySetter +// actually we only records metrics of invoking "QueryTable" and "QueryTableWithCtx" +type FilterChainBuilder struct { + summaryVec prometheus.ObserverVec + AppName string + ServerName string + RunMode string +} + +func (builder *FilterChainBuilder) FilterChain(next orm.Filter) orm.Filter { + + builder.summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{ + Name: "beego", + Subsystem: "orm_operation", + ConstLabels: map[string]string{ + "server": builder.ServerName, + "env": builder.RunMode, + "appname": builder.AppName, + }, + Help: "The statics info for orm operation", + }, []string{"method", "name", "duration", "insideTx", "txName"}) + + return func(ctx context.Context, inv *orm.Invocation) []interface{} { + startTime := time.Now() + res := next(ctx, inv) + endTime := time.Now() + dur := (endTime.Sub(startTime)) / time.Millisecond + + // if the TPS is too large, here may be some problem + // thinking about using goroutine pool + go builder.report(ctx, inv, dur) + return res + } +} + +func (builder *FilterChainBuilder) report(ctx context.Context, inv *orm.Invocation, dur time.Duration) { + // start a transaction, we don't record it + if strings.HasPrefix(inv.Method, "Begin") { + return + } + if inv.Method == "Commit" || inv.Method == "Rollback" { + builder.reportTxn(ctx, inv) + return + } + builder.summaryVec.WithLabelValues(inv.Method, inv.GetTableName(), strconv.Itoa(int(dur)), + strconv.FormatBool(inv.InsideTx), inv.TxName) +} + +func (builder *FilterChainBuilder) reportTxn(ctx context.Context, inv *orm.Invocation) { + dur := time.Now().Sub(inv.TxStartTime) / time.Millisecond + builder.summaryVec.WithLabelValues(inv.Method, inv.TxName, strconv.Itoa(int(dur)), + strconv.FormatBool(inv.InsideTx), inv.TxName) +} diff --git a/client/orm/filter/prometheus/filter_test.go b/client/orm/filter/prometheus/filter_test.go new file mode 100644 index 00000000..72b16038 --- /dev/null +++ b/client/orm/filter/prometheus/filter_test.go @@ -0,0 +1,62 @@ +// Copyright 2020 beego +// +// Licensed 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 prometheus + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/client/orm" +) + +func TestFilterChainBuilder_FilterChain1(t *testing.T) { + next := func(ctx context.Context, inv *orm.Invocation) []interface{} { + inv.Method = "coming" + return []interface{}{} + } + builder := &FilterChainBuilder{} + filter := builder.FilterChain(next) + + assert.NotNil(t, builder.summaryVec) + assert.NotNil(t, filter) + + inv := &orm.Invocation{} + filter(context.Background(), inv) + assert.Equal(t, "coming", inv.Method) + + inv = &orm.Invocation{ + Method: "Hello", + TxStartTime: time.Now(), + } + builder.reportTxn(context.Background(), inv) + + inv = &orm.Invocation{ + Method: "Begin", + } + + ctx := context.Background() + // it will be ignored + builder.report(ctx, inv, time.Second) + + inv.Method = "Commit" + builder.report(ctx, inv, time.Second) + + inv.Method = "Update" + builder.report(ctx, inv, time.Second) + +} diff --git a/client/orm/filter_orm_decorator.go b/client/orm/filter_orm_decorator.go new file mode 100644 index 00000000..9f837cba --- /dev/null +++ b/client/orm/filter_orm_decorator.go @@ -0,0 +1,514 @@ +// Copyright 2020 beego +// +// Licensed 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 orm + +import ( + "context" + "database/sql" + "reflect" + "time" + + "github.com/astaxie/beego/core/utils" +) + +const ( + TxNameKey = "TxName" +) + +var _ Ormer = new(filterOrmDecorator) +var _ TxOrmer = new(filterOrmDecorator) + +type filterOrmDecorator struct { + ormer + TxBeginner + TxCommitter + + root Filter + + insideTx bool + txStartTime time.Time + txName string +} + +func NewFilterOrmDecorator(delegate Ormer, filterChains ...FilterChain) Ormer { + res := &filterOrmDecorator{ + ormer: delegate, + TxBeginner: delegate, + root: func(ctx context.Context, inv *Invocation) []interface{} { + return inv.execute(ctx) + }, + } + + for i := len(filterChains) - 1; i >= 0; i-- { + node := filterChains[i] + res.root = node(res.root) + } + return res +} + +func NewFilterTxOrmDecorator(delegate TxOrmer, root Filter, txName string) TxOrmer { + res := &filterOrmDecorator{ + ormer: delegate, + TxCommitter: delegate, + root: root, + insideTx: true, + txStartTime: time.Now(), + txName: txName, + } + return res +} + +func (f *filterOrmDecorator) Read(md interface{}, cols ...string) error { + return f.ReadWithCtx(context.Background(), md, cols...) +} + +func (f *filterOrmDecorator) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error { + mi, _ := modelCache.getByMd(md) + inv := &Invocation{ + Method: "ReadWithCtx", + Args: []interface{}{md, cols}, + Md: md, + mi: mi, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + f: func(c context.Context) []interface{} { + err := f.ormer.ReadWithCtx(c, md, cols...) + return []interface{}{err} + }, + } + res := f.root(ctx, inv) + return f.convertError(res[0]) +} + +func (f *filterOrmDecorator) ReadForUpdate(md interface{}, cols ...string) error { + return f.ReadForUpdateWithCtx(context.Background(), md, cols...) +} + +func (f *filterOrmDecorator) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error { + mi, _ := modelCache.getByMd(md) + inv := &Invocation{ + Method: "ReadForUpdateWithCtx", + Args: []interface{}{md, cols}, + Md: md, + mi: mi, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + f: func(c context.Context) []interface{} { + err := f.ormer.ReadForUpdateWithCtx(c, md, cols...) + return []interface{}{err} + }, + } + res := f.root(ctx, inv) + return f.convertError(res[0]) +} + +func (f *filterOrmDecorator) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) { + return f.ReadOrCreateWithCtx(context.Background(), md, col1, cols...) +} + +func (f *filterOrmDecorator) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) { + + mi, _ := modelCache.getByMd(md) + inv := &Invocation{ + Method: "ReadOrCreateWithCtx", + Args: []interface{}{md, col1, cols}, + Md: md, + mi: mi, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + f: func(c context.Context) []interface{} { + ok, res, err := f.ormer.ReadOrCreateWithCtx(c, md, col1, cols...) + return []interface{}{ok, res, err} + }, + } + res := f.root(ctx, inv) + return res[0].(bool), res[1].(int64), f.convertError(res[2]) +} + +func (f *filterOrmDecorator) LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) { + return f.LoadRelatedWithCtx(context.Background(), md, name, args...) +} + +func (f *filterOrmDecorator) LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) { + + mi, _ := modelCache.getByMd(md) + inv := &Invocation{ + Method: "LoadRelatedWithCtx", + Args: []interface{}{md, name, args}, + Md: md, + mi: mi, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + f: func(c context.Context) []interface{} { + res, err := f.ormer.LoadRelatedWithCtx(c, md, name, args...) + return []interface{}{res, err} + }, + } + res := f.root(ctx, inv) + return res[0].(int64), f.convertError(res[1]) +} + +func (f *filterOrmDecorator) QueryM2M(md interface{}, name string) QueryM2Mer { + return f.QueryM2MWithCtx(context.Background(), md, name) +} + +func (f *filterOrmDecorator) QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer { + + mi, _ := modelCache.getByMd(md) + inv := &Invocation{ + Method: "QueryM2MWithCtx", + Args: []interface{}{md, name}, + Md: md, + mi: mi, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + f: func(c context.Context) []interface{} { + res := f.ormer.QueryM2MWithCtx(c, md, name) + return []interface{}{res} + }, + } + res := f.root(ctx, inv) + if res[0] == nil { + return nil + } + return res[0].(QueryM2Mer) +} + +func (f *filterOrmDecorator) QueryTable(ptrStructOrTableName interface{}) QuerySeter { + return f.QueryTableWithCtx(context.Background(), ptrStructOrTableName) +} + +func (f *filterOrmDecorator) QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) QuerySeter { + var ( + name string + md interface{} + mi *modelInfo + ) + + if table, ok := ptrStructOrTableName.(string); ok { + name = table + } else { + name = getFullName(indirectType(reflect.TypeOf(ptrStructOrTableName))) + md = ptrStructOrTableName + } + + if m, ok := modelCache.getByFullName(name); ok { + mi = m + } + + inv := &Invocation{ + Method: "QueryTableWithCtx", + Args: []interface{}{ptrStructOrTableName}, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + Md: md, + mi: mi, + f: func(c context.Context) []interface{} { + res := f.ormer.QueryTableWithCtx(c, ptrStructOrTableName) + return []interface{}{res} + }, + } + res := f.root(ctx, inv) + + if res[0] == nil { + return nil + } + return res[0].(QuerySeter) +} + +func (f *filterOrmDecorator) DBStats() *sql.DBStats { + inv := &Invocation{ + Method: "DBStats", + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + f: func(c context.Context) []interface{} { + res := f.ormer.DBStats() + return []interface{}{res} + }, + } + res := f.root(context.Background(), inv) + + if res[0] == nil { + return nil + } + + return res[0].(*sql.DBStats) +} + +func (f *filterOrmDecorator) Insert(md interface{}) (int64, error) { + return f.InsertWithCtx(context.Background(), md) +} + +func (f *filterOrmDecorator) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) { + mi, _ := modelCache.getByMd(md) + inv := &Invocation{ + Method: "InsertWithCtx", + Args: []interface{}{md}, + Md: md, + mi: mi, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + f: func(c context.Context) []interface{} { + res, err := f.ormer.InsertWithCtx(c, md) + return []interface{}{res, err} + }, + } + res := f.root(ctx, inv) + return res[0].(int64), f.convertError(res[1]) +} + +func (f *filterOrmDecorator) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) { + return f.InsertOrUpdateWithCtx(context.Background(), md, colConflitAndArgs...) +} + +func (f *filterOrmDecorator) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) { + mi, _ := modelCache.getByMd(md) + inv := &Invocation{ + Method: "InsertOrUpdateWithCtx", + Args: []interface{}{md, colConflitAndArgs}, + Md: md, + mi: mi, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + f: func(c context.Context) []interface{} { + res, err := f.ormer.InsertOrUpdateWithCtx(c, md, colConflitAndArgs...) + return []interface{}{res, err} + }, + } + res := f.root(ctx, inv) + return res[0].(int64), f.convertError(res[1]) +} + +func (f *filterOrmDecorator) InsertMulti(bulk int, mds interface{}) (int64, error) { + return f.InsertMultiWithCtx(context.Background(), bulk, mds) +} + +// InsertMultiWithCtx uses the first element's model info +func (f *filterOrmDecorator) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) { + var ( + md interface{} + mi *modelInfo + ) + + sind := reflect.Indirect(reflect.ValueOf(mds)) + + if (sind.Kind() == reflect.Array || sind.Kind() == reflect.Slice) && sind.Len() > 0 { + ind := reflect.Indirect(sind.Index(0)) + md = ind.Interface() + mi, _ = modelCache.getByMd(md) + } + + inv := &Invocation{ + Method: "InsertMultiWithCtx", + Args: []interface{}{bulk, mds}, + Md: md, + mi: mi, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + f: func(c context.Context) []interface{} { + res, err := f.ormer.InsertMultiWithCtx(c, bulk, mds) + return []interface{}{res, err} + }, + } + res := f.root(ctx, inv) + return res[0].(int64), f.convertError(res[1]) +} + +func (f *filterOrmDecorator) Update(md interface{}, cols ...string) (int64, error) { + return f.UpdateWithCtx(context.Background(), md, cols...) +} + +func (f *filterOrmDecorator) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) { + mi, _ := modelCache.getByMd(md) + inv := &Invocation{ + Method: "UpdateWithCtx", + Args: []interface{}{md, cols}, + Md: md, + mi: mi, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + f: func(c context.Context) []interface{} { + res, err := f.ormer.UpdateWithCtx(c, md, cols...) + return []interface{}{res, err} + }, + } + res := f.root(ctx, inv) + return res[0].(int64), f.convertError(res[1]) +} + +func (f *filterOrmDecorator) Delete(md interface{}, cols ...string) (int64, error) { + return f.DeleteWithCtx(context.Background(), md, cols...) +} + +func (f *filterOrmDecorator) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) { + mi, _ := modelCache.getByMd(md) + inv := &Invocation{ + Method: "DeleteWithCtx", + Args: []interface{}{md, cols}, + Md: md, + mi: mi, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + f: func(c context.Context) []interface{} { + res, err := f.ormer.DeleteWithCtx(c, md, cols...) + return []interface{}{res, err} + }, + } + res := f.root(ctx, inv) + return res[0].(int64), f.convertError(res[1]) +} + +func (f *filterOrmDecorator) Raw(query string, args ...interface{}) RawSeter { + return f.RawWithCtx(context.Background(), query, args...) +} + +func (f *filterOrmDecorator) RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter { + inv := &Invocation{ + Method: "RawWithCtx", + Args: []interface{}{query, args}, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + f: func(c context.Context) []interface{} { + res := f.ormer.RawWithCtx(c, query, args...) + return []interface{}{res} + }, + } + res := f.root(ctx, inv) + + if res[0] == nil { + return nil + } + return res[0].(RawSeter) +} + +func (f *filterOrmDecorator) Driver() Driver { + inv := &Invocation{ + Method: "Driver", + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + f: func(c context.Context) []interface{} { + res := f.ormer.Driver() + return []interface{}{res} + }, + } + res := f.root(context.Background(), inv) + if res[0] == nil { + return nil + } + return res[0].(Driver) +} + +func (f *filterOrmDecorator) Begin() (TxOrmer, error) { + return f.BeginWithCtxAndOpts(context.Background(), nil) +} + +func (f *filterOrmDecorator) BeginWithCtx(ctx context.Context) (TxOrmer, error) { + return f.BeginWithCtxAndOpts(ctx, nil) +} + +func (f *filterOrmDecorator) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) { + return f.BeginWithCtxAndOpts(context.Background(), opts) +} + +func (f *filterOrmDecorator) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) { + inv := &Invocation{ + Method: "BeginWithCtxAndOpts", + Args: []interface{}{opts}, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + f: func(c context.Context) []interface{} { + res, err := f.TxBeginner.BeginWithCtxAndOpts(c, opts) + res = NewFilterTxOrmDecorator(res, f.root, getTxNameFromCtx(c)) + return []interface{}{res, err} + }, + } + res := f.root(ctx, inv) + return res[0].(TxOrmer), f.convertError(res[1]) +} + +func (f *filterOrmDecorator) DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error { + return f.DoTxWithCtxAndOpts(context.Background(), nil, task) +} + +func (f *filterOrmDecorator) DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error { + return f.DoTxWithCtxAndOpts(ctx, nil, task) +} + +func (f *filterOrmDecorator) DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error { + return f.DoTxWithCtxAndOpts(context.Background(), opts, task) +} + +func (f *filterOrmDecorator) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error { + inv := &Invocation{ + Method: "DoTxWithCtxAndOpts", + Args: []interface{}{opts, task}, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + TxName: getTxNameFromCtx(ctx), + f: func(c context.Context) []interface{} { + err := doTxTemplate(f, c, opts, task) + return []interface{}{err} + }, + } + res := f.root(ctx, inv) + return f.convertError(res[0]) +} + +func (f *filterOrmDecorator) Commit() error { + inv := &Invocation{ + Method: "Commit", + Args: []interface{}{}, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + TxName: f.txName, + f: func(c context.Context) []interface{} { + err := f.TxCommitter.Commit() + return []interface{}{err} + }, + } + res := f.root(context.Background(), inv) + return f.convertError(res[0]) +} + +func (f *filterOrmDecorator) Rollback() error { + inv := &Invocation{ + Method: "Rollback", + Args: []interface{}{}, + InsideTx: f.insideTx, + TxStartTime: f.txStartTime, + TxName: f.txName, + f: func(c context.Context) []interface{} { + err := f.TxCommitter.Rollback() + return []interface{}{err} + }, + } + res := f.root(context.Background(), inv) + return f.convertError(res[0]) +} + +func (f *filterOrmDecorator) convertError(v interface{}) error { + if v == nil { + return nil + } + return v.(error) +} + +func getTxNameFromCtx(ctx context.Context) string { + txName := "" + if n, ok := ctx.Value(TxNameKey).(string); ok { + txName = n + } + return txName +} diff --git a/client/orm/filter_orm_decorator_test.go b/client/orm/filter_orm_decorator_test.go new file mode 100644 index 00000000..671ca001 --- /dev/null +++ b/client/orm/filter_orm_decorator_test.go @@ -0,0 +1,434 @@ +// Copyright 2020 beego +// +// Licensed 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 orm + +import ( + "context" + "database/sql" + "errors" + "sync" + "testing" + + "github.com/astaxie/beego/core/utils" + + "github.com/stretchr/testify/assert" +) + +func TestFilterOrmDecorator_Read(t *testing.T) { + + register() + + o := &filterMockOrm{} + od := NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + assert.Equal(t, "ReadWithCtx", inv.Method) + assert.Equal(t, 2, len(inv.Args)) + assert.Equal(t, "FILTER_TEST", inv.GetTableName()) + return next(ctx, inv) + } + }) + + fte := &FilterTestEntity{} + err := od.Read(fte) + assert.NotNil(t, err) + assert.Equal(t, "read error", err.Error()) +} + +func TestFilterOrmDecorator_BeginTx(t *testing.T) { + register() + + o := &filterMockOrm{} + od := NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + if inv.Method == "BeginWithCtxAndOpts" { + assert.Equal(t, 1, len(inv.Args)) + assert.Equal(t, "", inv.GetTableName()) + assert.False(t, inv.InsideTx) + } else if inv.Method == "Commit" { + assert.Equal(t, 0, len(inv.Args)) + assert.Equal(t, "Commit_tx", inv.TxName) + assert.Equal(t, "", inv.GetTableName()) + assert.True(t, inv.InsideTx) + } else if inv.Method == "Rollback" { + assert.Equal(t, 0, len(inv.Args)) + assert.Equal(t, "Rollback_tx", inv.TxName) + assert.Equal(t, "", inv.GetTableName()) + assert.True(t, inv.InsideTx) + } else { + t.Fail() + } + + return next(ctx, inv) + } + }) + to, err := od.Begin() + assert.True(t, validateBeginResult(t, to, err)) + + to, err = od.BeginWithOpts(nil) + assert.True(t, validateBeginResult(t, to, err)) + + ctx := context.WithValue(context.Background(), TxNameKey, "Commit_tx") + to, err = od.BeginWithCtx(ctx) + assert.True(t, validateBeginResult(t, to, err)) + + err = to.Commit() + assert.NotNil(t, err) + assert.Equal(t, "commit", err.Error()) + + ctx = context.WithValue(context.Background(), TxNameKey, "Rollback_tx") + to, err = od.BeginWithCtxAndOpts(ctx, nil) + assert.True(t, validateBeginResult(t, to, err)) + + err = to.Rollback() + assert.NotNil(t, err) + assert.Equal(t, "rollback", err.Error()) +} + +func TestFilterOrmDecorator_DBStats(t *testing.T) { + o := &filterMockOrm{} + od := NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + assert.Equal(t, "DBStats", inv.Method) + assert.Equal(t, 0, len(inv.Args)) + assert.Equal(t, "", inv.GetTableName()) + return next(ctx, inv) + } + }) + res := od.DBStats() + assert.NotNil(t, res) + assert.Equal(t, -1, res.MaxOpenConnections) +} + +func TestFilterOrmDecorator_Delete(t *testing.T) { + register() + o := &filterMockOrm{} + od := NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + assert.Equal(t, "DeleteWithCtx", inv.Method) + assert.Equal(t, 2, len(inv.Args)) + assert.Equal(t, "FILTER_TEST", inv.GetTableName()) + return next(ctx, inv) + } + }) + res, err := od.Delete(&FilterTestEntity{}) + assert.NotNil(t, err) + assert.Equal(t, "delete error", err.Error()) + assert.Equal(t, int64(-2), res) +} + +func TestFilterOrmDecorator_DoTx(t *testing.T) { + o := &filterMockOrm{} + od := NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + if inv.Method == "DoTxWithCtxAndOpts" { + assert.Equal(t, 2, len(inv.Args)) + assert.Equal(t, "", inv.GetTableName()) + assert.False(t, inv.InsideTx) + } + return next(ctx, inv) + } + }) + + err := od.DoTx(func(c context.Context, txOrm TxOrmer) error { + return nil + }) + assert.NotNil(t, err) + + err = od.DoTxWithCtx(context.Background(), func(c context.Context, txOrm TxOrmer) error { + return nil + }) + assert.NotNil(t, err) + + err = od.DoTxWithOpts(nil, func(c context.Context, txOrm TxOrmer) error { + return nil + }) + assert.NotNil(t, err) + + od = NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + if inv.Method == "DoTxWithCtxAndOpts" { + assert.Equal(t, 2, len(inv.Args)) + assert.Equal(t, "", inv.GetTableName()) + assert.Equal(t, "do tx name", inv.TxName) + assert.False(t, inv.InsideTx) + } + return next(ctx, inv) + } + }) + + ctx := context.WithValue(context.Background(), TxNameKey, "do tx name") + err = od.DoTxWithCtxAndOpts(ctx, nil, func(c context.Context, txOrm TxOrmer) error { + return nil + }) + assert.NotNil(t, err) +} + +func TestFilterOrmDecorator_Driver(t *testing.T) { + o := &filterMockOrm{} + od := NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + assert.Equal(t, "Driver", inv.Method) + assert.Equal(t, 0, len(inv.Args)) + assert.Equal(t, "", inv.GetTableName()) + assert.False(t, inv.InsideTx) + return next(ctx, inv) + } + }) + res := od.Driver() + assert.Nil(t, res) +} + +func TestFilterOrmDecorator_Insert(t *testing.T) { + register() + o := &filterMockOrm{} + od := NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + assert.Equal(t, "InsertWithCtx", inv.Method) + assert.Equal(t, 1, len(inv.Args)) + assert.Equal(t, "FILTER_TEST", inv.GetTableName()) + assert.False(t, inv.InsideTx) + return next(ctx, inv) + } + }) + + i, err := od.Insert(&FilterTestEntity{}) + assert.NotNil(t, err) + assert.Equal(t, "insert error", err.Error()) + assert.Equal(t, int64(100), i) +} + +func TestFilterOrmDecorator_InsertMulti(t *testing.T) { + register() + o := &filterMockOrm{} + od := NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + assert.Equal(t, "InsertMultiWithCtx", inv.Method) + assert.Equal(t, 2, len(inv.Args)) + assert.Equal(t, "FILTER_TEST", inv.GetTableName()) + assert.False(t, inv.InsideTx) + return next(ctx, inv) + } + }) + + bulk := []*FilterTestEntity{&FilterTestEntity{}, &FilterTestEntity{}} + i, err := od.InsertMulti(2, bulk) + assert.NotNil(t, err) + assert.Equal(t, "insert multi error", err.Error()) + assert.Equal(t, int64(2), i) +} + +func TestFilterOrmDecorator_InsertOrUpdate(t *testing.T) { + register() + o := &filterMockOrm{} + od := NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + assert.Equal(t, "InsertOrUpdateWithCtx", inv.Method) + assert.Equal(t, 2, len(inv.Args)) + assert.Equal(t, "FILTER_TEST", inv.GetTableName()) + assert.False(t, inv.InsideTx) + return next(ctx, inv) + } + }) + i, err := od.InsertOrUpdate(&FilterTestEntity{}) + assert.NotNil(t, err) + assert.Equal(t, "insert or update error", err.Error()) + assert.Equal(t, int64(1), i) +} + +func TestFilterOrmDecorator_LoadRelated(t *testing.T) { + o := &filterMockOrm{} + od := NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + assert.Equal(t, "LoadRelatedWithCtx", inv.Method) + assert.Equal(t, 3, len(inv.Args)) + assert.Equal(t, "FILTER_TEST", inv.GetTableName()) + assert.False(t, inv.InsideTx) + return next(ctx, inv) + } + }) + i, err := od.LoadRelated(&FilterTestEntity{}, "hello") + assert.NotNil(t, err) + assert.Equal(t, "load related error", err.Error()) + assert.Equal(t, int64(99), i) +} + +func TestFilterOrmDecorator_QueryM2M(t *testing.T) { + o := &filterMockOrm{} + od := NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + assert.Equal(t, "QueryM2MWithCtx", inv.Method) + assert.Equal(t, 2, len(inv.Args)) + assert.Equal(t, "FILTER_TEST", inv.GetTableName()) + assert.False(t, inv.InsideTx) + return next(ctx, inv) + } + }) + res := od.QueryM2M(&FilterTestEntity{}, "hello") + assert.Nil(t, res) +} + +func TestFilterOrmDecorator_QueryTable(t *testing.T) { + register() + o := &filterMockOrm{} + od := NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + assert.Equal(t, "QueryTableWithCtx", inv.Method) + assert.Equal(t, 1, len(inv.Args)) + assert.Equal(t, "FILTER_TEST", inv.GetTableName()) + assert.False(t, inv.InsideTx) + return next(ctx, inv) + } + }) + res := od.QueryTable(&FilterTestEntity{}) + assert.Nil(t, res) +} + +func TestFilterOrmDecorator_Raw(t *testing.T) { + register() + o := &filterMockOrm{} + od := NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + assert.Equal(t, "RawWithCtx", inv.Method) + assert.Equal(t, 2, len(inv.Args)) + assert.Equal(t, "", inv.GetTableName()) + assert.False(t, inv.InsideTx) + return next(ctx, inv) + } + }) + res := od.Raw("hh") + assert.Nil(t, res) +} + +func TestFilterOrmDecorator_ReadForUpdate(t *testing.T) { + register() + o := &filterMockOrm{} + od := NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + assert.Equal(t, "ReadForUpdateWithCtx", inv.Method) + assert.Equal(t, 2, len(inv.Args)) + assert.Equal(t, "FILTER_TEST", inv.GetTableName()) + assert.False(t, inv.InsideTx) + return next(ctx, inv) + } + }) + err := od.ReadForUpdate(&FilterTestEntity{}) + assert.NotNil(t, err) + assert.Equal(t, "read for update error", err.Error()) +} + +func TestFilterOrmDecorator_ReadOrCreate(t *testing.T) { + register() + o := &filterMockOrm{} + od := NewFilterOrmDecorator(o, func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + assert.Equal(t, "ReadOrCreateWithCtx", inv.Method) + assert.Equal(t, 3, len(inv.Args)) + assert.Equal(t, "FILTER_TEST", inv.GetTableName()) + assert.False(t, inv.InsideTx) + return next(ctx, inv) + } + }) + ok, i, err := od.ReadOrCreate(&FilterTestEntity{}, "name") + assert.NotNil(t, err) + assert.Equal(t, "read or create error", err.Error()) + assert.True(t, ok) + assert.Equal(t, int64(13), i) +} + +var _ Ormer = new(filterMockOrm) + +// filterMockOrm is only used in this test file +type filterMockOrm struct { + DoNothingOrm +} + +func (f *filterMockOrm) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) { + return true, 13, errors.New("read or create error") +} + +func (f *filterMockOrm) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error { + return errors.New("read for update error") +} + +func (f *filterMockOrm) LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) { + return 99, errors.New("load related error") +} + +func (f *filterMockOrm) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) { + return 1, errors.New("insert or update error") +} + +func (f *filterMockOrm) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) { + return 2, errors.New("insert multi error") +} + +func (f *filterMockOrm) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) { + return 100, errors.New("insert error") +} + +func (f *filterMockOrm) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(c context.Context, txOrm TxOrmer) error) error { + return task(ctx, nil) +} + +func (f *filterMockOrm) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) { + return -2, errors.New("delete error") +} + +func (f *filterMockOrm) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) { + return &filterMockOrm{}, errors.New("begin tx") +} + +func (f *filterMockOrm) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error { + return errors.New("read error") +} + +func (f *filterMockOrm) Commit() error { + return errors.New("commit") +} + +func (f *filterMockOrm) Rollback() error { + return errors.New("rollback") +} + +func (f *filterMockOrm) DBStats() *sql.DBStats { + return &sql.DBStats{ + MaxOpenConnections: -1, + } +} + +func validateBeginResult(t *testing.T, to TxOrmer, err error) bool { + assert.NotNil(t, err) + assert.Equal(t, "begin tx", err.Error()) + _, ok := to.(*filterOrmDecorator).TxCommitter.(*filterMockOrm) + assert.True(t, ok) + return true +} + +var filterTestEntityRegisterOnce sync.Once + +type FilterTestEntity struct { + ID int + Name string +} + +func register() { + filterTestEntityRegisterOnce.Do(func() { + RegisterModel(&FilterTestEntity{}) + }) +} + +func (f *FilterTestEntity) TableName() string { + return "FILTER_TEST" +} diff --git a/client/orm/filter_test.go b/client/orm/filter_test.go new file mode 100644 index 00000000..f9c86039 --- /dev/null +++ b/client/orm/filter_test.go @@ -0,0 +1,32 @@ +// Copyright 2020 beego +// +// Licensed 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 orm + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAddGlobalFilterChain(t *testing.T) { + AddGlobalFilterChain(func(next Filter) Filter { + return func(ctx context.Context, inv *Invocation) []interface{} { + return next(ctx, inv) + } + }) + assert.Equal(t, 1, len(globalFilterChains)) + globalFilterChains = nil +} diff --git a/client/orm/hints/db_hints.go b/client/orm/hints/db_hints.go new file mode 100644 index 00000000..7bfe8eb0 --- /dev/null +++ b/client/orm/hints/db_hints.go @@ -0,0 +1,103 @@ +// Copyright 2020 beego-dev +// +// Licensed 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 hints + +import ( + "github.com/astaxie/beego/core/utils" +) + +const ( + //query level + KeyForceIndex = iota + KeyUseIndex + KeyIgnoreIndex + KeyForUpdate + KeyLimit + KeyOffset + KeyOrderBy + KeyRelDepth +) + +type Hint struct { + key interface{} + value interface{} +} + +var _ utils.KV = new(Hint) + +// GetKey return key +func (s *Hint) GetKey() interface{} { + return s.key +} + +// GetValue return value +func (s *Hint) GetValue() interface{} { + return s.value +} + +var _ utils.KV = new(Hint) + +// ForceIndex return a hint about ForceIndex +func ForceIndex(indexes ...string) *Hint { + return NewHint(KeyForceIndex, indexes) +} + +// UseIndex return a hint about UseIndex +func UseIndex(indexes ...string) *Hint { + return NewHint(KeyUseIndex, indexes) +} + +// IgnoreIndex return a hint about IgnoreIndex +func IgnoreIndex(indexes ...string) *Hint { + return NewHint(KeyIgnoreIndex, indexes) +} + +// ForUpdate return a hint about ForUpdate +func ForUpdate() *Hint { + return NewHint(KeyForUpdate, true) +} + +// DefaultRelDepth return a hint about DefaultRelDepth +func DefaultRelDepth() *Hint { + return NewHint(KeyRelDepth, true) +} + +// RelDepth return a hint about RelDepth +func RelDepth(d int) *Hint { + return NewHint(KeyRelDepth, d) +} + +// Limit return a hint about Limit +func Limit(d int64) *Hint { + return NewHint(KeyLimit, d) +} + +// Offset return a hint about Offset +func Offset(d int64) *Hint { + return NewHint(KeyOffset, d) +} + +// OrderBy return a hint about OrderBy +func OrderBy(s string) *Hint { + return NewHint(KeyOrderBy, s) +} + +// NewHint return a hint +func NewHint(key interface{}, value interface{}) *Hint { + return &Hint{ + key: key, + value: value, + } +} diff --git a/client/orm/hints/db_hints_test.go b/client/orm/hints/db_hints_test.go new file mode 100644 index 00000000..510f9f16 --- /dev/null +++ b/client/orm/hints/db_hints_test.go @@ -0,0 +1,127 @@ +// Copyright 2020 beego-dev +// +// Licensed 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 hints + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestNewHint_time(t *testing.T) { + key := "qweqwe" + value := time.Second + hint := NewHint(key, value) + + assert.Equal(t, hint.GetKey(), key) + assert.Equal(t, hint.GetValue(), value) +} + +func TestNewHint_int(t *testing.T) { + key := "qweqwe" + value := 281230 + hint := NewHint(key, value) + + assert.Equal(t, hint.GetKey(), key) + assert.Equal(t, hint.GetValue(), value) +} + +func TestNewHint_float(t *testing.T) { + key := "qweqwe" + value := 21.2459753 + hint := NewHint(key, value) + + assert.Equal(t, hint.GetKey(), key) + assert.Equal(t, hint.GetValue(), value) +} + +func TestForceIndex(t *testing.T) { + s := []string{`f_index1`, `f_index2`, `f_index3`} + hint := ForceIndex(s...) + assert.Equal(t, hint.GetValue(), s) + assert.Equal(t, hint.GetKey(), KeyForceIndex) +} + +func TestForceIndex_0(t *testing.T) { + var s []string + hint := ForceIndex(s...) + assert.Equal(t, hint.GetValue(), s) + assert.Equal(t, hint.GetKey(), KeyForceIndex) +} + +func TestIgnoreIndex(t *testing.T) { + s := []string{`i_index1`, `i_index2`, `i_index3`} + hint := IgnoreIndex(s...) + assert.Equal(t, hint.GetValue(), s) + assert.Equal(t, hint.GetKey(), KeyIgnoreIndex) +} + +func TestIgnoreIndex_0(t *testing.T) { + var s []string + hint := IgnoreIndex(s...) + assert.Equal(t, hint.GetValue(), s) + assert.Equal(t, hint.GetKey(), KeyIgnoreIndex) +} + +func TestUseIndex(t *testing.T) { + s := []string{`u_index1`, `u_index2`, `u_index3`} + hint := UseIndex(s...) + assert.Equal(t, hint.GetValue(), s) + assert.Equal(t, hint.GetKey(), KeyUseIndex) +} + +func TestUseIndex_0(t *testing.T) { + var s []string + hint := UseIndex(s...) + assert.Equal(t, hint.GetValue(), s) + assert.Equal(t, hint.GetKey(), KeyUseIndex) +} + +func TestForUpdate(t *testing.T) { + hint := ForUpdate() + assert.Equal(t, hint.GetValue(), true) + assert.Equal(t, hint.GetKey(), KeyForUpdate) +} + +func TestDefaultRelDepth(t *testing.T) { + hint := DefaultRelDepth() + assert.Equal(t, hint.GetValue(), true) + assert.Equal(t, hint.GetKey(), KeyRelDepth) +} + +func TestRelDepth(t *testing.T) { + hint := RelDepth(157965) + assert.Equal(t, hint.GetValue(), 157965) + assert.Equal(t, hint.GetKey(), KeyRelDepth) +} + +func TestLimit(t *testing.T) { + hint := Limit(1579625) + assert.Equal(t, hint.GetValue(), int64(1579625)) + assert.Equal(t, hint.GetKey(), KeyLimit) +} + +func TestOffset(t *testing.T) { + hint := Offset(int64(1572123965)) + assert.Equal(t, hint.GetValue(), int64(1572123965)) + assert.Equal(t, hint.GetKey(), KeyOffset) +} + +func TestOrderBy(t *testing.T) { + hint := OrderBy(`-ID`) + assert.Equal(t, hint.GetValue(), `-ID`) + assert.Equal(t, hint.GetKey(), KeyOrderBy) +} diff --git a/client/orm/invocation.go b/client/orm/invocation.go new file mode 100644 index 00000000..9e7c1974 --- /dev/null +++ b/client/orm/invocation.go @@ -0,0 +1,58 @@ +// Copyright 2020 beego +// +// Licensed 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 orm + +import ( + "context" + "time" +) + +// Invocation represents an "Orm" invocation +type Invocation struct { + Method string + // Md may be nil in some cases. It depends on method + Md interface{} + // the args are all arguments except context.Context + Args []interface{} + + mi *modelInfo + // f is the Orm operation + f func(ctx context.Context) []interface{} + + // insideTx indicates whether this is inside a transaction + InsideTx bool + TxStartTime time.Time + TxName string +} + +func (inv *Invocation) GetTableName() string { + if inv.mi != nil { + return inv.mi.table + } + return "" +} + +func (inv *Invocation) execute(ctx context.Context) []interface{} { + return inv.f(ctx) +} + +// GetPkFieldName return the primary key of this table +// if not found, "" is returned +func (inv *Invocation) GetPkFieldName() string { + if inv.mi.fields.pk != nil { + return inv.mi.fields.pk.name + } + return "" +} diff --git a/migration/ddl.go b/client/orm/migration/ddl.go similarity index 85% rename from migration/ddl.go rename to client/orm/migration/ddl.go index cd2c1c49..a396d39a 100644 --- a/migration/ddl.go +++ b/client/orm/migration/ddl.go @@ -17,7 +17,7 @@ package migration import ( "fmt" - "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/core/logs" ) // Index struct defines the structure of Index Columns @@ -31,7 +31,7 @@ type Unique struct { Columns []*Column } -//Column struct defines a single column of a table +// Column struct defines a single column of a table type Column struct { Name string Inc string @@ -84,7 +84,7 @@ func (m *Migration) NewCol(name string) *Column { return col } -//PriCol creates a new primary column and attaches it to m struct +// PriCol creates a new primary column and attaches it to m struct func (m *Migration) PriCol(name string) *Column { col := &Column{Name: name} m.AddColumns(col) @@ -92,7 +92,7 @@ func (m *Migration) PriCol(name string) *Column { return col } -//UniCol creates / appends columns to specified unique key and attaches it to m struct +// UniCol creates / appends columns to specified unique key and attaches it to m struct func (m *Migration) UniCol(uni, name string) *Column { col := &Column{Name: name} m.AddColumns(col) @@ -114,7 +114,7 @@ func (m *Migration) UniCol(uni, name string) *Column { return col } -//ForeignCol creates a new foreign column and returns the instance of column +// ForeignCol creates a new foreign column and returns the instance of column func (m *Migration) ForeignCol(colname, foreigncol, foreigntable string) (foreign *Foreign) { foreign = &Foreign{ForeignColumn: foreigncol, ForeignTable: foreigntable} @@ -123,25 +123,25 @@ func (m *Migration) ForeignCol(colname, foreigncol, foreigntable string) (foreig return foreign } -//SetOnDelete sets the on delete of foreign +// SetOnDelete sets the on delete of foreign func (foreign *Foreign) SetOnDelete(del string) *Foreign { foreign.OnDelete = "ON DELETE" + del return foreign } -//SetOnUpdate sets the on update of foreign +// SetOnUpdate sets the on update of foreign func (foreign *Foreign) SetOnUpdate(update string) *Foreign { foreign.OnUpdate = "ON UPDATE" + update return foreign } -//Remove marks the columns to be removed. -//it allows reverse m to create the column. +// Remove marks the columns to be removed. +// it allows reverse m to create the column. func (c *Column) Remove() { c.remove = true } -//SetAuto enables auto_increment of column (can be used once) +// SetAuto enables auto_increment of column (can be used once) func (c *Column) SetAuto(inc bool) *Column { if inc { c.Inc = "auto_increment" @@ -149,7 +149,7 @@ func (c *Column) SetAuto(inc bool) *Column { return c } -//SetNullable sets the column to be null +// SetNullable sets the column to be null func (c *Column) SetNullable(null bool) *Column { if null { c.Null = "" @@ -160,13 +160,13 @@ func (c *Column) SetNullable(null bool) *Column { return c } -//SetDefault sets the default value, prepend with "DEFAULT " +// SetDefault sets the default value, prepend with "DEFAULT " func (c *Column) SetDefault(def string) *Column { c.Default = "DEFAULT " + def return c } -//SetUnsigned sets the column to be unsigned int +// SetUnsigned sets the column to be unsigned int func (c *Column) SetUnsigned(unsign bool) *Column { if unsign { c.Unsign = "UNSIGNED" @@ -174,13 +174,13 @@ func (c *Column) SetUnsigned(unsign bool) *Column { return c } -//SetDataType sets the dataType of the column +// SetDataType sets the dataType of the column func (c *Column) SetDataType(dataType string) *Column { c.DataType = dataType return c } -//SetOldNullable allows reverting to previous nullable on reverse ms +// SetOldNullable allows reverting to previous nullable on reverse ms func (c *RenameColumn) SetOldNullable(null bool) *RenameColumn { if null { c.OldNull = "" @@ -191,13 +191,13 @@ func (c *RenameColumn) SetOldNullable(null bool) *RenameColumn { return c } -//SetOldDefault allows reverting to previous default on reverse ms +// SetOldDefault allows reverting to previous default on reverse ms func (c *RenameColumn) SetOldDefault(def string) *RenameColumn { c.OldDefault = def return c } -//SetOldUnsigned allows reverting to previous unsgined on reverse ms +// SetOldUnsigned allows reverting to previous unsgined on reverse ms func (c *RenameColumn) SetOldUnsigned(unsign bool) *RenameColumn { if unsign { c.OldUnsign = "UNSIGNED" @@ -205,19 +205,19 @@ func (c *RenameColumn) SetOldUnsigned(unsign bool) *RenameColumn { return c } -//SetOldDataType allows reverting to previous datatype on reverse ms +// SetOldDataType allows reverting to previous datatype on reverse ms func (c *RenameColumn) SetOldDataType(dataType string) *RenameColumn { c.OldDataType = dataType return c } -//SetPrimary adds the columns to the primary key (can only be used any number of times in only one m) +// SetPrimary adds the columns to the primary key (can only be used any number of times in only one m) func (c *Column) SetPrimary(m *Migration) *Column { m.Primary = append(m.Primary, c) return c } -//AddColumnsToUnique adds the columns to Unique Struct +// AddColumnsToUnique adds the columns to Unique Struct func (unique *Unique) AddColumnsToUnique(columns ...*Column) *Unique { unique.Columns = append(unique.Columns, columns...) @@ -225,7 +225,7 @@ func (unique *Unique) AddColumnsToUnique(columns ...*Column) *Unique { return unique } -//AddColumns adds columns to m struct +// AddColumns adds columns to m struct func (m *Migration) AddColumns(columns ...*Column) *Migration { m.Columns = append(m.Columns, columns...) @@ -233,38 +233,38 @@ func (m *Migration) AddColumns(columns ...*Column) *Migration { return m } -//AddPrimary adds the column to primary in m struct +// AddPrimary adds the column to primary in m struct func (m *Migration) AddPrimary(primary *Column) *Migration { m.Primary = append(m.Primary, primary) return m } -//AddUnique adds the column to unique in m struct +// AddUnique adds the column to unique in m struct func (m *Migration) AddUnique(unique *Unique) *Migration { m.Uniques = append(m.Uniques, unique) return m } -//AddForeign adds the column to foreign in m struct +// AddForeign adds the column to foreign in m struct func (m *Migration) AddForeign(foreign *Foreign) *Migration { m.Foreigns = append(m.Foreigns, foreign) return m } -//AddIndex adds the column to index in m struct +// AddIndex adds the column to index in m struct func (m *Migration) AddIndex(index *Index) *Migration { m.Indexes = append(m.Indexes, index) return m } -//RenameColumn allows renaming of columns +// RenameColumn allows renaming of columns func (m *Migration) RenameColumn(from, to string) *RenameColumn { rename := &RenameColumn{OldName: from, NewName: to} m.Renames = append(m.Renames, rename) return rename } -//GetSQL returns the generated sql depending on ModifyType +// GetSQL returns the generated sql depending on ModifyType func (m *Migration) GetSQL() (sql string) { sql = "" switch m.ModifyType { diff --git a/client/orm/migration/doc.go b/client/orm/migration/doc.go new file mode 100644 index 00000000..0c6564d4 --- /dev/null +++ b/client/orm/migration/doc.go @@ -0,0 +1,32 @@ +// Package migration enables you to generate migrations back and forth. It generates both migrations. +// +// //Creates a table +// m.CreateTable("tablename","InnoDB","utf8"); +// +// //Alter a table +// m.AlterTable("tablename") +// +// Standard Column Methods +// * SetDataType +// * SetNullable +// * SetDefault +// * SetUnsigned (use only on integer types unless produces error) +// +// //Sets a primary column, multiple calls allowed, standard column methods available +// m.PriCol("id").SetAuto(true).SetNullable(false).SetDataType("INT(10)").SetUnsigned(true) +// +// //UniCol Can be used multiple times, allows standard Column methods. Use same "index" string to add to same index +// m.UniCol("index","column") +// +// //Standard Column Initialisation, can call .Remove() after NewCol("") on alter to remove +// m.NewCol("name").SetDataType("VARCHAR(255) COLLATE utf8_unicode_ci").SetNullable(false) +// m.NewCol("value").SetDataType("DOUBLE(8,2)").SetNullable(false) +// +// //Rename Columns , only use with Alter table, doesn't works with Create, prefix standard column methods with "Old" to +// //create a true reversible migration eg: SetOldDataType("DOUBLE(12,3)") +// m.RenameColumn("from","to")... +// +// //Foreign Columns, single columns are only supported, SetOnDelete & SetOnUpdate are available, call appropriately. +// //Supports standard column methods, automatic reverse. +// m.ForeignCol("local_col","foreign_col","foreign_table") +package migration diff --git a/migration/migration.go b/client/orm/migration/migration.go similarity index 99% rename from migration/migration.go rename to client/orm/migration/migration.go index 5ddfd972..aeea12c6 100644 --- a/migration/migration.go +++ b/client/orm/migration/migration.go @@ -33,8 +33,8 @@ import ( "strings" "time" - "github.com/astaxie/beego/logs" - "github.com/astaxie/beego/orm" + "github.com/astaxie/beego/client/orm" + "github.com/astaxie/beego/core/logs" ) // const the data format for the bee generate migration datatype diff --git a/client/orm/model_utils_test.go b/client/orm/model_utils_test.go new file mode 100644 index 00000000..b65aadcb --- /dev/null +++ b/client/orm/model_utils_test.go @@ -0,0 +1,62 @@ +// Copyright 2020 +// +// Licensed 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 orm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type Interface struct { + Id int + Name string + + Index1 string + Index2 string + + Unique1 string + Unique2 string +} + +func (i *Interface) TableIndex() [][]string { + return [][]string{{"index1"}, {"index2"}} +} + +func (i *Interface) TableUnique() [][]string { + return [][]string{{"unique1"}, {"unique2"}} +} + +func (i *Interface) TableName() string { + return "INTERFACE_" +} + +func (i *Interface) TableEngine() string { + return "innodb" +} + +func TestDbBase_GetTables(t *testing.T) { + RegisterModel(&Interface{}) + mi, ok := modelCache.get("INTERFACE_") + assert.True(t, ok) + assert.NotNil(t, mi) + + engine := getTableEngine(mi.addrField) + assert.Equal(t, "innodb", engine) + uniques := getTableUnique(mi.addrField) + assert.Equal(t, [][]string{{"unique1"}, {"unique2"}}, uniques) + indexes := getTableIndex(mi.addrField) + assert.Equal(t, [][]string{{"index1"}, {"index2"}}, indexes) +} diff --git a/client/orm/models.go b/client/orm/models.go new file mode 100644 index 00000000..19941d2e --- /dev/null +++ b/client/orm/models.go @@ -0,0 +1,569 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "errors" + "fmt" + "reflect" + "runtime/debug" + "strings" + "sync" +) + +const ( + odCascade = "cascade" + odSetNULL = "set_null" + odSetDefault = "set_default" + odDoNothing = "do_nothing" + defaultStructTagName = "orm" + defaultStructTagDelim = ";" +) + +var ( + modelCache = NewModelCacheHandler() +) + +// model info collection +type _modelCache struct { + sync.RWMutex // only used outsite for bootStrap + orders []string + cache map[string]*modelInfo + cacheByFullName map[string]*modelInfo + done bool +} + +//NewModelCacheHandler generator of _modelCache +func NewModelCacheHandler() *_modelCache { + return &_modelCache{ + cache: make(map[string]*modelInfo), + cacheByFullName: make(map[string]*modelInfo), + } +} + +// get all model info +func (mc *_modelCache) all() map[string]*modelInfo { + m := make(map[string]*modelInfo, len(mc.cache)) + for k, v := range mc.cache { + m[k] = v + } + return m +} + +// get ordered model info +func (mc *_modelCache) allOrdered() []*modelInfo { + m := make([]*modelInfo, 0, len(mc.orders)) + for _, table := range mc.orders { + m = append(m, mc.cache[table]) + } + return m +} + +// get model info by table name +func (mc *_modelCache) get(table string) (mi *modelInfo, ok bool) { + mi, ok = mc.cache[table] + return +} + +// get model info by full name +func (mc *_modelCache) getByFullName(name string) (mi *modelInfo, ok bool) { + mi, ok = mc.cacheByFullName[name] + return +} + +func (mc *_modelCache) getByMd(md interface{}) (*modelInfo, bool) { + val := reflect.ValueOf(md) + ind := reflect.Indirect(val) + typ := ind.Type() + name := getFullName(typ) + return mc.getByFullName(name) +} + +// set model info to collection +func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo { + mii := mc.cache[table] + mc.cache[table] = mi + mc.cacheByFullName[mi.fullName] = mi + if mii == nil { + mc.orders = append(mc.orders, table) + } + return mii +} + +// clean all model info. +func (mc *_modelCache) clean() { + mc.Lock() + defer mc.Unlock() + + mc.orders = make([]string, 0) + mc.cache = make(map[string]*modelInfo) + mc.cacheByFullName = make(map[string]*modelInfo) + mc.done = false +} + +//bootstrap bootstrap for models +func (mc *_modelCache) bootstrap() { + mc.Lock() + defer mc.Unlock() + if mc.done { + return + } + var ( + err error + models map[string]*modelInfo + ) + if dataBaseCache.getDefault() == nil { + err = fmt.Errorf("must have one register DataBase alias named `default`") + goto end + } + + // set rel and reverse model + // RelManyToMany set the relTable + models = mc.all() + for _, mi := range models { + for _, fi := range mi.fields.columns { + if fi.rel || fi.reverse { + elm := fi.addrValue.Type().Elem() + if fi.fieldType == RelReverseMany || fi.fieldType == RelManyToMany { + elm = elm.Elem() + } + // check the rel or reverse model already register + name := getFullName(elm) + mii, ok := mc.getByFullName(name) + if !ok || mii.pkg != elm.PkgPath() { + err = fmt.Errorf("can not find rel in field `%s`, `%s` may be miss register", fi.fullName, elm.String()) + goto end + } + fi.relModelInfo = mii + + switch fi.fieldType { + case RelManyToMany: + if fi.relThrough != "" { + if i := strings.LastIndex(fi.relThrough, "."); i != -1 && len(fi.relThrough) > (i+1) { + pn := fi.relThrough[:i] + rmi, ok := mc.getByFullName(fi.relThrough) + if !ok || pn != rmi.pkg { + err = fmt.Errorf("field `%s` wrong rel_through value `%s` cannot find table", fi.fullName, fi.relThrough) + goto end + } + fi.relThroughModelInfo = rmi + fi.relTable = rmi.table + } else { + err = fmt.Errorf("field `%s` wrong rel_through value `%s`", fi.fullName, fi.relThrough) + goto end + } + } else { + i := newM2MModelInfo(mi, mii) + if fi.relTable != "" { + i.table = fi.relTable + } + if v := mc.set(i.table, i); v != nil { + err = fmt.Errorf("the rel table name `%s` already registered, cannot be use, please change one", fi.relTable) + goto end + } + fi.relTable = i.table + fi.relThroughModelInfo = i + } + + fi.relThroughModelInfo.isThrough = true + } + } + } + } + + // check the rel filed while the relModelInfo also has filed point to current model + // if not exist, add a new field to the relModelInfo + models = mc.all() + for _, mi := range models { + for _, fi := range mi.fields.fieldsRel { + switch fi.fieldType { + case RelForeignKey, RelOneToOne, RelManyToMany: + inModel := false + for _, ffi := range fi.relModelInfo.fields.fieldsReverse { + if ffi.relModelInfo == mi { + inModel = true + break + } + } + if !inModel { + rmi := fi.relModelInfo + ffi := new(fieldInfo) + ffi.name = mi.name + ffi.column = ffi.name + ffi.fullName = rmi.fullName + "." + ffi.name + ffi.reverse = true + ffi.relModelInfo = mi + ffi.mi = rmi + if fi.fieldType == RelOneToOne { + ffi.fieldType = RelReverseOne + } else { + ffi.fieldType = RelReverseMany + } + if !rmi.fields.Add(ffi) { + added := false + for cnt := 0; cnt < 5; cnt++ { + ffi.name = fmt.Sprintf("%s%d", mi.name, cnt) + ffi.column = ffi.name + ffi.fullName = rmi.fullName + "." + ffi.name + if added = rmi.fields.Add(ffi); added { + break + } + } + if !added { + panic(fmt.Errorf("cannot generate auto reverse field info `%s` to `%s`", fi.fullName, ffi.fullName)) + } + } + } + } + } + } + + models = mc.all() + for _, mi := range models { + for _, fi := range mi.fields.fieldsRel { + switch fi.fieldType { + case RelManyToMany: + for _, ffi := range fi.relThroughModelInfo.fields.fieldsRel { + switch ffi.fieldType { + case RelOneToOne, RelForeignKey: + if ffi.relModelInfo == fi.relModelInfo { + fi.reverseFieldInfoTwo = ffi + } + if ffi.relModelInfo == mi { + fi.reverseField = ffi.name + fi.reverseFieldInfo = ffi + } + } + } + if fi.reverseFieldInfoTwo == nil { + err = fmt.Errorf("can not find m2m field for m2m model `%s`, ensure your m2m model defined correct", + fi.relThroughModelInfo.fullName) + goto end + } + } + } + } + + models = mc.all() + for _, mi := range models { + for _, fi := range mi.fields.fieldsReverse { + switch fi.fieldType { + case RelReverseOne: + found := false + mForA: + for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelOneToOne] { + if ffi.relModelInfo == mi { + found = true + fi.reverseField = ffi.name + fi.reverseFieldInfo = ffi + + ffi.reverseField = fi.name + ffi.reverseFieldInfo = fi + break mForA + } + } + if !found { + err = fmt.Errorf("reverse field `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName) + goto end + } + case RelReverseMany: + found := false + mForB: + for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelForeignKey] { + if ffi.relModelInfo == mi { + found = true + fi.reverseField = ffi.name + fi.reverseFieldInfo = ffi + + ffi.reverseField = fi.name + ffi.reverseFieldInfo = fi + + break mForB + } + } + if !found { + mForC: + for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelManyToMany] { + conditions := fi.relThrough != "" && fi.relThrough == ffi.relThrough || + fi.relTable != "" && fi.relTable == ffi.relTable || + fi.relThrough == "" && fi.relTable == "" + if ffi.relModelInfo == mi && conditions { + found = true + + fi.reverseField = ffi.reverseFieldInfoTwo.name + fi.reverseFieldInfo = ffi.reverseFieldInfoTwo + fi.relThroughModelInfo = ffi.relThroughModelInfo + fi.reverseFieldInfoTwo = ffi.reverseFieldInfo + fi.reverseFieldInfoM2M = ffi + ffi.reverseFieldInfoM2M = fi + + break mForC + } + } + } + if !found { + err = fmt.Errorf("reverse field for `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName) + goto end + } + } + } + } + +end: + if err != nil { + fmt.Println(err) + debug.PrintStack() + } + mc.done = true + return +} + +// register register models to model cache +func (mc *_modelCache) register(prefixOrSuffixStr string, prefixOrSuffix bool, models ...interface{}) (err error) { + if mc.done { + err = fmt.Errorf("register must be run before BootStrap") + return + } + + for _, model := range models { + val := reflect.ValueOf(model) + typ := reflect.Indirect(val).Type() + + if val.Kind() != reflect.Ptr { + err = fmt.Errorf(" cannot use non-ptr model struct `%s`", getFullName(typ)) + return + } + // For this case: + // u := &User{} + // registerModel(&u) + if typ.Kind() == reflect.Ptr { + err = fmt.Errorf(" only allow ptr model struct, it looks you use two reference to the struct `%s`", typ) + return + } + + table := getTableName(val) + + if prefixOrSuffixStr != "" { + if prefixOrSuffix { + table = prefixOrSuffixStr + table + } else { + table = table + prefixOrSuffixStr + } + } + + // models's fullname is pkgpath + struct name + name := getFullName(typ) + if _, ok := mc.getByFullName(name); ok { + err = fmt.Errorf(" model `%s` repeat register, must be unique\n", name) + return + } + + if _, ok := mc.get(table); ok { + err = fmt.Errorf(" table name `%s` repeat register, must be unique\n", table) + return + } + + mi := newModelInfo(val) + if mi.fields.pk == nil { + outFor: + for _, fi := range mi.fields.fieldsDB { + if strings.ToLower(fi.name) == "id" { + switch fi.addrValue.Elem().Kind() { + case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64: + fi.auto = true + fi.pk = true + mi.fields.pk = fi + break outFor + } + } + } + + if mi.fields.pk == nil { + err = fmt.Errorf(" `%s` needs a primary key field, default is to use 'id' if not set\n", name) + return + } + + } + + mi.table = table + mi.pkg = typ.PkgPath() + mi.model = model + mi.manual = true + + mc.set(table, mi) + } + return +} + +//getDbDropSQL get database scheme drop sql queries +func (mc *_modelCache) getDbDropSQL(al *alias) (queries []string, err error) { + if len(mc.cache) == 0 { + err = errors.New("no Model found, need register your model") + return + } + + Q := al.DbBaser.TableQuote() + + for _, mi := range mc.allOrdered() { + queries = append(queries, fmt.Sprintf(`DROP TABLE IF EXISTS %s%s%s`, Q, mi.table, Q)) + } + return queries, nil +} + +//getDbCreateSQL get database scheme creation sql queries +func (mc *_modelCache) getDbCreateSQL(al *alias) (queries []string, tableIndexes map[string][]dbIndex, err error) { + if len(mc.cache) == 0 { + err = errors.New("no Model found, need register your model") + return + } + + Q := al.DbBaser.TableQuote() + T := al.DbBaser.DbTypes() + sep := fmt.Sprintf("%s, %s", Q, Q) + + tableIndexes = make(map[string][]dbIndex) + + for _, mi := range mc.allOrdered() { + sql := fmt.Sprintf("-- %s\n", strings.Repeat("-", 50)) + sql += fmt.Sprintf("-- Table Structure for `%s`\n", mi.fullName) + sql += fmt.Sprintf("-- %s\n", strings.Repeat("-", 50)) + + sql += fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s%s%s (\n", Q, mi.table, Q) + + columns := make([]string, 0, len(mi.fields.fieldsDB)) + + sqlIndexes := [][]string{} + + for _, fi := range mi.fields.fieldsDB { + + column := fmt.Sprintf(" %s%s%s ", Q, fi.column, Q) + col := getColumnTyp(al, fi) + + if fi.auto { + switch al.Driver { + case DRSqlite, DRPostgres: + column += T["auto"] + default: + column += col + " " + T["auto"] + } + } else if fi.pk { + column += col + " " + T["pk"] + } else { + column += col + + if !fi.null { + column += " " + "NOT NULL" + } + + //if fi.initial.String() != "" { + // column += " DEFAULT " + fi.initial.String() + //} + + // Append attribute DEFAULT + column += getColumnDefault(fi) + + if fi.unique { + column += " " + "UNIQUE" + } + + if fi.index { + sqlIndexes = append(sqlIndexes, []string{fi.column}) + } + } + + if strings.Contains(column, "%COL%") { + column = strings.Replace(column, "%COL%", fi.column, -1) + } + + if fi.description != "" && al.Driver != DRSqlite { + column += " " + fmt.Sprintf("COMMENT '%s'", fi.description) + } + + columns = append(columns, column) + } + + if mi.model != nil { + allnames := getTableUnique(mi.addrField) + if !mi.manual && len(mi.uniques) > 0 { + allnames = append(allnames, mi.uniques) + } + for _, names := range allnames { + cols := make([]string, 0, len(names)) + for _, name := range names { + if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol { + cols = append(cols, fi.column) + } else { + panic(fmt.Errorf("cannot found column `%s` when parse UNIQUE in `%s.TableUnique`", name, mi.fullName)) + } + } + column := fmt.Sprintf(" UNIQUE (%s%s%s)", Q, strings.Join(cols, sep), Q) + columns = append(columns, column) + } + } + + sql += strings.Join(columns, ",\n") + sql += "\n)" + + if al.Driver == DRMySQL { + var engine string + if mi.model != nil { + engine = getTableEngine(mi.addrField) + } + if engine == "" { + engine = al.Engine + } + sql += " ENGINE=" + engine + } + + sql += ";" + queries = append(queries, sql) + + if mi.model != nil { + for _, names := range getTableIndex(mi.addrField) { + cols := make([]string, 0, len(names)) + for _, name := range names { + if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol { + cols = append(cols, fi.column) + } else { + panic(fmt.Errorf("cannot found column `%s` when parse INDEX in `%s.TableIndex`", name, mi.fullName)) + } + } + sqlIndexes = append(sqlIndexes, cols) + } + } + + for _, names := range sqlIndexes { + name := mi.table + "_" + strings.Join(names, "_") + cols := strings.Join(names, sep) + sql := fmt.Sprintf("CREATE INDEX %s%s%s ON %s%s%s (%s%s%s);", Q, name, Q, Q, mi.table, Q, Q, cols, Q) + + index := dbIndex{} + index.Table = mi.table + index.Name = name + index.SQL = sql + + tableIndexes[mi.table] = append(tableIndexes[mi.table], index) + } + + } + + return +} + +// ResetModelCache Clean model cache. Then you can re-RegisterModel. +// Common use this api for test case. +func ResetModelCache() { + modelCache.clean() +} diff --git a/client/orm/models_boot.go b/client/orm/models_boot.go new file mode 100644 index 00000000..9a0ce893 --- /dev/null +++ b/client/orm/models_boot.go @@ -0,0 +1,40 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +// RegisterModel register models +func RegisterModel(models ...interface{}) { + RegisterModelWithPrefix("", models...) +} + +// RegisterModelWithPrefix register models with a prefix +func RegisterModelWithPrefix(prefix string, models ...interface{}) { + if err := modelCache.register(prefix, true, models...); err != nil { + panic(err) + } +} + +// RegisterModelWithSuffix register models with a suffix +func RegisterModelWithSuffix(suffix string, models ...interface{}) { + if err := modelCache.register(suffix, false, models...); err != nil { + panic(err) + } +} + +// BootStrap bootstrap models. +// make all model parsed and can not add more models +func BootStrap() { + modelCache.bootstrap() +} diff --git a/orm/models_fields.go b/client/orm/models_fields.go similarity index 100% rename from orm/models_fields.go rename to client/orm/models_fields.go diff --git a/orm/models_info_f.go b/client/orm/models_info_f.go similarity index 97% rename from orm/models_info_f.go rename to client/orm/models_info_f.go index 7044b0bd..7152fada 100644 --- a/orm/models_info_f.go +++ b/client/orm/models_info_f.go @@ -137,6 +137,7 @@ type fieldInfo struct { isFielder bool // implement Fielder interface onDelete string description string + timePrecision *int } // new field info @@ -177,7 +178,7 @@ func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mN decimals := tags["decimals"] size := tags["size"] onDelete := tags["on_delete"] - + precision := tags["precision"] initial.Clear() if v, ok := tags["default"]; ok { initial.Set(v) @@ -377,6 +378,18 @@ checkType: fi.index = false fi.unique = false case TypeTimeField, TypeDateField, TypeDateTimeField: + if fieldType == TypeDateTimeField { + if precision != "" { + v, e := StrTo(precision).Int() + if e != nil { + err = fmt.Errorf("convert %s to int error:%v", precision, e) + } else { + fi.timePrecision = &v + } + } + + } + if attrs["auto_now"] { fi.autoNow = true } else if attrs["auto_now_add"] { diff --git a/orm/models_info_m.go b/client/orm/models_info_m.go similarity index 98% rename from orm/models_info_m.go rename to client/orm/models_info_m.go index a4d733b6..c450239c 100644 --- a/orm/models_info_m.go +++ b/client/orm/models_info_m.go @@ -29,7 +29,7 @@ type modelInfo struct { model interface{} fields *fields manual bool - addrField reflect.Value //store the original struct value + addrField reflect.Value // store the original struct value uniques []string isThrough bool } diff --git a/orm/models_test.go b/client/orm/models_test.go similarity index 66% rename from orm/models_test.go rename to client/orm/models_test.go index e3a635f2..d5aa2fa0 100644 --- a/orm/models_test.go +++ b/client/orm/models_test.go @@ -53,18 +53,24 @@ func (e *SliceStringField) FieldType() int { } func (e *SliceStringField) SetRaw(value interface{}) error { - switch d := value.(type) { - case []string: - e.Set(d) - case string: - if len(d) > 0 { - parts := strings.Split(d, ",") + f := func(str string) { + if len(str) > 0 { + parts := strings.Split(str, ",") v := make([]string, 0, len(parts)) for _, p := range parts { v = append(v, strings.TrimSpace(p)) } e.Set(v) } + } + + switch d := value.(type) { + case []string: + e.Set(d) + case string: + f(d) + case []byte: + f(string(d)) default: return fmt.Errorf(" unknown value `%v`", value) } @@ -96,6 +102,8 @@ func (e *JSONFieldTest) SetRaw(value interface{}) error { switch d := value.(type) { case string: return json.Unmarshal([]byte(d), e) + case []byte: + return json.Unmarshal(d, e) default: return fmt.Errorf(" unknown value `%v`", value) } @@ -135,55 +143,56 @@ type Data struct { } type DataNull struct { - ID int `orm:"column(id)"` - Boolean bool `orm:"null"` - Char string `orm:"null;size(50)"` - Text string `orm:"null;type(text)"` - JSON string `orm:"type(json);null"` - Jsonb string `orm:"type(jsonb);null"` - Time time.Time `orm:"null;type(time)"` - Date time.Time `orm:"null;type(date)"` - DateTime time.Time `orm:"null;column(datetime)"` - Byte byte `orm:"null"` - Rune rune `orm:"null"` - Int int `orm:"null"` - Int8 int8 `orm:"null"` - Int16 int16 `orm:"null"` - Int32 int32 `orm:"null"` - Int64 int64 `orm:"null"` - Uint uint `orm:"null"` - Uint8 uint8 `orm:"null"` - Uint16 uint16 `orm:"null"` - Uint32 uint32 `orm:"null"` - Uint64 uint64 `orm:"null"` - Float32 float32 `orm:"null"` - Float64 float64 `orm:"null"` - Decimal float64 `orm:"digits(8);decimals(4);null"` - NullString sql.NullString `orm:"null"` - NullBool sql.NullBool `orm:"null"` - NullFloat64 sql.NullFloat64 `orm:"null"` - NullInt64 sql.NullInt64 `orm:"null"` - BooleanPtr *bool `orm:"null"` - CharPtr *string `orm:"null;size(50)"` - TextPtr *string `orm:"null;type(text)"` - BytePtr *byte `orm:"null"` - RunePtr *rune `orm:"null"` - IntPtr *int `orm:"null"` - Int8Ptr *int8 `orm:"null"` - Int16Ptr *int16 `orm:"null"` - Int32Ptr *int32 `orm:"null"` - Int64Ptr *int64 `orm:"null"` - UintPtr *uint `orm:"null"` - Uint8Ptr *uint8 `orm:"null"` - Uint16Ptr *uint16 `orm:"null"` - Uint32Ptr *uint32 `orm:"null"` - Uint64Ptr *uint64 `orm:"null"` - Float32Ptr *float32 `orm:"null"` - Float64Ptr *float64 `orm:"null"` - DecimalPtr *float64 `orm:"digits(8);decimals(4);null"` - TimePtr *time.Time `orm:"null;type(time)"` - DatePtr *time.Time `orm:"null;type(date)"` - DateTimePtr *time.Time `orm:"null"` + ID int `orm:"column(id)"` + Boolean bool `orm:"null"` + Char string `orm:"null;size(50)"` + Text string `orm:"null;type(text)"` + JSON string `orm:"type(json);null"` + Jsonb string `orm:"type(jsonb);null"` + Time time.Time `orm:"null;type(time)"` + Date time.Time `orm:"null;type(date)"` + DateTime time.Time `orm:"null;column(datetime)"` + DateTimePrecision time.Time `orm:"null;type(datetime);precision(4)"` + Byte byte `orm:"null"` + Rune rune `orm:"null"` + Int int `orm:"null"` + Int8 int8 `orm:"null"` + Int16 int16 `orm:"null"` + Int32 int32 `orm:"null"` + Int64 int64 `orm:"null"` + Uint uint `orm:"null"` + Uint8 uint8 `orm:"null"` + Uint16 uint16 `orm:"null"` + Uint32 uint32 `orm:"null"` + Uint64 uint64 `orm:"null"` + Float32 float32 `orm:"null"` + Float64 float64 `orm:"null"` + Decimal float64 `orm:"digits(8);decimals(4);null"` + NullString sql.NullString `orm:"null"` + NullBool sql.NullBool `orm:"null"` + NullFloat64 sql.NullFloat64 `orm:"null"` + NullInt64 sql.NullInt64 `orm:"null"` + BooleanPtr *bool `orm:"null"` + CharPtr *string `orm:"null;size(50)"` + TextPtr *string `orm:"null;type(text)"` + BytePtr *byte `orm:"null"` + RunePtr *rune `orm:"null"` + IntPtr *int `orm:"null"` + Int8Ptr *int8 `orm:"null"` + Int16Ptr *int16 `orm:"null"` + Int32Ptr *int32 `orm:"null"` + Int64Ptr *int64 `orm:"null"` + UintPtr *uint `orm:"null"` + Uint8Ptr *uint8 `orm:"null"` + Uint16Ptr *uint16 `orm:"null"` + Uint32Ptr *uint32 `orm:"null"` + Uint64Ptr *uint64 `orm:"null"` + Float32Ptr *float32 `orm:"null"` + Float64Ptr *float64 `orm:"null"` + DecimalPtr *float64 `orm:"digits(8);decimals(4);null"` + TimePtr *time.Time `orm:"null;type(time)"` + DatePtr *time.Time `orm:"null;type(date)"` + DateTimePtr *time.Time `orm:"null"` } type String string @@ -231,6 +240,21 @@ type UserBig struct { Name string } +type TM struct { + ID int `orm:"column(id)"` + TMPrecision1 time.Time `orm:"type(datetime);precision(3)"` + TMPrecision2 time.Time `orm:"auto_now_add;type(datetime);precision(4)"` +} + +func (t *TM) TableName() string { + return "tm" +} + +func NewTM() *TM { + obj := new(TM) + return obj +} + type User struct { ID int `orm:"column(id)"` UserName string `orm:"size(30);unique"` @@ -287,13 +311,14 @@ func NewProfile() *Profile { } type Post struct { - ID int `orm:"column(id)"` - User *User `orm:"rel(fk)"` - Title string `orm:"size(60)"` - Content string `orm:"type(text)"` - Created time.Time `orm:"auto_now_add"` - Updated time.Time `orm:"auto_now"` - Tags []*Tag `orm:"rel(m2m);rel_through(github.com/astaxie/beego/orm.PostTags)"` + ID int `orm:"column(id)"` + User *User `orm:"rel(fk)"` + Title string `orm:"size(60)"` + Content string `orm:"type(text)"` + Created time.Time `orm:"auto_now_add"` + Updated time.Time `orm:"auto_now"` + UpdatedPrecision time.Time `orm:"auto_now;type(datetime);precision(4)"` + Tags []*Tag `orm:"rel(m2m);rel_through(github.com/astaxie/beego/client/orm.PostTags)"` } func (u *Post) TableIndex() [][]string { @@ -351,7 +376,7 @@ type Group struct { type Permission struct { ID int `orm:"column(id)"` Name string - Groups []*Group `orm:"rel(m2m);rel_through(github.com/astaxie/beego/orm.GroupPermissions)"` + Groups []*Group `orm:"rel(m2m);rel_through(github.com/astaxie/beego/client/orm.GroupPermissions)"` } type GroupPermissions struct { @@ -380,6 +405,15 @@ type InLine struct { Email string } +type Index struct { + // Common Fields + Id int `orm:"column(id)"` + + // Other Fields + F1 int `orm:"column(f1);index"` + F2 int `orm:"column(f2);index"` +} + func NewInLine() *InLine { return new(InLine) } @@ -411,6 +445,11 @@ type PtrPk struct { Positive bool } +type StrPk struct { + Id string `orm:"column(id);size(64);pk"` + Value string +} + var DBARGS = struct { Driver string Source string @@ -446,7 +485,7 @@ var ( usage: - go get -u github.com/astaxie/beego/orm + go get -u github.com/astaxie/beego/client/orm go get -u github.com/go-sql-driver/mysql go get -u github.com/mattn/go-sqlite3 go get -u github.com/lib/pq @@ -456,38 +495,43 @@ var ( mysql -u root -e 'create database orm_test;' export ORM_DRIVER=mysql export ORM_SOURCE="root:@/orm_test?charset=utf8" - go test -v github.com/astaxie/beego/orm + go test -v github.com/astaxie/beego/client/orm #### Sqlite3 export ORM_DRIVER=sqlite3 export ORM_SOURCE='file:memory_test?mode=memory' - go test -v github.com/astaxie/beego/orm + go test -v github.com/astaxie/beego/client/orm #### PostgreSQL psql -c 'create database orm_test;' -U postgres export ORM_DRIVER=postgres export ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable" - go test -v github.com/astaxie/beego/orm + go test -v github.com/astaxie/beego/client/orm #### TiDB export ORM_DRIVER=tidb export ORM_SOURCE='memory://test/test' - go test -v github.com/astaxie/beego/orm + go test -v github.com/astaxie/beego/pgk/orm ` ) func init() { - Debug, _ = StrTo(DBARGS.Debug).Bool() + // Debug, _ = StrTo(DBARGS.Debug).Bool() + Debug = true if DBARGS.Driver == "" || DBARGS.Source == "" { fmt.Println(helpinfo) os.Exit(2) } - RegisterDataBase("default", DBARGS.Driver, DBARGS.Source, 20) + err := RegisterDataBase("default", DBARGS.Driver, DBARGS.Source, MaxIdleConnections(20)) + + if err != nil { + panic(fmt.Sprintf("can not register database: %v", err)) + } alias := getDbAlias("default") if alias.Driver == DRMySQL { diff --git a/orm/models_utils.go b/client/orm/models_utils.go similarity index 93% rename from orm/models_utils.go rename to client/orm/models_utils.go index 71127a6b..950ca243 100644 --- a/orm/models_utils.go +++ b/client/orm/models_utils.go @@ -45,6 +45,7 @@ var supportTag = map[string]int{ "on_delete": 2, "type": 2, "description": 2, + "precision": 2, } // get reflect.Type name with package path. @@ -106,6 +107,18 @@ func getTableUnique(val reflect.Value) [][]string { return nil } +// get whether the table needs to be created for the database alias +func isApplicableTableForDB(val reflect.Value, db string) bool { + fun := val.MethodByName("IsApplicableTableForDB") + if fun.IsValid() { + vals := fun.Call([]reflect.Value{reflect.ValueOf(db)}) + if len(vals) > 0 && vals[0].Kind() == reflect.Bool { + return vals[0].Bool() + } + } + return true +} + // get snaked column name func getColumnName(ft int, addrField reflect.Value, sf reflect.StructField, col string) string { column := col diff --git a/client/orm/models_utils_test.go b/client/orm/models_utils_test.go new file mode 100644 index 00000000..0a6995b3 --- /dev/null +++ b/client/orm/models_utils_test.go @@ -0,0 +1,35 @@ +// Copyright 2020 +// +// Licensed 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 orm + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +type NotApplicableModel struct { + Id int +} + +func (n *NotApplicableModel) IsApplicableTableForDB(db string) bool { + return db == "default" +} + +func Test_IsApplicableTableForDB(t *testing.T) { + assert.False(t, isApplicableTableForDB(reflect.ValueOf(&NotApplicableModel{}), "defa")) + assert.True(t, isApplicableTableForDB(reflect.ValueOf(&NotApplicableModel{}), "default")) +} diff --git a/orm/orm.go b/client/orm/orm.go similarity index 57% rename from orm/orm.go rename to client/orm/orm.go index 0551b1cd..a83faeb2 100644 --- a/orm/orm.go +++ b/client/orm/orm.go @@ -21,7 +21,7 @@ // // import ( // "fmt" -// "github.com/astaxie/beego/orm" +// "github.com/astaxie/beego/client/orm" // _ "github.com/go-sql-driver/mysql" // import your used driver // ) // @@ -60,8 +60,12 @@ import ( "fmt" "os" "reflect" - "sync" "time" + + "github.com/astaxie/beego/client/orm/hints" + "github.com/astaxie/beego/core/utils" + + "github.com/astaxie/beego/core/logs" ) // DebugQueries define the debug @@ -76,13 +80,14 @@ var ( DefaultRowsLimit = -1 DefaultRelsDepth = 2 DefaultTimeLoc = time.Local - ErrTxHasBegan = errors.New(" transaction already begin") - ErrTxDone = errors.New(" transaction not begin") + ErrTxDone = errors.New(" transaction already done") ErrMultiRows = errors.New(" return multi rows") ErrNoRows = errors.New(" no row found") ErrStmtClosed = errors.New(" stmt already closed") ErrArgs = errors.New(" args error may be empty") ErrNotImplement = errors.New("have not implement") + + ErrLastInsertIdUnavailable = errors.New(" last insert id is unavailable") ) // Params stores the Params @@ -91,16 +96,17 @@ type Params map[string]interface{} // ParamsList stores paramslist type ParamsList []interface{} -type orm struct { +type ormBase struct { alias *alias db dbQuerier - isTx bool } -var _ Ormer = new(orm) +var _ DQL = new(ormBase) +var _ DML = new(ormBase) +var _ DriverGetter = new(ormBase) // get model info and model reflect value -func (o *orm) getMiInd(md interface{}, needPtr bool) (mi *modelInfo, ind reflect.Value) { +func (o *ormBase) getMiInd(md interface{}, needPtr bool) (mi *modelInfo, ind reflect.Value) { val := reflect.ValueOf(md) ind = reflect.Indirect(val) typ := ind.Type() @@ -115,7 +121,7 @@ func (o *orm) getMiInd(md interface{}, needPtr bool) (mi *modelInfo, ind reflect } // get field info from model info by given field name -func (o *orm) getFieldInfo(mi *modelInfo, name string) *fieldInfo { +func (o *ormBase) getFieldInfo(mi *modelInfo, name string) *fieldInfo { fi, ok := mi.fields.GetByAny(name) if !ok { panic(fmt.Errorf(" cannot find field `%s` for model `%s`", name, mi.fullName)) @@ -124,33 +130,42 @@ func (o *orm) getFieldInfo(mi *modelInfo, name string) *fieldInfo { } // read data to model -func (o *orm) Read(md interface{}, cols ...string) error { +func (o *ormBase) Read(md interface{}, cols ...string) error { + return o.ReadWithCtx(context.Background(), md, cols...) +} +func (o *ormBase) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error { mi, ind := o.getMiInd(md, true) return o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, false) } // read data to model, like Read(), but use "SELECT FOR UPDATE" form -func (o *orm) ReadForUpdate(md interface{}, cols ...string) error { +func (o *ormBase) ReadForUpdate(md interface{}, cols ...string) error { + return o.ReadForUpdateWithCtx(context.Background(), md, cols...) +} +func (o *ormBase) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error { mi, ind := o.getMiInd(md, true) return o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, true) } // Try to read a row from the database, or insert one if it doesn't exist -func (o *orm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) { +func (o *ormBase) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) { + return o.ReadOrCreateWithCtx(context.Background(), md, col1, cols...) +} +func (o *ormBase) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) { cols = append([]string{col1}, cols...) mi, ind := o.getMiInd(md, true) err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, false) if err == ErrNoRows { // Create - id, err := o.Insert(md) - return (err == nil), id, err + id, err := o.InsertWithCtx(ctx, md) + return err == nil, id, err } id, vid := int64(0), ind.FieldByIndex(mi.fields.pk.fieldIndex) if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 { id = int64(vid.Uint()) } else if mi.fields.pk.rel { - return o.ReadOrCreate(vid.Interface(), mi.fields.pk.relModelInfo.fields.pk.name) + return o.ReadOrCreateWithCtx(ctx, vid.Interface(), mi.fields.pk.relModelInfo.fields.pk.name) } else { id = vid.Int() } @@ -159,7 +174,10 @@ func (o *orm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, i } // insert model data to database -func (o *orm) Insert(md interface{}) (int64, error) { +func (o *ormBase) Insert(md interface{}) (int64, error) { + return o.InsertWithCtx(context.Background(), md) +} +func (o *ormBase) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) { mi, ind := o.getMiInd(md, true) id, err := o.alias.DbBaser.Insert(o.db, mi, ind, o.alias.TZ) if err != nil { @@ -172,7 +190,7 @@ func (o *orm) Insert(md interface{}) (int64, error) { } // set auto pk field -func (o *orm) setPk(mi *modelInfo, ind reflect.Value, id int64) { +func (o *ormBase) setPk(mi *modelInfo, ind reflect.Value, id int64) { if mi.fields.pk.auto { if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 { ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(uint64(id)) @@ -183,7 +201,10 @@ func (o *orm) setPk(mi *modelInfo, ind reflect.Value, id int64) { } // insert some models to database -func (o *orm) InsertMulti(bulk int, mds interface{}) (int64, error) { +func (o *ormBase) InsertMulti(bulk int, mds interface{}) (int64, error) { + return o.InsertMultiWithCtx(context.Background(), bulk, mds) +} +func (o *ormBase) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) { var cnt int64 sind := reflect.Indirect(reflect.ValueOf(mds)) @@ -218,7 +239,10 @@ func (o *orm) InsertMulti(bulk int, mds interface{}) (int64, error) { } // InsertOrUpdate data to database -func (o *orm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) { +func (o *ormBase) InsertOrUpdate(md interface{}, colConflictAndArgs ...string) (int64, error) { + return o.InsertOrUpdateWithCtx(context.Background(), md, colConflictAndArgs...) +} +func (o *ormBase) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) { mi, ind := o.getMiInd(md, true) id, err := o.alias.DbBaser.InsertOrUpdate(o.db, mi, ind, o.alias, colConflitAndArgs...) if err != nil { @@ -232,14 +256,20 @@ func (o *orm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64 // update model to database. // cols set the columns those want to update. -func (o *orm) Update(md interface{}, cols ...string) (int64, error) { +func (o *ormBase) Update(md interface{}, cols ...string) (int64, error) { + return o.UpdateWithCtx(context.Background(), md, cols...) +} +func (o *ormBase) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) { mi, ind := o.getMiInd(md, true) return o.alias.DbBaser.Update(o.db, mi, ind, o.alias.TZ, cols) } // delete model in database // cols shows the delete conditions values read from. default is pk -func (o *orm) Delete(md interface{}, cols ...string) (int64, error) { +func (o *ormBase) Delete(md interface{}, cols ...string) (int64, error) { + return o.DeleteWithCtx(context.Background(), md, cols...) +} +func (o *ormBase) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) { mi, ind := o.getMiInd(md, true) num, err := o.alias.DbBaser.Delete(o.db, mi, ind, o.alias.TZ, cols) if err != nil { @@ -252,7 +282,10 @@ func (o *orm) Delete(md interface{}, cols ...string) (int64, error) { } // create a models to models queryer -func (o *orm) QueryM2M(md interface{}, name string) QueryM2Mer { +func (o *ormBase) QueryM2M(md interface{}, name string) QueryM2Mer { + return o.QueryM2MWithCtx(context.Background(), md, name) +} +func (o *ormBase) QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer { mi, ind := o.getMiInd(md, true) fi := o.getFieldInfo(mi, name) @@ -274,32 +307,38 @@ func (o *orm) QueryM2M(md interface{}, name string) QueryM2Mer { // for _,tag := range post.Tags{...} // // make sure the relation is defined in model struct tags. -func (o *orm) LoadRelated(md interface{}, name string, args ...interface{}) (int64, error) { - _, fi, ind, qseter := o.queryRelated(md, name) - - qs := qseter.(*querySet) +func (o *ormBase) LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) { + return o.LoadRelatedWithCtx(context.Background(), md, name, args...) +} +func (o *ormBase) LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) { + _, fi, ind, qs := o.queryRelated(md, name) var relDepth int var limit, offset int64 var order string - for i, arg := range args { - switch i { - case 0: - if v, ok := arg.(bool); ok { - if v { - relDepth = DefaultRelsDepth - } - } else if v, ok := arg.(int); ok { - relDepth = v + + kvs := utils.NewKVs(args...) + kvs.IfContains(hints.KeyRelDepth, func(value interface{}) { + if v, ok := value.(bool); ok { + if v { + relDepth = DefaultRelsDepth } - case 1: - limit = ToInt64(arg) - case 2: - offset = ToInt64(arg) - case 3: - order, _ = arg.(string) + } else if v, ok := value.(int); ok { + relDepth = v } - } + }).IfContains(hints.KeyLimit, func(value interface{}) { + if v, ok := value.(int64); ok { + limit = v + } + }).IfContains(hints.KeyOffset, func(value interface{}) { + if v, ok := value.(int64); ok { + offset = v + } + }).IfContains(hints.KeyOrderBy, func(value interface{}) { + if v, ok := value.(string); ok { + order = v + } + }) switch fi.fieldType { case RelOneToOne, RelForeignKey, RelReverseOne: @@ -335,20 +374,8 @@ func (o *orm) LoadRelated(md interface{}, name string, args ...interface{}) (int return nums, err } -// return a QuerySeter for related models to md model. -// it can do all, update, delete in QuerySeter. -// example: -// qs := orm.QueryRelated(post,"Tag") -// qs.All(&[]*Tag{}) -// -func (o *orm) QueryRelated(md interface{}, name string) QuerySeter { - // is this api needed ? - _, _, _, qs := o.queryRelated(md, name) - return qs -} - // get QuerySeter for related models to md model -func (o *orm) queryRelated(md interface{}, name string) (*modelInfo, *fieldInfo, reflect.Value, QuerySeter) { +func (o *ormBase) queryRelated(md interface{}, name string) (*modelInfo, *fieldInfo, reflect.Value, *querySet) { mi, ind := o.getMiInd(md, true) fi := o.getFieldInfo(mi, name) @@ -380,7 +407,7 @@ func (o *orm) queryRelated(md interface{}, name string) (*modelInfo, *fieldInfo, } // get reverse relation QuerySeter -func (o *orm) getReverseQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet { +func (o *ormBase) getReverseQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet { switch fi.fieldType { case RelReverseOne, RelReverseMany: default: @@ -401,7 +428,7 @@ func (o *orm) getReverseQs(md interface{}, mi *modelInfo, fi *fieldInfo) *queryS } // get relation QuerySeter -func (o *orm) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet { +func (o *ormBase) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet { switch fi.fieldType { case RelOneToOne, RelForeignKey, RelManyToMany: default: @@ -423,7 +450,10 @@ func (o *orm) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet { // return a QuerySeter for table operations. // table name can be string or struct. // e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)), -func (o *orm) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) { +func (o *ormBase) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) { + return o.QueryTableWithCtx(context.Background(), ptrStructOrTableName) +} +func (o *ormBase) QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) (qs QuerySeter) { var name string if table, ok := ptrStructOrTableName.(string); ok { name = nameStrategyMap[defaultNameStrategy](table) @@ -442,89 +472,21 @@ func (o *orm) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) { return } -// switch to another registered database driver by given name. -func (o *orm) Using(name string) error { - if o.isTx { - panic(fmt.Errorf(" transaction has been start, cannot change db")) - } - if al, ok := dataBaseCache.get(name); ok { - o.alias = al - if Debug { - o.db = newDbQueryLog(al, al.DB) - } else { - o.db = al.DB - } - } else { - return fmt.Errorf(" unknown db alias name `%s`", name) - } - return nil -} - -// begin transaction -func (o *orm) Begin() error { - return o.BeginTx(context.Background(), nil) -} - -func (o *orm) BeginTx(ctx context.Context, opts *sql.TxOptions) error { - if o.isTx { - return ErrTxHasBegan - } - var tx *sql.Tx - tx, err := o.db.(txer).BeginTx(ctx, opts) - if err != nil { - return err - } - o.isTx = true - if Debug { - o.db.(*dbQueryLog).SetDB(tx) - } else { - o.db = tx - } - return nil -} - -// commit transaction -func (o *orm) Commit() error { - if !o.isTx { - return ErrTxDone - } - err := o.db.(txEnder).Commit() - if err == nil { - o.isTx = false - o.Using(o.alias.Name) - } else if err == sql.ErrTxDone { - return ErrTxDone - } - return err -} - -// rollback transaction -func (o *orm) Rollback() error { - if !o.isTx { - return ErrTxDone - } - err := o.db.(txEnder).Rollback() - if err == nil { - o.isTx = false - o.Using(o.alias.Name) - } else if err == sql.ErrTxDone { - return ErrTxDone - } - return err -} - // return a raw query seter for raw sql string. -func (o *orm) Raw(query string, args ...interface{}) RawSeter { +func (o *ormBase) Raw(query string, args ...interface{}) RawSeter { + return o.RawWithCtx(context.Background(), query, args...) +} +func (o *ormBase) RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter { return newRawSet(o, query, args) } // return current using database Driver -func (o *orm) Driver() Driver { +func (o *ormBase) Driver() Driver { return driver(o.alias.Name) } // return sql.DBStats for current database -func (o *orm) DBStats() *sql.DBStats { +func (o *ormBase) DBStats() *sql.DBStats { if o.alias != nil && o.alias.DB != nil { stats := o.alias.DB.DB.Stats() return &stats @@ -532,48 +494,134 @@ func (o *orm) DBStats() *sql.DBStats { return nil } +type orm struct { + ormBase +} + +var _ Ormer = new(orm) + +func (o *orm) Begin() (TxOrmer, error) { + return o.BeginWithCtx(context.Background()) +} + +func (o *orm) BeginWithCtx(ctx context.Context) (TxOrmer, error) { + return o.BeginWithCtxAndOpts(ctx, nil) +} + +func (o *orm) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) { + return o.BeginWithCtxAndOpts(context.Background(), opts) +} + +func (o *orm) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) { + tx, err := o.db.(txer).BeginTx(ctx, opts) + if err != nil { + return nil, err + } + + _txOrm := &txOrm{ + ormBase: ormBase{ + alias: o.alias, + db: &TxDB{tx: tx}, + }, + } + + var taskTxOrm TxOrmer = _txOrm + return taskTxOrm, nil +} + +func (o *orm) DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error { + return o.DoTxWithCtx(context.Background(), task) +} + +func (o *orm) DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error { + return o.DoTxWithCtxAndOpts(ctx, nil, task) +} + +func (o *orm) DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error { + return o.DoTxWithCtxAndOpts(context.Background(), opts, task) +} + +func (o *orm) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error { + return doTxTemplate(o, ctx, opts, task) +} + +func doTxTemplate(o TxBeginner, ctx context.Context, opts *sql.TxOptions, + task func(ctx context.Context, txOrm TxOrmer) error) error { + _txOrm, err := o.BeginWithCtxAndOpts(ctx, opts) + if err != nil { + return err + } + panicked := true + defer func() { + if panicked || err != nil { + e := _txOrm.Rollback() + if e != nil { + logs.Error("rollback transaction failed: %v,%v", e, panicked) + } + } else { + e := _txOrm.Commit() + if e != nil { + logs.Error("commit transaction failed: %v,%v", e, panicked) + } + } + }() + var taskTxOrm = _txOrm + err = task(ctx, taskTxOrm) + panicked = false + return err +} + +type txOrm struct { + ormBase +} + +var _ TxOrmer = new(txOrm) + +func (t *txOrm) Commit() error { + return t.db.(txEnder).Commit() +} + +func (t *txOrm) Rollback() error { + return t.db.(txEnder).Rollback() +} + // NewOrm create new orm func NewOrm() Ormer { BootStrap() // execute only once + return NewOrmUsingDB(`default`) +} - o := new(orm) - err := o.Using("default") - if err != nil { - panic(err) +// NewOrmUsingDB create new orm with the name +func NewOrmUsingDB(aliasName string) Ormer { + if al, ok := dataBaseCache.get(aliasName); ok { + return newDBWithAlias(al) + } else { + panic(fmt.Errorf(" unknown db alias name `%s`", aliasName)) } - return o } // NewOrmWithDB create a new ormer object with specify *sql.DB for query -func NewOrmWithDB(driverName, aliasName string, db *sql.DB) (Ormer, error) { - var al *alias - - if dr, ok := drivers[driverName]; ok { - al = new(alias) - al.DbBaser = dbBasers[dr] - al.Driver = dr - } else { - return nil, fmt.Errorf("driver name `%s` have not registered", driverName) +func NewOrmWithDB(driverName, aliasName string, db *sql.DB, params ...DBOption) (Ormer, error) { + al, err := newAliasWithDb(aliasName, driverName, db, params...) + if err != nil { + return nil, err } - al.Name = aliasName - al.DriverName = driverName - al.DB = &DB{ - RWMutex: new(sync.RWMutex), - DB: db, - stmtDecorators: newStmtDecoratorLruWithEvict(), - } - - detectTZ(al) + return newDBWithAlias(al), nil +} +func newDBWithAlias(al *alias) Ormer { o := new(orm) o.alias = al if Debug { - o.db = newDbQueryLog(o.alias, db) + o.db = newDbQueryLog(al, al.DB) } else { - o.db = db + o.db = al.DB } - return o, nil + if len(globalFilterChains) > 0 { + return NewFilterOrmDecorator(o, globalFilterChains...) + } + return o } diff --git a/orm/orm_conds.go b/client/orm/orm_conds.go similarity index 100% rename from orm/orm_conds.go rename to client/orm/orm_conds.go diff --git a/orm/orm_log.go b/client/orm/orm_log.go similarity index 88% rename from orm/orm_log.go rename to client/orm/orm_log.go index f107bb59..d8df7e36 100644 --- a/orm/orm_log.go +++ b/client/orm/orm_log.go @@ -61,7 +61,7 @@ func debugLogQueies(alias *alias, operaton, query string, t time.Time, err error con += " - " + err.Error() } logMap["sql"] = fmt.Sprintf("%s-`%s`", query, strings.Join(cons, "`, `")) - if LogFunc != nil{ + if LogFunc != nil { LogFunc(logMap) } DebugLog.Println(con) @@ -127,10 +127,7 @@ var _ txer = new(dbQueryLog) var _ txEnder = new(dbQueryLog) func (d *dbQueryLog) Prepare(query string) (*sql.Stmt, error) { - a := time.Now() - stmt, err := d.db.Prepare(query) - debugLogQueies(d.alias, "db.Prepare", query, a, err) - return stmt, err + return d.PrepareContext(context.Background(), query) } func (d *dbQueryLog) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) { @@ -141,10 +138,7 @@ func (d *dbQueryLog) PrepareContext(ctx context.Context, query string) (*sql.Stm } func (d *dbQueryLog) Exec(query string, args ...interface{}) (sql.Result, error) { - a := time.Now() - res, err := d.db.Exec(query, args...) - debugLogQueies(d.alias, "db.Exec", query, a, err, args...) - return res, err + return d.ExecContext(context.Background(), query, args...) } func (d *dbQueryLog) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { @@ -155,10 +149,7 @@ func (d *dbQueryLog) ExecContext(ctx context.Context, query string, args ...inte } func (d *dbQueryLog) Query(query string, args ...interface{}) (*sql.Rows, error) { - a := time.Now() - res, err := d.db.Query(query, args...) - debugLogQueies(d.alias, "db.Query", query, a, err, args...) - return res, err + return d.QueryContext(context.Background(), query, args...) } func (d *dbQueryLog) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { @@ -169,10 +160,7 @@ func (d *dbQueryLog) QueryContext(ctx context.Context, query string, args ...int } func (d *dbQueryLog) QueryRow(query string, args ...interface{}) *sql.Row { - a := time.Now() - res := d.db.QueryRow(query, args...) - debugLogQueies(d.alias, "db.QueryRow", query, a, nil, args...) - return res + return d.QueryRowContext(context.Background(), query, args...) } func (d *dbQueryLog) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { @@ -183,10 +171,7 @@ func (d *dbQueryLog) QueryRowContext(ctx context.Context, query string, args ... } func (d *dbQueryLog) Begin() (*sql.Tx, error) { - a := time.Now() - tx, err := d.db.(txer).Begin() - debugLogQueies(d.alias, "db.Begin", "START TRANSACTION", a, err) - return tx, err + return d.BeginTx(context.Background(), nil) } func (d *dbQueryLog) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) { diff --git a/orm/orm_object.go b/client/orm/orm_object.go similarity index 96% rename from orm/orm_object.go rename to client/orm/orm_object.go index de3181ce..6f9798d3 100644 --- a/orm/orm_object.go +++ b/client/orm/orm_object.go @@ -22,7 +22,7 @@ import ( // an insert queryer struct type insertSet struct { mi *modelInfo - orm *orm + orm *ormBase stmt stmtQuerier closed bool } @@ -70,7 +70,7 @@ func (o *insertSet) Close() error { } // create new insert queryer. -func newInsertSet(orm *orm, mi *modelInfo) (Inserter, error) { +func newInsertSet(orm *ormBase, mi *modelInfo) (Inserter, error) { bi := new(insertSet) bi.orm = orm bi.mi = mi diff --git a/orm/orm_querym2m.go b/client/orm/orm_querym2m.go similarity index 97% rename from orm/orm_querym2m.go rename to client/orm/orm_querym2m.go index 6a270a0d..17e1b5d1 100644 --- a/orm/orm_querym2m.go +++ b/client/orm/orm_querym2m.go @@ -129,7 +129,7 @@ func (o *queryM2M) Count() (int64, error) { var _ QueryM2Mer = new(queryM2M) // create new M2M queryer. -func newQueryM2M(md interface{}, o *orm, mi *modelInfo, fi *fieldInfo, ind reflect.Value) QueryM2Mer { +func newQueryM2M(md interface{}, o *ormBase, mi *modelInfo, fi *fieldInfo, ind reflect.Value) QueryM2Mer { qm2m := new(queryM2M) qm2m.md = md qm2m.mi = mi diff --git a/orm/orm_queryset.go b/client/orm/orm_queryset.go similarity index 91% rename from orm/orm_queryset.go rename to client/orm/orm_queryset.go index 878b836b..ed223e24 100644 --- a/orm/orm_queryset.go +++ b/client/orm/orm_queryset.go @@ -17,6 +17,8 @@ package orm import ( "context" "fmt" + + "github.com/astaxie/beego/client/orm/hints" ) type colValue struct { @@ -71,8 +73,10 @@ type querySet struct { groups []string orders []string distinct bool - forupdate bool - orm *orm + forUpdate bool + useIndex int + indexes []string + orm *ormBase ctx context.Context forContext bool } @@ -148,7 +152,28 @@ func (o querySet) Distinct() QuerySeter { // add FOR UPDATE to SELECT func (o querySet) ForUpdate() QuerySeter { - o.forupdate = true + o.forUpdate = true + return &o +} + +// ForceIndex force index for query +func (o querySet) ForceIndex(indexes ...string) QuerySeter { + o.useIndex = hints.KeyForceIndex + o.indexes = indexes + return &o +} + +// UseIndex use index for query +func (o querySet) UseIndex(indexes ...string) QuerySeter { + o.useIndex = hints.KeyUseIndex + o.indexes = indexes + return &o +} + +// IgnoreIndex ignore index for query +func (o querySet) IgnoreIndex(indexes ...string) QuerySeter { + o.useIndex = hints.KeyIgnoreIndex + o.indexes = indexes return &o } @@ -292,7 +317,7 @@ func (o querySet) WithContext(ctx context.Context) QuerySeter { } // create new QuerySeter. -func newQuerySet(orm *orm, mi *modelInfo) QuerySeter { +func newQuerySet(orm *ormBase, mi *modelInfo) QuerySeter { o := new(querySet) o.mi = mi o.orm = orm diff --git a/orm/orm_raw.go b/client/orm/orm_raw.go similarity index 91% rename from orm/orm_raw.go rename to client/orm/orm_raw.go index 3325a7ea..e11e97fa 100644 --- a/orm/orm_raw.go +++ b/client/orm/orm_raw.go @@ -19,6 +19,8 @@ import ( "fmt" "reflect" "time" + + "github.com/pkg/errors" ) // raw sql string prepared statement @@ -32,7 +34,8 @@ func (o *rawPrepare) Exec(args ...interface{}) (sql.Result, error) { if o.closed { return nil, ErrStmtClosed } - return o.stmt.Exec(args...) + flatParams := getFlatParams(nil, args, o.rs.orm.alias.TZ) + return o.stmt.Exec(flatParams...) } func (o *rawPrepare) Close() error { @@ -63,7 +66,7 @@ func newRawPreparer(rs *rawSet) (RawPreparer, error) { type rawSet struct { query string args []interface{} - orm *orm + orm *ormBase } var _ RawSeter = new(rawSet) @@ -327,6 +330,8 @@ func (o *rawSet) QueryRow(containers ...interface{}) error { return err } + structTagMap := make(map[reflect.StructTag]map[string]string) + defer rows.Close() if rows.Next() { @@ -368,23 +373,50 @@ func (o *rawSet) QueryRow(containers ...interface{}) error { field.Set(mf) field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex) } - o.setFieldValue(field, value) + if fi.isFielder { + fd := field.Addr().Interface().(Fielder) + err := fd.SetRaw(value) + if err != nil { + return errors.Errorf("set raw error:%s", err) + } + } else { + o.setFieldValue(field, value) + } } } } else { - for i := 0; i < ind.NumField(); i++ { - f := ind.Field(i) - fe := ind.Type().Field(i) - _, tags := parseStructTag(fe.Tag.Get(defaultStructTagName)) - var col string - if col = tags["column"]; col == "" { - col = nameStrategyMap[nameStrategy](fe.Name) - } - if v, ok := columnsMp[col]; ok { - value := reflect.ValueOf(v).Elem().Interface() - o.setFieldValue(f, value) + // define recursive function + var recursiveSetField func(rv reflect.Value) + recursiveSetField = func(rv reflect.Value) { + for i := 0; i < rv.NumField(); i++ { + f := rv.Field(i) + fe := rv.Type().Field(i) + + // check if the field is a Struct + // recursive the Struct type + if fe.Type.Kind() == reflect.Struct { + recursiveSetField(f) + } + + // thanks @Gazeboxu. + tags := structTagMap[fe.Tag] + if tags == nil { + _, tags = parseStructTag(fe.Tag.Get(defaultStructTagName)) + structTagMap[fe.Tag] = tags + } + var col string + if col = tags["column"]; col == "" { + col = nameStrategyMap[nameStrategy](fe.Name) + } + if v, ok := columnsMp[col]; ok { + value := reflect.ValueOf(v).Elem().Interface() + o.setFieldValue(f, value) + } } } + + // init call the recursive function + recursiveSetField(ind) } } else { @@ -509,7 +541,15 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) { field.Set(mf) field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex) } - o.setFieldValue(field, value) + if fi.isFielder { + fd := field.Addr().Interface().(Fielder) + err := fd.SetRaw(value) + if err != nil { + return 0, errors.Errorf("set raw error:%s", err) + } + } else { + o.setFieldValue(field, value) + } } } } else { @@ -858,7 +898,7 @@ func (o *rawSet) Prepare() (RawPreparer, error) { return newRawPreparer(o) } -func newRawSet(orm *orm, query string, args []interface{}) RawSeter { +func newRawSet(orm *ormBase, query string, args []interface{}) RawSeter { o := new(rawSet) o.query = query o.args = args diff --git a/orm/orm_test.go b/client/orm/orm_test.go similarity index 89% rename from orm/orm_test.go rename to client/orm/orm_test.go index bdb430b6..565f6c60 100644 --- a/orm/orm_test.go +++ b/client/orm/orm_test.go @@ -30,6 +30,10 @@ import ( "strings" "testing" "time" + + "github.com/astaxie/beego/client/orm/hints" + + "github.com/stretchr/testify/assert" ) var _ = os.PathSeparator @@ -141,6 +145,7 @@ func getCaller(skip int) string { return fmt.Sprintf("%s:%s:%d: \n%s", fn, funName, line, strings.Join(codes, "\n")) } +// Deprecated: Using stretchr/testify/assert func throwFail(t *testing.T, err error, args ...interface{}) { if err != nil { con := fmt.Sprintf("\t\nError: %s\n%s\n", err.Error(), getCaller(2)) @@ -197,6 +202,9 @@ func TestSyncDb(t *testing.T) { RegisterModel(new(IntegerPk)) RegisterModel(new(UintPk)) RegisterModel(new(PtrPk)) + RegisterModel(new(Index)) + RegisterModel(new(StrPk)) + RegisterModel(new(TM)) err := RunSyncdb("default", true, Debug) throwFail(t, err) @@ -221,6 +229,9 @@ func TestRegisterModels(t *testing.T) { RegisterModel(new(IntegerPk)) RegisterModel(new(UintPk)) RegisterModel(new(PtrPk)) + RegisterModel(new(Index)) + RegisterModel(new(StrPk)) + RegisterModel(new(TM)) BootStrap() @@ -294,19 +305,34 @@ func TestDataTypes(t *testing.T) { vu := e.Interface() switch name { case "Date": - vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDate) - value = value.(time.Time).In(DefaultTimeLoc).Format(testDate) case "DateTime": - vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDateTime) - value = value.(time.Time).In(DefaultTimeLoc).Format(testDateTime) case "Time": - vu = vu.(time.Time).In(DefaultTimeLoc).Format(testTime) - value = value.(time.Time).In(DefaultTimeLoc).Format(testTime) + assert.True(t, vu.(time.Time).In(DefaultTimeLoc).Sub(value.(time.Time).In(DefaultTimeLoc)) <= time.Second) + break + default: + assert.Equal(t, value, vu) } - throwFail(t, AssertIs(vu == value, true), value, vu) } } +func TestTM(t *testing.T) { + // The precision of sqlite is not implemented + if dORM.Driver().Type() == 2 { + return + } + var recTM TM + tm := NewTM() + tm.TMPrecision1 = time.Unix(1596766024, 123456789) + tm.TMPrecision2 = time.Unix(1596766024, 123456789) + _, err := dORM.Insert(tm) + throwFail(t, err) + + err = dORM.QueryTable("tm").One(&recTM) + throwFail(t, err) + throwFail(t, AssertIs(recTM.TMPrecision1.String(), "2020-08-07 02:07:04.123 +0000 UTC")) + throwFail(t, AssertIs(recTM.TMPrecision2.String(), "2020-08-07 02:07:04.1235 +0000 UTC")) +} + func TestNullDataTypes(t *testing.T) { d := DataNull{} @@ -455,9 +481,11 @@ func TestNullDataTypes(t *testing.T) { throwFail(t, AssertIs(*d.Float32Ptr, float32Ptr)) throwFail(t, AssertIs(*d.Float64Ptr, float64Ptr)) throwFail(t, AssertIs(*d.DecimalPtr, decimalPtr)) - throwFail(t, AssertIs((*d.TimePtr).UTC().Format(testTime), timePtr.UTC().Format(testTime))) - throwFail(t, AssertIs((*d.DatePtr).UTC().Format(testDate), datePtr.UTC().Format(testDate))) - throwFail(t, AssertIs((*d.DateTimePtr).UTC().Format(testDateTime), dateTimePtr.UTC().Format(testDateTime))) + + // in mysql, there are some precision problem, (*d.TimePtr).UTC() != timePtr.UTC() + assert.True(t, (*d.TimePtr).UTC().Sub(timePtr.UTC()) <= time.Second) + assert.True(t, (*d.DatePtr).UTC().Sub(datePtr.UTC()) <= time.Second) + assert.True(t, (*d.DateTimePtr).UTC().Sub(dateTimePtr.UTC()) <= time.Second) // test support for pointer fields using RawSeter.QueryRows() var dnList []*DataNull @@ -532,8 +560,9 @@ func TestCRUD(t *testing.T) { throwFail(t, AssertIs(u.Status, 3)) throwFail(t, AssertIs(u.IsStaff, true)) throwFail(t, AssertIs(u.IsActive, true)) - throwFail(t, AssertIs(u.Created.In(DefaultTimeLoc), user.Created.In(DefaultTimeLoc), testDate)) - throwFail(t, AssertIs(u.Updated.In(DefaultTimeLoc), user.Updated.In(DefaultTimeLoc), testDateTime)) + + assert.True(t, u.Created.In(DefaultTimeLoc).Sub(user.Created.In(DefaultTimeLoc)) <= time.Second) + assert.True(t, u.Updated.In(DefaultTimeLoc).Sub(user.Updated.In(DefaultTimeLoc)) <= time.Second) user.UserName = "astaxie" user.Profile = profile @@ -769,6 +798,20 @@ func TestCustomField(t *testing.T) { throwFailNow(t, AssertIs(user.Extra.Name, "beego")) throwFailNow(t, AssertIs(user.Extra.Data, "orm")) + + var users []User + Q := dDbBaser.TableQuote() + n, err := dORM.Raw(fmt.Sprintf("SELECT * FROM %suser%s where id=?", Q, Q), 2).QueryRows(&users) + throwFailNow(t, err) + throwFailNow(t, AssertIs(n, 1)) + throwFailNow(t, AssertIs(users[0].Extra.Name, "beego")) + throwFailNow(t, AssertIs(users[0].Extra.Data, "orm")) + + user = User{} + err = dORM.Raw(fmt.Sprintf("SELECT * FROM %suser%s where id=?", Q, Q), 2).QueryRow(&user) + throwFailNow(t, err) + throwFailNow(t, AssertIs(user.Extra.Name, "beego")) + throwFailNow(t, AssertIs(user.Extra.Data, "orm")) } func TestExpr(t *testing.T) { @@ -790,6 +833,32 @@ func TestExpr(t *testing.T) { // throwFail(t, AssertIs(num, 3)) } +func TestSpecifyIndex(t *testing.T) { + var index *Index + index = &Index{ + F1: 1, + F2: 2, + } + _, _ = dORM.Insert(index) + throwFailNow(t, AssertIs(index.Id, 1)) + + index = &Index{ + F1: 3, + F2: 4, + } + _, _ = dORM.Insert(index) + throwFailNow(t, AssertIs(index.Id, 2)) + + _ = dORM.QueryTable(&Index{}).Filter(`f1`, `1`).ForceIndex(`index_f1`).One(index) + throwFailNow(t, AssertIs(index.F2, 2)) + + _ = dORM.QueryTable(&Index{}).Filter(`f2`, `4`).UseIndex(`index_f2`).One(index) + throwFailNow(t, AssertIs(index.F1, 3)) + + _ = dORM.QueryTable(&Index{}).Filter(`f1`, `1`).IgnoreIndex(`index_f1`, `index_f2`).One(index) + throwFailNow(t, AssertIs(index.F2, 2)) +} + func TestOperators(t *testing.T) { qs := dORM.QueryTable("user") num, err := qs.Filter("user_name", "slene").Count() @@ -808,6 +877,17 @@ func TestOperators(t *testing.T) { throwFail(t, err) throwFail(t, AssertIs(num, 1)) + if IsMysql { + // Now only mysql support `strictexact` + num, err = qs.Filter("user_name__strictexact", "Slene").Count() + throwFail(t, err) + throwFail(t, AssertIs(num, 0)) + + num, err = qs.Filter("user_name__strictexact", "slene").Count() + throwFail(t, err) + throwFail(t, AssertIs(num, 1)) + } + num, err = qs.Filter("user_name__contains", "e").Count() throwFail(t, err) throwFail(t, AssertIs(num, 2)) @@ -1276,24 +1356,32 @@ func TestLoadRelated(t *testing.T) { throwFailNow(t, AssertIs(len(user.Posts), 2)) throwFailNow(t, AssertIs(user.Posts[0].User.ID, 3)) - num, err = dORM.LoadRelated(&user, "Posts", true) + num, err = dORM.LoadRelated(&user, "Posts", hints.DefaultRelDepth()) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 2)) throwFailNow(t, AssertIs(len(user.Posts), 2)) throwFailNow(t, AssertIs(user.Posts[0].User.UserName, "astaxie")) - num, err = dORM.LoadRelated(&user, "Posts", true, 1) + num, err = dORM.LoadRelated(&user, "Posts", + hints.DefaultRelDepth(), + hints.Limit(1)) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 1)) throwFailNow(t, AssertIs(len(user.Posts), 1)) - num, err = dORM.LoadRelated(&user, "Posts", true, 0, 0, "-Id") + num, err = dORM.LoadRelated(&user, "Posts", + hints.DefaultRelDepth(), + hints.OrderBy("-Id")) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 2)) throwFailNow(t, AssertIs(len(user.Posts), 2)) throwFailNow(t, AssertIs(user.Posts[0].Title, "Formatting")) - num, err = dORM.LoadRelated(&user, "Posts", true, 1, 1, "Id") + num, err = dORM.LoadRelated(&user, "Posts", + hints.DefaultRelDepth(), + hints.Limit(1), + hints.Offset(1), + hints.OrderBy("Id")) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 1)) throwFailNow(t, AssertIs(len(user.Posts), 1)) @@ -1315,7 +1403,7 @@ func TestLoadRelated(t *testing.T) { throwFailNow(t, AssertIs(profile.User == nil, false)) throwFailNow(t, AssertIs(profile.User.UserName, "astaxie")) - num, err = dORM.LoadRelated(&profile, "User", true) + num, err = dORM.LoadRelated(&profile, "User", hints.DefaultRelDepth()) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 1)) throwFailNow(t, AssertIs(profile.User == nil, false)) @@ -1332,7 +1420,7 @@ func TestLoadRelated(t *testing.T) { throwFailNow(t, AssertIs(user.Profile == nil, false)) throwFailNow(t, AssertIs(user.Profile.Age, 30)) - num, err = dORM.LoadRelated(&user, "Profile", true) + num, err = dORM.LoadRelated(&user, "Profile", hints.DefaultRelDepth()) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 1)) throwFailNow(t, AssertIs(user.Profile == nil, false)) @@ -1352,7 +1440,7 @@ func TestLoadRelated(t *testing.T) { throwFailNow(t, AssertIs(post.User == nil, false)) throwFailNow(t, AssertIs(post.User.UserName, "astaxie")) - num, err = dORM.LoadRelated(&post, "User", true) + num, err = dORM.LoadRelated(&post, "User", hints.DefaultRelDepth()) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 1)) throwFailNow(t, AssertIs(post.User == nil, false)) @@ -1372,7 +1460,7 @@ func TestLoadRelated(t *testing.T) { throwFailNow(t, AssertIs(len(post.Tags), 2)) throwFailNow(t, AssertIs(post.Tags[0].Name, "golang")) - num, err = dORM.LoadRelated(&post, "Tags", true) + num, err = dORM.LoadRelated(&post, "Tags", hints.DefaultRelDepth()) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 2)) throwFailNow(t, AssertIs(len(post.Tags), 2)) @@ -1393,7 +1481,7 @@ func TestLoadRelated(t *testing.T) { throwFailNow(t, AssertIs(tag.Posts[0].User.ID, 2)) throwFailNow(t, AssertIs(tag.Posts[0].User.Profile == nil, true)) - num, err = dORM.LoadRelated(&tag, "Posts", true) + num, err = dORM.LoadRelated(&tag, "Posts", hints.DefaultRelDepth()) throwFailNow(t, err) throwFailNow(t, AssertIs(num, 3)) throwFailNow(t, AssertIs(tag.Posts[0].Title, "Introduction")) @@ -1656,18 +1744,14 @@ func TestRawQueryRow(t *testing.T) { switch col { case "id": throwFail(t, AssertIs(id, 1)) + break case "time": - v = v.(time.Time).In(DefaultTimeLoc) - value := dataValues[col].(time.Time).In(DefaultTimeLoc) - throwFail(t, AssertIs(v, value, testTime)) case "date": - v = v.(time.Time).In(DefaultTimeLoc) - value := dataValues[col].(time.Time).In(DefaultTimeLoc) - throwFail(t, AssertIs(v, value, testDate)) case "datetime": v = v.(time.Time).In(DefaultTimeLoc) value := dataValues[col].(time.Time).In(DefaultTimeLoc) - throwFail(t, AssertIs(v, value, testDateTime)) + assert.True(t, v.(time.Time).Sub(value) <= time.Second) + break default: throwFail(t, AssertIs(v, dataValues[col])) } @@ -1689,6 +1773,24 @@ func TestRawQueryRow(t *testing.T) { throwFail(t, AssertIs(*status, 3)) throwFail(t, AssertIs(pid, nil)) + type Embeded struct { + Email string + } + type queryRowNoModelTest struct { + Id int + EmbedField Embeded + } + + cols = []string{ + "id", "email", + } + var row queryRowNoModelTest + query = fmt.Sprintf("SELECT %s%s%s FROM %suser%s WHERE id = ?", Q, strings.Join(cols, sep), Q, Q, Q) + err = dORM.Raw(query, 4).QueryRow(&row) + throwFail(t, err) + throwFail(t, AssertIs(row.Id, 4)) + throwFail(t, AssertIs(row.EmbedField.Email, "nobody@gmail.com")) + // test for sql.Null* fields nData := &DataNull{ NullString: sql.NullString{String: "test sql.null", Valid: true}, @@ -1740,16 +1842,13 @@ func TestQueryRows(t *testing.T) { vu := e.Interface() switch name { case "Time": - vu = vu.(time.Time).In(DefaultTimeLoc).Format(testTime) - value = value.(time.Time).In(DefaultTimeLoc).Format(testTime) case "Date": - vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDate) - value = value.(time.Time).In(DefaultTimeLoc).Format(testDate) case "DateTime": - vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDateTime) - value = value.(time.Time).In(DefaultTimeLoc).Format(testDateTime) + assert.True(t, vu.(time.Time).In(DefaultTimeLoc).Sub(value.(time.Time).In(DefaultTimeLoc)) <= time.Second) + break + default: + assert.Equal(t, value, vu) } - throwFail(t, AssertIs(vu == value, true), value, vu) } var datas2 []Data @@ -1767,16 +1866,14 @@ func TestQueryRows(t *testing.T) { vu := e.Interface() switch name { case "Time": - vu = vu.(time.Time).In(DefaultTimeLoc).Format(testTime) - value = value.(time.Time).In(DefaultTimeLoc).Format(testTime) case "Date": - vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDate) - value = value.(time.Time).In(DefaultTimeLoc).Format(testDate) case "DateTime": - vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDateTime) - value = value.(time.Time).In(DefaultTimeLoc).Format(testDateTime) + assert.True(t, vu.(time.Time).In(DefaultTimeLoc).Sub(value.(time.Time).In(DefaultTimeLoc)) <= time.Second) + break + default: + assert.Equal(t, value, vu) } - throwFail(t, AssertIs(vu == value, true), value, vu) + } var ids []int @@ -1793,7 +1890,7 @@ func TestQueryRows(t *testing.T) { throwFailNow(t, AssertIs(ids[2], 4)) throwFailNow(t, AssertIs(usernames[2], "nobody")) - //test query rows by nested struct + // test query rows by nested struct var l []userProfile query = fmt.Sprintf("SELECT * FROM %suser_profile%s LEFT JOIN %suser%s ON %suser_profile%s.%sid%s = %suser%s.%sid%s", Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q, Q) num, err = dORM.Raw(query).QueryRows(&l) @@ -2020,24 +2117,24 @@ func TestTransaction(t *testing.T) { // this test worked when database support transaction o := NewOrm() - err := o.Begin() + to, err := o.Begin() throwFail(t, err) var names = []string{"1", "2", "3"} var tag Tag tag.Name = names[0] - id, err := o.Insert(&tag) + id, err := to.Insert(&tag) throwFail(t, err) throwFail(t, AssertIs(id > 0, true)) - num, err := o.QueryTable("tag").Filter("name", "golang").Update(Params{"name": names[1]}) + num, err := to.QueryTable("tag").Filter("name", "golang").Update(Params{"name": names[1]}) throwFail(t, err) throwFail(t, AssertIs(num, 1)) switch { case IsMysql || IsSqlite: - res, err := o.Raw("INSERT INTO tag (name) VALUES (?)", names[2]).Exec() + res, err := to.Raw("INSERT INTO tag (name) VALUES (?)", names[2]).Exec() throwFail(t, err) if err == nil { id, err = res.LastInsertId() @@ -2046,22 +2143,22 @@ func TestTransaction(t *testing.T) { } } - err = o.Rollback() + err = to.Rollback() throwFail(t, err) num, err = o.QueryTable("tag").Filter("name__in", names).Count() throwFail(t, err) throwFail(t, AssertIs(num, 0)) - err = o.Begin() + to, err = o.Begin() throwFail(t, err) tag.Name = "commit" - id, err = o.Insert(&tag) + id, err = to.Insert(&tag) throwFail(t, err) throwFail(t, AssertIs(id > 0, true)) - o.Commit() + to.Commit() throwFail(t, err) num, err = o.QueryTable("tag").Filter("name", "commit").Delete() @@ -2080,33 +2177,33 @@ func TestTransactionIsolationLevel(t *testing.T) { o2 := NewOrm() // start two transaction with isolation level repeatable read - err := o1.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead}) + to1, err := o1.BeginWithCtxAndOpts(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead}) throwFail(t, err) - err = o2.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead}) + to2, err := o2.BeginWithCtxAndOpts(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead}) throwFail(t, err) // o1 insert tag var tag Tag tag.Name = "test-transaction" - id, err := o1.Insert(&tag) + id, err := to1.Insert(&tag) throwFail(t, err) throwFail(t, AssertIs(id > 0, true)) // o2 query tag table, no result - num, err := o2.QueryTable("tag").Filter("name", "test-transaction").Count() + num, err := to2.QueryTable("tag").Filter("name", "test-transaction").Count() throwFail(t, err) throwFail(t, AssertIs(num, 0)) // o1 commit - o1.Commit() + to1.Commit() // o2 query tag table, still no result - num, err = o2.QueryTable("tag").Filter("name", "test-transaction").Count() + num, err = to2.QueryTable("tag").Filter("name", "test-transaction").Count() throwFail(t, err) throwFail(t, AssertIs(num, 0)) // o2 commit and query tag table, get the result - o2.Commit() + to2.Commit() num, err = o2.QueryTable("tag").Filter("name", "test-transaction").Count() throwFail(t, err) throwFail(t, AssertIs(num, 1)) @@ -2119,14 +2216,14 @@ func TestTransactionIsolationLevel(t *testing.T) { func TestBeginTxWithContextCanceled(t *testing.T) { o := NewOrm() ctx, cancel := context.WithCancel(context.Background()) - o.BeginTx(ctx, nil) - id, err := o.Insert(&Tag{Name: "test-context"}) + to, _ := o.BeginWithCtx(ctx) + id, err := to.Insert(&Tag{Name: "test-context"}) throwFail(t, err) throwFail(t, AssertIs(id > 0, true)) // cancel the context before commit to make it error cancel() - err = o.Commit() + err = to.Commit() throwFail(t, AssertIs(err, context.Canceled)) } @@ -2187,8 +2284,8 @@ func TestInLine(t *testing.T) { throwFail(t, AssertIs(il.Name, name)) throwFail(t, AssertIs(il.Email, email)) - throwFail(t, AssertIs(il.Created.In(DefaultTimeLoc), inline.Created.In(DefaultTimeLoc), testDate)) - throwFail(t, AssertIs(il.Updated.In(DefaultTimeLoc), inline.Updated.In(DefaultTimeLoc), testDateTime)) + assert.True(t, il.Created.In(DefaultTimeLoc).Sub(inline.Created.In(DefaultTimeLoc)) <= time.Second) + assert.True(t, il.Updated.In(DefaultTimeLoc).Sub(inline.Updated.In(DefaultTimeLoc)) <= time.Second) } func TestInLineOneToOne(t *testing.T) { @@ -2413,7 +2510,7 @@ func TestInsertOrUpdate(t *testing.T) { fmt.Println("sqlite3 is nonsupport") return } - //test1 + // test1 _, err := dORM.InsertOrUpdate(&user1, "user_name") if err != nil { fmt.Println(err) @@ -2425,7 +2522,7 @@ func TestInsertOrUpdate(t *testing.T) { dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(user1.Status, test.Status)) } - //test2 + // test2 _, err = dORM.InsertOrUpdate(&user2, "user_name") if err != nil { fmt.Println(err) @@ -2439,11 +2536,11 @@ func TestInsertOrUpdate(t *testing.T) { throwFailNow(t, AssertIs(user2.Password, strings.TrimSpace(test.Password))) } - //postgres ON CONFLICT DO UPDATE SET can`t use colu=colu+values + // postgres ON CONFLICT DO UPDATE SET can`t use colu=colu+values if IsPostgres { return } - //test3 + + // test3 + _, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status+1") if err != nil { fmt.Println(err) @@ -2455,7 +2552,7 @@ func TestInsertOrUpdate(t *testing.T) { dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(user2.Status+1, test.Status)) } - //test4 - + // test4 - _, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status-1") if err != nil { fmt.Println(err) @@ -2467,7 +2564,7 @@ func TestInsertOrUpdate(t *testing.T) { dORM.Read(&test, "user_name") throwFailNow(t, AssertIs((user2.Status+1)-1, test.Status)) } - //test5 * + // test5 * _, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status*3") if err != nil { fmt.Println(err) @@ -2479,7 +2576,7 @@ func TestInsertOrUpdate(t *testing.T) { dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(((user2.Status+1)-1)*3, test.Status)) } - //test6 / + // test6 / _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status/3") if err != nil { fmt.Println(err) @@ -2492,3 +2589,82 @@ func TestInsertOrUpdate(t *testing.T) { throwFailNow(t, AssertIs((((user2.Status+1)-1)*3)/3, test.Status)) } } + +func TestStrPkInsert(t *testing.T) { + RegisterModel(new(StrPk)) + pk := `1` + value := `StrPkValues(*56` + strPk := &StrPk{ + Id: pk, + Value: value, + } + + var err error + _, err = dORM.Insert(strPk) + if err != ErrLastInsertIdUnavailable { + throwFailNow(t, AssertIs(err, nil)) + } + + var vForTesting StrPk + err = dORM.QueryTable(new(StrPk)).Filter(`id`, pk).One(&vForTesting) + throwFailNow(t, AssertIs(err, nil)) + throwFailNow(t, AssertIs(vForTesting.Value, value)) + + value2 := `s8s5da7as` + strPkForUpsert := &StrPk{ + Id: pk, + Value: value2, + } + + _, err = dORM.InsertOrUpdate(strPkForUpsert, `id`) + if err != nil { + fmt.Println(err) + if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" { + } else if err == ErrLastInsertIdUnavailable { + } else { + throwFailNow(t, err) + } + } else { + var vForTesting2 StrPk + err = dORM.QueryTable(new(StrPk)).Filter(`id`, pk).One(&vForTesting2) + throwFailNow(t, AssertIs(err, nil)) + throwFailNow(t, AssertIs(vForTesting2.Value, value2)) + } +} + +func TestPSQueryBuilder(t *testing.T) { + // only test postgres + if dORM.Driver().Type() != 4 { + return + } + + var user User + var l []userProfile + o := NewOrm() + + qb, err := NewQueryBuilder("postgres") + if err != nil { + throwFailNow(t, err) + } + qb.Select("user.id", "user.user_name"). + From("user").Where("id = ?").OrderBy("user_name"). + Desc().Limit(1).Offset(0) + sql := qb.String() + err = o.Raw(sql, 2).QueryRow(&user) + if err != nil { + throwFailNow(t, err) + } + throwFail(t, AssertIs(user.UserName, "slene")) + + qb.Select("*"). + From("user_profile").InnerJoin("user"). + On("user_profile.id = user.id") + sql = qb.String() + num, err := o.Raw(sql).QueryRows(&l) + if err != nil { + throwFailNow(t, err) + } + throwFailNow(t, AssertIs(num, 1)) + throwFailNow(t, AssertIs(l[0].UserName, "astaxie")) + throwFailNow(t, AssertIs(l[0].Age, 30)) +} diff --git a/orm/qb.go b/client/orm/qb.go similarity index 96% rename from orm/qb.go rename to client/orm/qb.go index e0655a17..c82d2255 100644 --- a/orm/qb.go +++ b/client/orm/qb.go @@ -52,7 +52,7 @@ func NewQueryBuilder(driver string) (qb QueryBuilder, err error) { } else if driver == "tidb" { qb = new(TiDBQueryBuilder) } else if driver == "postgres" { - err = errors.New("postgres query builder is not supported yet") + qb = new(PostgresQueryBuilder) } else if driver == "sqlite" { err = errors.New("sqlite query builder is not supported yet") } else { diff --git a/orm/qb_mysql.go b/client/orm/qb_mysql.go similarity index 71% rename from orm/qb_mysql.go rename to client/orm/qb_mysql.go index 23bdc9ee..19130496 100644 --- a/orm/qb_mysql.go +++ b/client/orm/qb_mysql.go @@ -25,144 +25,144 @@ const CommaSpace = ", " // MySQLQueryBuilder is the SQL build type MySQLQueryBuilder struct { - Tokens []string + tokens []string } // Select will join the fields func (qb *MySQLQueryBuilder) Select(fields ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "SELECT", strings.Join(fields, CommaSpace)) + qb.tokens = append(qb.tokens, "SELECT", strings.Join(fields, CommaSpace)) return qb } // ForUpdate add the FOR UPDATE clause func (qb *MySQLQueryBuilder) ForUpdate() QueryBuilder { - qb.Tokens = append(qb.Tokens, "FOR UPDATE") + qb.tokens = append(qb.tokens, "FOR UPDATE") return qb } // From join the tables func (qb *MySQLQueryBuilder) From(tables ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "FROM", strings.Join(tables, CommaSpace)) + qb.tokens = append(qb.tokens, "FROM", strings.Join(tables, CommaSpace)) return qb } // InnerJoin INNER JOIN the table func (qb *MySQLQueryBuilder) InnerJoin(table string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "INNER JOIN", table) + qb.tokens = append(qb.tokens, "INNER JOIN", table) return qb } // LeftJoin LEFT JOIN the table func (qb *MySQLQueryBuilder) LeftJoin(table string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "LEFT JOIN", table) + qb.tokens = append(qb.tokens, "LEFT JOIN", table) return qb } // RightJoin RIGHT JOIN the table func (qb *MySQLQueryBuilder) RightJoin(table string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "RIGHT JOIN", table) + qb.tokens = append(qb.tokens, "RIGHT JOIN", table) return qb } // On join with on cond func (qb *MySQLQueryBuilder) On(cond string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "ON", cond) + qb.tokens = append(qb.tokens, "ON", cond) return qb } // Where join the Where cond func (qb *MySQLQueryBuilder) Where(cond string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "WHERE", cond) + qb.tokens = append(qb.tokens, "WHERE", cond) return qb } // And join the and cond func (qb *MySQLQueryBuilder) And(cond string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "AND", cond) + qb.tokens = append(qb.tokens, "AND", cond) return qb } // Or join the or cond func (qb *MySQLQueryBuilder) Or(cond string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "OR", cond) + qb.tokens = append(qb.tokens, "OR", cond) return qb } // In join the IN (vals) func (qb *MySQLQueryBuilder) In(vals ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "IN", "(", strings.Join(vals, CommaSpace), ")") + qb.tokens = append(qb.tokens, "IN", "(", strings.Join(vals, CommaSpace), ")") return qb } // OrderBy join the Order by fields func (qb *MySQLQueryBuilder) OrderBy(fields ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "ORDER BY", strings.Join(fields, CommaSpace)) + qb.tokens = append(qb.tokens, "ORDER BY", strings.Join(fields, CommaSpace)) return qb } // Asc join the asc func (qb *MySQLQueryBuilder) Asc() QueryBuilder { - qb.Tokens = append(qb.Tokens, "ASC") + qb.tokens = append(qb.tokens, "ASC") return qb } // Desc join the desc func (qb *MySQLQueryBuilder) Desc() QueryBuilder { - qb.Tokens = append(qb.Tokens, "DESC") + qb.tokens = append(qb.tokens, "DESC") return qb } // Limit join the limit num func (qb *MySQLQueryBuilder) Limit(limit int) QueryBuilder { - qb.Tokens = append(qb.Tokens, "LIMIT", strconv.Itoa(limit)) + qb.tokens = append(qb.tokens, "LIMIT", strconv.Itoa(limit)) return qb } // Offset join the offset num func (qb *MySQLQueryBuilder) Offset(offset int) QueryBuilder { - qb.Tokens = append(qb.Tokens, "OFFSET", strconv.Itoa(offset)) + qb.tokens = append(qb.tokens, "OFFSET", strconv.Itoa(offset)) return qb } // GroupBy join the Group by fields func (qb *MySQLQueryBuilder) GroupBy(fields ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "GROUP BY", strings.Join(fields, CommaSpace)) + qb.tokens = append(qb.tokens, "GROUP BY", strings.Join(fields, CommaSpace)) return qb } // Having join the Having cond func (qb *MySQLQueryBuilder) Having(cond string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "HAVING", cond) + qb.tokens = append(qb.tokens, "HAVING", cond) return qb } // Update join the update table func (qb *MySQLQueryBuilder) Update(tables ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "UPDATE", strings.Join(tables, CommaSpace)) + qb.tokens = append(qb.tokens, "UPDATE", strings.Join(tables, CommaSpace)) return qb } // Set join the set kv func (qb *MySQLQueryBuilder) Set(kv ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "SET", strings.Join(kv, CommaSpace)) + qb.tokens = append(qb.tokens, "SET", strings.Join(kv, CommaSpace)) return qb } // Delete join the Delete tables func (qb *MySQLQueryBuilder) Delete(tables ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "DELETE") + qb.tokens = append(qb.tokens, "DELETE") if len(tables) != 0 { - qb.Tokens = append(qb.Tokens, strings.Join(tables, CommaSpace)) + qb.tokens = append(qb.tokens, strings.Join(tables, CommaSpace)) } return qb } // InsertInto join the insert SQL func (qb *MySQLQueryBuilder) InsertInto(table string, fields ...string) QueryBuilder { - qb.Tokens = append(qb.Tokens, "INSERT INTO", table) + qb.tokens = append(qb.tokens, "INSERT INTO", table) if len(fields) != 0 { fieldsStr := strings.Join(fields, CommaSpace) - qb.Tokens = append(qb.Tokens, "(", fieldsStr, ")") + qb.tokens = append(qb.tokens, "(", fieldsStr, ")") } return qb } @@ -170,7 +170,7 @@ func (qb *MySQLQueryBuilder) InsertInto(table string, fields ...string) QueryBui // Values join the Values(vals) func (qb *MySQLQueryBuilder) Values(vals ...string) QueryBuilder { valsStr := strings.Join(vals, CommaSpace) - qb.Tokens = append(qb.Tokens, "VALUES", "(", valsStr, ")") + qb.tokens = append(qb.tokens, "VALUES", "(", valsStr, ")") return qb } @@ -179,7 +179,9 @@ func (qb *MySQLQueryBuilder) Subquery(sub string, alias string) string { return fmt.Sprintf("(%s) AS %s", sub, alias) } -// String join all Tokens +// String join all tokens func (qb *MySQLQueryBuilder) String() string { - return strings.Join(qb.Tokens, " ") + s := strings.Join(qb.tokens, " ") + qb.tokens = qb.tokens[:0] + return s } diff --git a/client/orm/qb_postgres.go b/client/orm/qb_postgres.go new file mode 100644 index 00000000..eec784df --- /dev/null +++ b/client/orm/qb_postgres.go @@ -0,0 +1,221 @@ +package orm + +import ( + "fmt" + "strconv" + "strings" +) + +var quote string = `"` + +// PostgresQueryBuilder is the SQL build +type PostgresQueryBuilder struct { + tokens []string +} + +func processingStr(str []string) string { + s := strings.Join(str, `","`) + s = fmt.Sprintf("%s%s%s", quote, s, quote) + return s +} + +// Select will join the fields +func (qb *PostgresQueryBuilder) Select(fields ...string) QueryBuilder { + + var str string + n := len(fields) + + if fields[0] == "*" { + str = "*" + } else { + for i := 0; i < n; i++ { + sli := strings.Split(fields[i], ".") + s := strings.Join(sli, `"."`) + s = fmt.Sprintf("%s%s%s", quote, s, quote) + if n == 1 || i == n-1 { + str += s + } else { + str += s + "," + } + } + } + + qb.tokens = append(qb.tokens, "SELECT", str) + return qb +} + +// ForUpdate add the FOR UPDATE clause +func (qb *PostgresQueryBuilder) ForUpdate() QueryBuilder { + qb.tokens = append(qb.tokens, "FOR UPDATE") + return qb +} + +// From join the tables +func (qb *PostgresQueryBuilder) From(tables ...string) QueryBuilder { + str := processingStr(tables) + qb.tokens = append(qb.tokens, "FROM", str) + return qb +} + +// InnerJoin INNER JOIN the table +func (qb *PostgresQueryBuilder) InnerJoin(table string) QueryBuilder { + str := fmt.Sprintf("%s%s%s", quote, table, quote) + qb.tokens = append(qb.tokens, "INNER JOIN", str) + return qb +} + +// LeftJoin LEFT JOIN the table +func (qb *PostgresQueryBuilder) LeftJoin(table string) QueryBuilder { + str := fmt.Sprintf("%s%s%s", quote, table, quote) + qb.tokens = append(qb.tokens, "LEFT JOIN", str) + return qb +} + +// RightJoin RIGHT JOIN the table +func (qb *PostgresQueryBuilder) RightJoin(table string) QueryBuilder { + str := fmt.Sprintf("%s%s%s", quote, table, quote) + qb.tokens = append(qb.tokens, "RIGHT JOIN", str) + return qb +} + +// On join with on cond +func (qb *PostgresQueryBuilder) On(cond string) QueryBuilder { + + var str string + cond = strings.Replace(cond, " ", "", -1) + slice := strings.Split(cond, "=") + for i := 0; i < len(slice); i++ { + sli := strings.Split(slice[i], ".") + s := strings.Join(sli, `"."`) + s = fmt.Sprintf("%s%s%s", quote, s, quote) + if i == 0 { + str = s + " =" + " " + } else { + str += s + } + } + + qb.tokens = append(qb.tokens, "ON", str) + return qb +} + +// Where join the Where cond +func (qb *PostgresQueryBuilder) Where(cond string) QueryBuilder { + qb.tokens = append(qb.tokens, "WHERE", cond) + return qb +} + +// And join the and cond +func (qb *PostgresQueryBuilder) And(cond string) QueryBuilder { + qb.tokens = append(qb.tokens, "AND", cond) + return qb +} + +// Or join the or cond +func (qb *PostgresQueryBuilder) Or(cond string) QueryBuilder { + qb.tokens = append(qb.tokens, "OR", cond) + return qb +} + +// In join the IN (vals) +func (qb *PostgresQueryBuilder) In(vals ...string) QueryBuilder { + qb.tokens = append(qb.tokens, "IN", "(", strings.Join(vals, CommaSpace), ")") + return qb +} + +// OrderBy join the Order by fields +func (qb *PostgresQueryBuilder) OrderBy(fields ...string) QueryBuilder { + str := processingStr(fields) + qb.tokens = append(qb.tokens, "ORDER BY", str) + return qb +} + +// Asc join the asc +func (qb *PostgresQueryBuilder) Asc() QueryBuilder { + qb.tokens = append(qb.tokens, "ASC") + return qb +} + +// Desc join the desc +func (qb *PostgresQueryBuilder) Desc() QueryBuilder { + qb.tokens = append(qb.tokens, "DESC") + return qb +} + +// Limit join the limit num +func (qb *PostgresQueryBuilder) Limit(limit int) QueryBuilder { + qb.tokens = append(qb.tokens, "LIMIT", strconv.Itoa(limit)) + return qb +} + +// Offset join the offset num +func (qb *PostgresQueryBuilder) Offset(offset int) QueryBuilder { + qb.tokens = append(qb.tokens, "OFFSET", strconv.Itoa(offset)) + return qb +} + +// GroupBy join the Group by fields +func (qb *PostgresQueryBuilder) GroupBy(fields ...string) QueryBuilder { + str := processingStr(fields) + qb.tokens = append(qb.tokens, "GROUP BY", str) + return qb +} + +// Having join the Having cond +func (qb *PostgresQueryBuilder) Having(cond string) QueryBuilder { + qb.tokens = append(qb.tokens, "HAVING", cond) + return qb +} + +// Update join the update table +func (qb *PostgresQueryBuilder) Update(tables ...string) QueryBuilder { + str := processingStr(tables) + qb.tokens = append(qb.tokens, "UPDATE", str) + return qb +} + +// Set join the set kv +func (qb *PostgresQueryBuilder) Set(kv ...string) QueryBuilder { + qb.tokens = append(qb.tokens, "SET", strings.Join(kv, CommaSpace)) + return qb +} + +// Delete join the Delete tables +func (qb *PostgresQueryBuilder) Delete(tables ...string) QueryBuilder { + qb.tokens = append(qb.tokens, "DELETE") + if len(tables) != 0 { + str := processingStr(tables) + qb.tokens = append(qb.tokens, str) + } + return qb +} + +// InsertInto join the insert SQL +func (qb *PostgresQueryBuilder) InsertInto(table string, fields ...string) QueryBuilder { + str := fmt.Sprintf("%s%s%s", quote, table, quote) + qb.tokens = append(qb.tokens, "INSERT INTO", str) + if len(fields) != 0 { + fieldsStr := strings.Join(fields, CommaSpace) + qb.tokens = append(qb.tokens, "(", fieldsStr, ")") + } + return qb +} + +// Values join the Values(vals) +func (qb *PostgresQueryBuilder) Values(vals ...string) QueryBuilder { + valsStr := strings.Join(vals, CommaSpace) + qb.tokens = append(qb.tokens, "VALUES", "(", valsStr, ")") + return qb +} + +// Subquery join the sub as alias +func (qb *PostgresQueryBuilder) Subquery(sub string, alias string) string { + return fmt.Sprintf("(%s) AS %s", sub, alias) +} + +// String join all tokens +func (qb *PostgresQueryBuilder) String() string { + s := strings.Join(qb.tokens, " ") + qb.tokens = qb.tokens[:0] + return s +} diff --git a/client/orm/qb_tidb.go b/client/orm/qb_tidb.go new file mode 100644 index 00000000..772edb5d --- /dev/null +++ b/client/orm/qb_tidb.go @@ -0,0 +1,21 @@ +// Copyright 2015 TiDB Author. All Rights Reserved. +// +// Licensed 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 orm + +// TiDBQueryBuilder is the SQL build +type TiDBQueryBuilder struct { + MySQLQueryBuilder + tokens []string +} diff --git a/orm/types.go b/client/orm/types.go similarity index 77% rename from orm/types.go rename to client/orm/types.go index 2fd10774..34c61d51 100644 --- a/orm/types.go +++ b/client/orm/types.go @@ -19,8 +19,67 @@ import ( "database/sql" "reflect" "time" + + "github.com/astaxie/beego/core/utils" ) +// TableNaming is usually used by model +// when you custom your table name, please implement this interfaces +// for example: +// type User struct { +// ... +// } +// func (u *User) TableName() string { +// return "USER_TABLE" +// } +type TableNameI interface { + TableName() string +} + +// TableEngineI is usually used by model +// when you want to use specific engine, like myisam, you can implement this interface +// for example: +// type User struct { +// ... +// } +// func (u *User) TableEngine() string { +// return "myisam" +// } +type TableEngineI interface { + TableEngine() string +} + +// TableIndexI is usually used by model +// when you want to create indexes, you can implement this interface +// for example: +// type User struct { +// ... +// } +// func (u *User) TableIndex() [][]string { +// return [][]string{{"Name"}} +// } +type TableIndexI interface { + TableIndex() [][]string +} + +// TableUniqueI is usually used by model +// when you want to create unique indexes, you can implement this interface +// for example: +// type User struct { +// ... +// } +// func (u *User) TableUnique() [][]string { +// return [][]string{{"Email"}} +// } +type TableUniqueI interface { + TableUnique() [][]string +} + +// IsApplicableTableForDB if return false, we won't create table to this db +type IsApplicableTableForDB interface { + IsApplicableTableForDB(db string) bool +} + // Driver define database driver type Driver interface { Name() string @@ -35,35 +94,43 @@ type Fielder interface { RawValue() interface{} } -// Ormer define the orm interface -type Ormer interface { - // read data to model - // for example: - // this will find User by Id field - // u = &User{Id: user.Id} - // err = Ormer.Read(u) - // this will find User by UserName field - // u = &User{UserName: "astaxie", Password: "pass"} - // err = Ormer.Read(u, "UserName") - Read(md interface{}, cols ...string) error - // Like Read(), but with "FOR UPDATE" clause, useful in transaction. - // Some databases are not support this feature. - ReadForUpdate(md interface{}, cols ...string) error - // Try to read a row from the database, or insert one if it doesn't exist - ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) +type TxBeginner interface { + //self control transaction + Begin() (TxOrmer, error) + BeginWithCtx(ctx context.Context) (TxOrmer, error) + BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) + BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) + + //closure control transaction + DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error + DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error + DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error + DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error +} + +type TxCommitter interface { + Commit() error + Rollback() error +} + +//Data Manipulation Language +type DML interface { // insert model data to database // for example: // user := new(User) // id, err = Ormer.Insert(user) // user must be a pointer and Insert will set user's pk field - Insert(interface{}) (int64, error) + Insert(md interface{}) (int64, error) + InsertWithCtx(ctx context.Context, md interface{}) (int64, error) // mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value") // if colu type is integer : can use(+-*/), string : convert(colu,"value") // postgres: InsertOrUpdate(model,"conflictColumnName") or InsertOrUpdate(model,"conflictColumnName","colu=colu+value") // if colu type is integer : can use(+-*/), string : colu || "value" InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) + InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) // insert some models to database InsertMulti(bulk int, mds interface{}) (int64, error) + InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) // update model to database. // cols set the columns those want to update. // find model by Id(pk) field and update columns specified by fields, if cols is null then update all columns @@ -74,63 +141,92 @@ type Ormer interface { // user.Extra.Data = "orm" // num, err = Ormer.Update(&user, "Langs", "Extra") Update(md interface{}, cols ...string) (int64, error) + UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) // delete model in database Delete(md interface{}, cols ...string) (int64, error) + DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) + + // return a raw query seter for raw sql string. + // for example: + // ormer.Raw("UPDATE `user` SET `user_name` = ? WHERE `user_name` = ?", "slene", "testing").Exec() + // // update user testing's name to slene + Raw(query string, args ...interface{}) RawSeter + RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter +} + +// Data Query Language +type DQL interface { + // read data to model + // for example: + // this will find User by Id field + // u = &User{Id: user.Id} + // err = Ormer.Read(u) + // this will find User by UserName field + // u = &User{UserName: "astaxie", Password: "pass"} + // err = Ormer.Read(u, "UserName") + Read(md interface{}, cols ...string) error + ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error + + // Like Read(), but with "FOR UPDATE" clause, useful in transaction. + // Some databases are not support this feature. + ReadForUpdate(md interface{}, cols ...string) error + ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error + + // Try to read a row from the database, or insert one if it doesn't exist + ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) + ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) + // load related models to md model. // args are limit, offset int and order string. // // example: // Ormer.LoadRelated(post,"Tags") // for _,tag := range post.Tags{...} - //args[0] bool true useDefaultRelsDepth ; false depth 0 - //args[0] int loadRelationDepth - //args[1] int limit default limit 1000 - //args[2] int offset default offset 0 - //args[3] string order for example : "-Id" + // hints.DefaultRelDepth useDefaultRelsDepth ; or depth 0 + // hints.RelDepth loadRelationDepth + // hints.Limit limit default limit 1000 + // hints.Offset int offset default offset 0 + // hints.OrderBy string order for example : "-Id" // make sure the relation is defined in model struct tags. - LoadRelated(md interface{}, name string, args ...interface{}) (int64, error) + LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) + LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) + // create a models to models queryer // for example: // post := Post{Id: 4} // m2m := Ormer.QueryM2M(&post, "Tags") QueryM2M(md interface{}, name string) QueryM2Mer + QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer + // return a QuerySeter for table operations. // table name can be string or struct. // e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)), QueryTable(ptrStructOrTableName interface{}) QuerySeter - // switch to another registered database driver by given name. - Using(name string) error - // begin transaction - // for example: - // o := NewOrm() - // err := o.Begin() - // ... - // err = o.Rollback() - Begin() error - // begin transaction with provided context and option - // the provided context is used until the transaction is committed or rolled back. - // if the context is canceled, the transaction will be rolled back. - // the provided TxOptions is optional and may be nil if defaults should be used. - // if a non-default isolation level is used that the driver doesn't support, an error will be returned. - // for example: - // o := NewOrm() - // err := o.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead}) - // ... - // err = o.Rollback() - BeginTx(ctx context.Context, opts *sql.TxOptions) error - // commit transaction - Commit() error - // rollback transaction - Rollback() error - // return a raw query seter for raw sql string. - // for example: - // ormer.Raw("UPDATE `user` SET `user_name` = ? WHERE `user_name` = ?", "slene", "testing").Exec() - // // update user testing's name to slene - Raw(query string, args ...interface{}) RawSeter - Driver() Driver + QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) QuerySeter + DBStats() *sql.DBStats } +type DriverGetter interface { + Driver() Driver +} + +type ormer interface { + DQL + DML + DriverGetter +} + +type Ormer interface { + ormer + TxBeginner +} + +type TxOrmer interface { + ormer + TxCommitter +} + // Inserter insert prepared statement type Inserter interface { Insert(interface{}) (int64, error) @@ -193,6 +289,21 @@ type QuerySeter interface { // for example: // qs.OrderBy("-status") OrderBy(exprs ...string) QuerySeter + // add FORCE INDEX expression. + // for example: + // qs.ForceIndex(`idx_name1`,`idx_name2`) + // ForceIndex, UseIndex , IgnoreIndex are mutually exclusive + ForceIndex(indexes ...string) QuerySeter + // add USE INDEX expression. + // for example: + // qs.UseIndex(`idx_name1`,`idx_name2`) + // ForceIndex, UseIndex , IgnoreIndex are mutually exclusive + UseIndex(indexes ...string) QuerySeter + // add IGNORE INDEX expression. + // for example: + // qs.IgnoreIndex(`idx_name1`,`idx_name2`) + // ForceIndex, UseIndex , IgnoreIndex are mutually exclusive + IgnoreIndex(indexes ...string) QuerySeter // set relation model to query together. // it will query relation models and assign to parent model. // for example: @@ -229,7 +340,7 @@ type QuerySeter interface { // }) // user slene's name will change to slene2 Update(values Params) (int64, error) // delete from table - //for example: + // for example: // num ,err = qs.Filter("user_name__in", "testing1", "testing2").Delete() // //delete two user who's name is testing1 or testing2 Delete() (int64, error) @@ -314,8 +425,8 @@ type QueryM2Mer interface { // remove models following the origin model relationship // only delete rows from m2m table // for example: - //tag3 := &Tag{Id:5,Name: "TestTag3"} - //num, err = m2m.Remove(tag3) + // tag3 := &Tag{Id:5,Name: "TestTag3"} + // num, err = m2m.Remove(tag3) Remove(...interface{}) (int64, error) // check model is existed in relationship of origin model Exist(interface{}) bool @@ -337,10 +448,10 @@ type RawPreparer interface { // sql := fmt.Sprintf("SELECT %sid%s,%sname%s FROM %suser%s WHERE id = ?",Q,Q,Q,Q,Q,Q) // rs := Ormer.Raw(sql, 1) type RawSeter interface { - //execute sql and get result + // execute sql and get result Exec() (sql.Result, error) - //query data and map to container - //for example: + // query data and map to container + // for example: // var name string // var id int // rs.QueryRow(&id,&name) // id==2 name=="slene" @@ -396,11 +507,11 @@ type RawSeter interface { type stmtQuerier interface { Close() error Exec(args ...interface{}) (sql.Result, error) - //ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) + // ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) Query(args ...interface{}) (*sql.Rows, error) - //QueryContext(args ...interface{}) (*sql.Rows, error) + // QueryContext(args ...interface{}) (*sql.Rows, error) QueryRow(args ...interface{}) *sql.Row - //QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row + // QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row } // db querier @@ -438,24 +549,27 @@ type txEnder interface { // base database struct type dbBaser interface { Read(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string, bool) error + ReadBatch(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, *time.Location, []string) (int64, error) + Count(dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error) + ReadValues(dbQuerier, *querySet, *modelInfo, *Condition, []string, interface{}, *time.Location) (int64, error) + Insert(dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error) InsertOrUpdate(dbQuerier, *modelInfo, reflect.Value, *alias, ...string) (int64, error) InsertMulti(dbQuerier, *modelInfo, reflect.Value, int, *time.Location) (int64, error) InsertValue(dbQuerier, *modelInfo, bool, []string, []interface{}) (int64, error) InsertStmt(stmtQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error) + Update(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error) - Delete(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error) - ReadBatch(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, *time.Location, []string) (int64, error) - SupportUpdateJoin() bool UpdateBatch(dbQuerier, *querySet, *modelInfo, *Condition, Params, *time.Location) (int64, error) + + Delete(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error) DeleteBatch(dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error) - Count(dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error) + + SupportUpdateJoin() bool OperatorSQL(string) string GenerateOperatorSQL(*modelInfo, *fieldInfo, string, []interface{}, *time.Location) (string, []interface{}) GenerateOperatorLeftCol(*fieldInfo, string, *string) PrepareInsert(dbQuerier, *modelInfo) (stmtQuerier, string, error) - ReadValues(dbQuerier, *querySet, *modelInfo, *Condition, []string, interface{}, *time.Location) (int64, error) - RowsTo(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, string, string, *time.Location) (int64, error) MaxLimit() uint64 TableQuote() string ReplaceMarks(*string) @@ -470,4 +584,6 @@ type dbBaser interface { IndexExists(dbQuerier, string, string) bool collectFieldValue(*modelInfo, *fieldInfo, reflect.Value, bool, *time.Location) (interface{}, error) setval(dbQuerier, *modelInfo, []string) error + + GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string } diff --git a/orm/utils.go b/client/orm/utils.go similarity index 100% rename from orm/utils.go rename to client/orm/utils.go diff --git a/client/orm/utils_test.go b/client/orm/utils_test.go new file mode 100644 index 00000000..7d94cada --- /dev/null +++ b/client/orm/utils_test.go @@ -0,0 +1,70 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 orm + +import ( + "testing" +) + +func TestCamelString(t *testing.T) { + snake := []string{"pic_url", "hello_world_", "hello__World", "_HelLO_Word", "pic_url_1", "pic_url__1"} + camel := []string{"PicUrl", "HelloWorld", "HelloWorld", "HelLOWord", "PicUrl1", "PicUrl1"} + + answer := make(map[string]string) + for i, v := range snake { + answer[v] = camel[i] + } + + for _, v := range snake { + res := camelString(v) + if res != answer[v] { + t.Error("Unit Test Fail:", v, res, answer[v]) + } + } +} + +func TestSnakeString(t *testing.T) { + camel := []string{"PicUrl", "HelloWorld", "HelloWorld", "HelLOWord", "PicUrl1", "XyXX"} + snake := []string{"pic_url", "hello_world", "hello_world", "hel_l_o_word", "pic_url1", "xy_x_x"} + + answer := make(map[string]string) + for i, v := range camel { + answer[v] = snake[i] + } + + for _, v := range camel { + res := snakeString(v) + if res != answer[v] { + t.Error("Unit Test Fail:", v, res, answer[v]) + } + } +} + +func TestSnakeStringWithAcronym(t *testing.T) { + camel := []string{"ID", "PicURL", "HelloWorld", "HelloWorld", "HelLOWord", "PicUrl1", "XyXX"} + snake := []string{"id", "pic_url", "hello_world", "hello_world", "hel_lo_word", "pic_url1", "xy_xx"} + + answer := make(map[string]string) + for i, v := range camel { + answer[v] = snake[i] + } + + for _, v := range camel { + res := snakeStringWithAcronym(v) + if res != answer[v] { + t.Error("Unit Test Fail:", v, res, answer[v]) + } + } +} diff --git a/core/bean/context.go b/core/bean/context.go new file mode 100644 index 00000000..7cee2c7e --- /dev/null +++ b/core/bean/context.go @@ -0,0 +1,20 @@ +// Copyright 2020 +// +// Licensed 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 bean + +// ApplicationContext define for future +// when we decide to support DI, IoC, this will be core API +type ApplicationContext interface { +} diff --git a/core/bean/doc.go b/core/bean/doc.go new file mode 100644 index 00000000..f806a081 --- /dev/null +++ b/core/bean/doc.go @@ -0,0 +1,17 @@ +// Copyright 2020 +// +// Licensed 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. + +// bean is a basic package +// it should not depend on other modules except common module, log module and config module +package bean diff --git a/core/bean/factory.go b/core/bean/factory.go new file mode 100644 index 00000000..1097604c --- /dev/null +++ b/core/bean/factory.go @@ -0,0 +1,25 @@ +// Copyright 2020 +// +// Licensed 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 bean + +import ( + "context" +) + +// AutoWireBeanFactory wire the bean based on ApplicationContext and context.Context +type AutoWireBeanFactory interface { + // AutoWire will wire the bean. + AutoWire(ctx context.Context, appCtx ApplicationContext, bean interface{}) error +} diff --git a/core/bean/metadata.go b/core/bean/metadata.go new file mode 100644 index 00000000..e2e34f55 --- /dev/null +++ b/core/bean/metadata.go @@ -0,0 +1,28 @@ +// Copyright 2020 +// +// Licensed 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 bean + +// BeanMetadata, in other words, bean's config. +// it could be read from config file +type BeanMetadata struct { + // Fields: field name => field metadata + Fields map[string]*FieldMetadata +} + +// FieldMetadata contains metadata +type FieldMetadata struct { + // default value in string format + DftValue string +} diff --git a/core/bean/tag_auto_wire_bean_factory.go b/core/bean/tag_auto_wire_bean_factory.go new file mode 100644 index 00000000..b88a42ff --- /dev/null +++ b/core/bean/tag_auto_wire_bean_factory.go @@ -0,0 +1,231 @@ +// Copyright 2020 +// +// Licensed 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 bean + +import ( + "context" + "fmt" + "reflect" + "strconv" + + "github.com/pkg/errors" + + "github.com/astaxie/beego/core/logs" +) + +const DefaultValueTagKey = "default" + +// TagAutoWireBeanFactory wire the bean based on Fields' tag +// if field's value is "zero value", we will execute injection +// see reflect.Value.IsZero() +// If field's kind is one of(reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Slice +// reflect.UnsafePointer, reflect.Array, reflect.Uintptr, reflect.Complex64, reflect.Complex128 +// reflect.Ptr, reflect.Struct), +// it will be ignored +type TagAutoWireBeanFactory struct { + // we allow user register their TypeAdapter + Adapters map[string]TypeAdapter + + // FieldTagParser is an extension point which means that you can custom how to read field's metadata from tag + FieldTagParser func(field reflect.StructField) *FieldMetadata +} + +// NewTagAutoWireBeanFactory create an instance of TagAutoWireBeanFactory +// by default, we register Time adapter, the time will be parse by using layout "2006-01-02 15:04:05" +// If you need more adapter, you can implement interface TypeAdapter +func NewTagAutoWireBeanFactory() *TagAutoWireBeanFactory { + return &TagAutoWireBeanFactory{ + Adapters: map[string]TypeAdapter{ + "Time": &TimeTypeAdapter{Layout: "2006-01-02 15:04:05"}, + }, + + FieldTagParser: func(field reflect.StructField) *FieldMetadata { + return &FieldMetadata{ + DftValue: field.Tag.Get(DefaultValueTagKey), + } + }, + } +} + +// AutoWire use value from appCtx to wire the bean, or use default value, or do nothing +func (t *TagAutoWireBeanFactory) AutoWire(ctx context.Context, appCtx ApplicationContext, bean interface{}) error { + if bean == nil { + return nil + } + + v := reflect.Indirect(reflect.ValueOf(bean)) + + bm := t.getConfig(v) + + // field name, field metadata + for fn, fm := range bm.Fields { + + fValue := v.FieldByName(fn) + if len(fm.DftValue) == 0 || !t.needInject(fValue) || !fValue.CanSet() { + continue + } + + // handle type adapter + typeName := fValue.Type().Name() + if adapter, ok := t.Adapters[typeName]; ok { + dftValue, err := adapter.DefaultValue(ctx, fm.DftValue) + if err == nil { + fValue.Set(reflect.ValueOf(dftValue)) + continue + } else { + return err + } + } + + switch fValue.Kind() { + case reflect.Bool: + if v, err := strconv.ParseBool(fm.DftValue); err != nil { + return errors.WithMessage(err, + fmt.Sprintf("can not convert the field[%s]'s default value[%s] to bool value", + fn, fm.DftValue)) + } else { + fValue.SetBool(v) + continue + } + case reflect.Int: + if err := t.setIntXValue(fm.DftValue, 0, fn, fValue); err != nil { + return err + } + continue + case reflect.Int8: + if err := t.setIntXValue(fm.DftValue, 8, fn, fValue); err != nil { + return err + } + continue + case reflect.Int16: + if err := t.setIntXValue(fm.DftValue, 16, fn, fValue); err != nil { + return err + } + continue + + case reflect.Int32: + if err := t.setIntXValue(fm.DftValue, 32, fn, fValue); err != nil { + return err + } + continue + + case reflect.Int64: + if err := t.setIntXValue(fm.DftValue, 64, fn, fValue); err != nil { + return err + } + continue + + case reflect.Uint: + if err := t.setUIntXValue(fm.DftValue, 0, fn, fValue); err != nil { + return err + } + + case reflect.Uint8: + if err := t.setUIntXValue(fm.DftValue, 8, fn, fValue); err != nil { + return err + } + continue + + case reflect.Uint16: + if err := t.setUIntXValue(fm.DftValue, 16, fn, fValue); err != nil { + return err + } + continue + case reflect.Uint32: + if err := t.setUIntXValue(fm.DftValue, 32, fn, fValue); err != nil { + return err + } + continue + + case reflect.Uint64: + if err := t.setUIntXValue(fm.DftValue, 64, fn, fValue); err != nil { + return err + } + continue + + case reflect.Float32: + if err := t.setFloatXValue(fm.DftValue, 32, fn, fValue); err != nil { + return err + } + continue + case reflect.Float64: + if err := t.setFloatXValue(fm.DftValue, 64, fn, fValue); err != nil { + return err + } + continue + + case reflect.String: + fValue.SetString(fm.DftValue) + continue + + // case reflect.Ptr: + // case reflect.Struct: + default: + logs.Warn("this field[%s] has default setting, but we don't support this type: %s", + fn, fValue.Kind().String()) + } + } + return nil +} + +func (t *TagAutoWireBeanFactory) setFloatXValue(dftValue string, bitSize int, fn string, fv reflect.Value) error { + if v, err := strconv.ParseFloat(dftValue, bitSize); err != nil { + return errors.WithMessage(err, + fmt.Sprintf("can not convert the field[%s]'s default value[%s] to float%d value", + fn, dftValue, bitSize)) + } else { + fv.SetFloat(v) + return nil + } +} + +func (t *TagAutoWireBeanFactory) setUIntXValue(dftValue string, bitSize int, fn string, fv reflect.Value) error { + if v, err := strconv.ParseUint(dftValue, 10, bitSize); err != nil { + return errors.WithMessage(err, + fmt.Sprintf("can not convert the field[%s]'s default value[%s] to uint%d value", + fn, dftValue, bitSize)) + } else { + fv.SetUint(v) + return nil + } +} + +func (t *TagAutoWireBeanFactory) setIntXValue(dftValue string, bitSize int, fn string, fv reflect.Value) error { + if v, err := strconv.ParseInt(dftValue, 10, bitSize); err != nil { + return errors.WithMessage(err, + fmt.Sprintf("can not convert the field[%s]'s default value[%s] to int%d value", + fn, dftValue, bitSize)) + } else { + fv.SetInt(v) + return nil + } +} + +func (t *TagAutoWireBeanFactory) needInject(fValue reflect.Value) bool { + return fValue.IsZero() +} + +// getConfig never return nil +func (t *TagAutoWireBeanFactory) getConfig(beanValue reflect.Value) *BeanMetadata { + fms := make(map[string]*FieldMetadata, beanValue.NumField()) + for i := 0; i < beanValue.NumField(); i++ { + // f => StructField + f := beanValue.Type().Field(i) + fms[f.Name] = t.FieldTagParser(f) + } + return &BeanMetadata{ + Fields: fms, + } +} diff --git a/core/bean/tag_auto_wire_bean_factory_test.go b/core/bean/tag_auto_wire_bean_factory_test.go new file mode 100644 index 00000000..bcdada67 --- /dev/null +++ b/core/bean/tag_auto_wire_bean_factory_test.go @@ -0,0 +1,75 @@ +// Copyright 2020 +// +// Licensed 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 bean + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestTagAutoWireBeanFactory_AutoWire(t *testing.T) { + factory := NewTagAutoWireBeanFactory() + bm := &ComplicateStruct{} + err := factory.AutoWire(context.Background(), nil, bm) + assert.Nil(t, err) + assert.Equal(t, 12, bm.IntValue) + assert.Equal(t, "hello, strValue", bm.StrValue) + + assert.Equal(t, int8(8), bm.Int8Value) + assert.Equal(t, int16(16), bm.Int16Value) + assert.Equal(t, int32(32), bm.Int32Value) + assert.Equal(t, int64(64), bm.Int64Value) + + assert.Equal(t, uint(13), bm.UintValue) + assert.Equal(t, uint8(88), bm.Uint8Value) + assert.Equal(t, uint16(1616), bm.Uint16Value) + assert.Equal(t, uint32(3232), bm.Uint32Value) + assert.Equal(t, uint64(6464), bm.Uint64Value) + + assert.Equal(t, float32(32.32), bm.Float32Value) + assert.Equal(t, float64(64.64), bm.Float64Value) + + assert.True(t, bm.BoolValue) + assert.Equal(t, 0, bm.ignoreInt) + + assert.NotNil(t, bm.TimeValue) +} + +type ComplicateStruct struct { + IntValue int `default:"12"` + StrValue string `default:"hello, strValue"` + Int8Value int8 `default:"8"` + Int16Value int16 `default:"16"` + Int32Value int32 `default:"32"` + Int64Value int64 `default:"64"` + + UintValue uint `default:"13"` + Uint8Value uint8 `default:"88"` + Uint16Value uint16 `default:"1616"` + Uint32Value uint32 `default:"3232"` + Uint64Value uint64 `default:"6464"` + + Float32Value float32 `default:"32.32"` + Float64Value float64 `default:"64.64"` + + BoolValue bool `default:"true"` + + ignoreInt int `default:"11"` + + TimeValue time.Time `default:"2018-02-03 12:13:14.000"` +} diff --git a/core/bean/time_type_adapter.go b/core/bean/time_type_adapter.go new file mode 100644 index 00000000..b0e99896 --- /dev/null +++ b/core/bean/time_type_adapter.go @@ -0,0 +1,35 @@ +// Copyright 2020 +// +// Licensed 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 bean + +import ( + "context" + "time" +) + +// TimeTypeAdapter process the time.Time +type TimeTypeAdapter struct { + Layout string +} + +// DefaultValue parse the DftValue to time.Time +// and if the DftValue == now +// time.Now() is returned +func (t *TimeTypeAdapter) DefaultValue(ctx context.Context, dftValue string) (interface{}, error) { + if dftValue == "now" { + return time.Now(), nil + } + return time.Parse(t.Layout, dftValue) +} diff --git a/core/bean/time_type_adapter_test.go b/core/bean/time_type_adapter_test.go new file mode 100644 index 00000000..140ef5a6 --- /dev/null +++ b/core/bean/time_type_adapter_test.go @@ -0,0 +1,29 @@ +// Copyright 2020 +// +// Licensed 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 bean + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTimeTypeAdapter_DefaultValue(t *testing.T) { + typeAdapter := &TimeTypeAdapter{Layout: "2006-01-02 15:04:05"} + tm, err := typeAdapter.DefaultValue(context.Background(), "2018-02-03 12:34:11") + assert.Nil(t, err) + assert.NotNil(t, tm) +} diff --git a/core/bean/type_adapter.go b/core/bean/type_adapter.go new file mode 100644 index 00000000..5869032d --- /dev/null +++ b/core/bean/type_adapter.go @@ -0,0 +1,26 @@ +// Copyright 2020 +// +// Licensed 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 bean + +import ( + "context" +) + +// TypeAdapter is an abstraction that define some behavior of target type +// usually, we don't use this to support basic type since golang has many restriction for basic types +// This is an important extension point +type TypeAdapter interface { + DefaultValue(ctx context.Context, dftValue string) (interface{}, error) +} diff --git a/core/config/base_config_test.go b/core/config/base_config_test.go new file mode 100644 index 00000000..74a669a7 --- /dev/null +++ b/core/config/base_config_test.go @@ -0,0 +1,72 @@ +// Copyright 2020 +// +// Licensed 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 config + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBaseConfiger_DefaultBool(t *testing.T) { + bc := newBaseConfier("true") + assert.True(t, bc.DefaultBool("key1", false)) + assert.True(t, bc.DefaultBool("key2", true)) +} + +func TestBaseConfiger_DefaultFloat(t *testing.T) { + bc := newBaseConfier("12.3") + assert.Equal(t, 12.3, bc.DefaultFloat("key1", 0.1)) + assert.Equal(t, 0.1, bc.DefaultFloat("key2", 0.1)) +} + +func TestBaseConfiger_DefaultInt(t *testing.T) { + bc := newBaseConfier("10") + assert.Equal(t, 10, bc.DefaultInt("key1", 8)) + assert.Equal(t, 8, bc.DefaultInt("key2", 8)) +} + +func TestBaseConfiger_DefaultInt64(t *testing.T) { + bc := newBaseConfier("64") + assert.Equal(t, int64(64), bc.DefaultInt64("key1", int64(8))) + assert.Equal(t, int64(8), bc.DefaultInt64("key2", int64(8))) +} + +func TestBaseConfiger_DefaultString(t *testing.T) { + bc := newBaseConfier("Hello") + assert.Equal(t, "Hello", bc.DefaultString("key1", "world")) + assert.Equal(t, "world", bc.DefaultString("key2", "world")) +} + +func TestBaseConfiger_DefaultStrings(t *testing.T) { + bc := newBaseConfier("Hello;world") + assert.Equal(t, []string{"Hello", "world"}, bc.DefaultStrings("key1", []string{"world"})) + assert.Equal(t, []string{"world"}, bc.DefaultStrings("key2", []string{"world"})) +} + +func newBaseConfier(str1 string) *BaseConfiger { + return &BaseConfiger{ + reader: func(ctx context.Context, key string) (string, error) { + if key == "key1" { + return str1, nil + } else { + return "", errors.New("mock error") + } + + }, + } +} diff --git a/config/config.go b/core/config/config.go similarity index 65% rename from config/config.go rename to core/config/config.go index bfd79e85..a4a24fff 100644 --- a/config/config.go +++ b/core/config/config.go @@ -15,7 +15,7 @@ // Package config is used to parse config. // Usage: // import "github.com/astaxie/beego/config" -//Examples. +// Examples. // // cnf, err := config.NewConfig("ini", "config.conf") // @@ -37,36 +37,163 @@ // cnf.DIY(key string) (interface{}, error) // cnf.GetSection(section string) (map[string]string, error) // cnf.SaveConfigFile(filename string) error -//More docs http://beego.me/docs/module/config.md +// More docs http://beego.me/docs/module/config.md package config import ( + "context" + "errors" "fmt" "os" "reflect" + "strconv" + "strings" "time" ) // Configer defines how to get and set value from configuration raw data. type Configer interface { - Set(key, val string) error //support section::key type in given key when using ini type. - String(key string) string //support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. - Strings(key string) []string //get string slice + // support section::key type in given key when using ini type. + Set(key, val string) error + + // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. + String(key string) (string, error) + // get string slice + Strings(key string) ([]string, error) Int(key string) (int, error) Int64(key string) (int64, error) Bool(key string) (bool, error) Float(key string) (float64, error) - DefaultString(key string, defaultVal string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. - DefaultStrings(key string, defaultVal []string) []string //get string slice + // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. + DefaultString(key string, defaultVal string) string + // get string slice + DefaultStrings(key string, defaultVal []string) []string DefaultInt(key string, defaultVal int) int DefaultInt64(key string, defaultVal int64) int64 DefaultBool(key string, defaultVal bool) bool DefaultFloat(key string, defaultVal float64) float64 + + // DIY return the original value DIY(key string) (interface{}, error) + GetSection(section string) (map[string]string, error) + + Unmarshaler(prefix string, obj interface{}, opt ...DecodeOption) error + Sub(key string) (Configer, error) + OnChange(key string, fn func(value string)) SaveConfigFile(filename string) error } +type BaseConfiger struct { + // The reader should support key like "a.b.c" + reader func(ctx context.Context, key string) (string, error) +} + +func NewBaseConfiger(reader func(ctx context.Context, key string) (string, error)) BaseConfiger { + return BaseConfiger{ + reader: reader, + } +} + +func (c *BaseConfiger) Int(key string) (int, error) { + res, err := c.reader(context.TODO(), key) + if err != nil { + return 0, err + } + return strconv.Atoi(res) +} + +func (c *BaseConfiger) Int64(key string) (int64, error) { + res, err := c.reader(context.TODO(), key) + if err != nil { + return 0, err + } + return strconv.ParseInt(res, 10, 64) +} + +func (c *BaseConfiger) Bool(key string) (bool, error) { + res, err := c.reader(context.TODO(), key) + if err != nil { + return false, err + } + return ParseBool(res) +} + +func (c *BaseConfiger) Float(key string) (float64, error) { + res, err := c.reader(context.TODO(), key) + if err != nil { + return 0, err + } + return strconv.ParseFloat(res, 64) +} + +// DefaultString returns the string value for a given key. +// if err != nil or value is empty return defaultval +func (c *BaseConfiger) DefaultString(key string, defaultVal string) string { + if res, err := c.String(key); res != "" && err == nil { + return res + } + return defaultVal +} + +// DefaultStrings returns the []string value for a given key. +// if err != nil return defaultval +func (c *BaseConfiger) DefaultStrings(key string, defaultVal []string) []string { + if res, err := c.Strings(key); len(res) > 0 && err == nil { + return res + } + return defaultVal +} + +func (c *BaseConfiger) DefaultInt(key string, defaultVal int) int { + if res, err := c.Int(key); err == nil { + return res + } + return defaultVal +} + +func (c *BaseConfiger) DefaultInt64(key string, defaultVal int64) int64 { + if res, err := c.Int64(key); err == nil { + return res + } + return defaultVal +} + +func (c *BaseConfiger) DefaultBool(key string, defaultVal bool) bool { + if res, err := c.Bool(key); err == nil { + return res + } + return defaultVal +} +func (c *BaseConfiger) DefaultFloat(key string, defaultVal float64) float64 { + if res, err := c.Float(key); err == nil { + return res + } + return defaultVal +} + +func (c *BaseConfiger) String(key string) (string, error) { + return c.reader(context.TODO(), key) +} + +// Strings returns the []string value for a given key. +// Return nil if config value does not exist or is empty. +func (c *BaseConfiger) Strings(key string) ([]string, error) { + res, err := c.String(key) + if err != nil || res == "" { + return nil, err + } + return strings.Split(res, ";"), nil +} + +func (c *BaseConfiger) Sub(key string) (Configer, error) { + return nil, errors.New("unsupported operation") +} + +func (c *BaseConfiger) OnChange(key string, fn func(value string)) { + // do nothing +} + // Config is the adapter interface for parsing config file to get raw data to Configer. type Config interface { Parse(key string) (Configer, error) @@ -240,3 +367,8 @@ func ToString(x interface{}) string { // Fallback to fmt package for anything else like numeric types return fmt.Sprint(x) } + +type DecodeOption func(options decodeOptions) + +type decodeOptions struct { +} diff --git a/core/config/config_test.go b/core/config/config_test.go new file mode 100644 index 00000000..15d6ffa6 --- /dev/null +++ b/core/config/config_test.go @@ -0,0 +1,55 @@ +// Copyright 2016 beego Author. All Rights Reserved. +// +// Licensed 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 config + +import ( + "os" + "testing" +) + +func TestExpandValueEnv(t *testing.T) { + + testCases := []struct { + item string + want string + }{ + {"", ""}, + {"$", "$"}, + {"{", "{"}, + {"{}", "{}"}, + {"${}", ""}, + {"${|}", ""}, + {"${}", ""}, + {"${{}}", ""}, + {"${{||}}", "}"}, + {"${pwd||}", ""}, + {"${pwd||}", ""}, + {"${pwd||}", ""}, + {"${pwd||}}", "}"}, + {"${pwd||{{||}}}", "{{||}}"}, + {"${GOPATH}", os.Getenv("GOPATH")}, + {"${GOPATH||}", os.Getenv("GOPATH")}, + {"${GOPATH||root}", os.Getenv("GOPATH")}, + {"${GOPATH_NOT||root}", "root"}, + {"${GOPATH_NOT||||root}", "||root"}, + } + + for _, c := range testCases { + if got := ExpandValueEnv(c.item); got != c.want { + t.Errorf("expand value error, item %q want %q, got %q", c.item, c.want, got) + } + } + +} diff --git a/config/env/env.go b/core/config/env/env.go similarity index 96% rename from config/env/env.go rename to core/config/env/env.go index 34f094fe..d3903d74 100644 --- a/config/env/env.go +++ b/core/config/env/env.go @@ -21,7 +21,7 @@ import ( "os" "strings" - "github.com/astaxie/beego/utils" + "github.com/astaxie/beego/core/utils" ) var env *utils.BeeMap @@ -34,7 +34,7 @@ func init() { } } -// Get returns a value by key. +// Get returns a value for a given key. // If the key does not exist, the default value will be returned. func Get(key string, defVal string) string { if val := env.Get(key); val != nil { diff --git a/core/config/env/env_test.go b/core/config/env/env_test.go new file mode 100644 index 00000000..3f1d4dba --- /dev/null +++ b/core/config/env/env_test.go @@ -0,0 +1,75 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// Copyright 2017 Faissal Elamraoui. All Rights Reserved. +// +// Licensed 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 env + +import ( + "os" + "testing" +) + +func TestEnvGet(t *testing.T) { + gopath := Get("GOPATH", "") + if gopath != os.Getenv("GOPATH") { + t.Error("expected GOPATH not empty.") + } + + noExistVar := Get("NOEXISTVAR", "foo") + if noExistVar != "foo" { + t.Errorf("expected NOEXISTVAR to equal foo, got %s.", noExistVar) + } +} + +func TestEnvMustGet(t *testing.T) { + gopath, err := MustGet("GOPATH") + if err != nil { + t.Error(err) + } + + if gopath != os.Getenv("GOPATH") { + t.Errorf("expected GOPATH to be the same, got %s.", gopath) + } + + _, err = MustGet("NOEXISTVAR") + if err == nil { + t.Error("expected error to be non-nil") + } +} + +func TestEnvSet(t *testing.T) { + Set("MYVAR", "foo") + myVar := Get("MYVAR", "bar") + if myVar != "foo" { + t.Errorf("expected MYVAR to equal foo, got %s.", myVar) + } +} + +func TestEnvMustSet(t *testing.T) { + err := MustSet("FOO", "bar") + if err != nil { + t.Error(err) + } + + fooVar := os.Getenv("FOO") + if fooVar != "bar" { + t.Errorf("expected FOO variable to equal bar, got %s.", fooVar) + } +} + +func TestEnvGetAll(t *testing.T) { + envMap := GetAll() + if len(envMap) == 0 { + t.Error("expected environment not empty.") + } +} diff --git a/core/config/error.go b/core/config/error.go new file mode 100644 index 00000000..e4636c45 --- /dev/null +++ b/core/config/error.go @@ -0,0 +1,25 @@ +// Copyright 2020 +// +// Licensed 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 config + +import ( + "github.com/pkg/errors" +) + +// now not all implementation return those error codes +var ( + KeyNotFoundError = errors.New("the key is not found") + InvalidValueTypeError = errors.New("the value is not expected type") +) diff --git a/core/config/etcd/config.go b/core/config/etcd/config.go new file mode 100644 index 00000000..6c3d33d4 --- /dev/null +++ b/core/config/etcd/config.go @@ -0,0 +1,195 @@ +// Copyright 2020 +// +// Licensed 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 etcd + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/coreos/etcd/clientv3" + grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" + "google.golang.org/grpc" + + "github.com/astaxie/beego/core/config" + "github.com/astaxie/beego/core/logs" +) + +type EtcdConfiger struct { + prefix string + client *clientv3.Client + config.BaseConfiger +} + +func newEtcdConfiger(client *clientv3.Client, prefix string) *EtcdConfiger { + res := &EtcdConfiger{ + client: client, + prefix: prefix, + } + + res.BaseConfiger = config.NewBaseConfiger(res.reader) + return res +} + +// reader is an general implementation that read config from etcd. +func (e *EtcdConfiger) reader(ctx context.Context, key string) (string, error) { + resp, err := get(e.client, e.prefix+key) + if err != nil { + return "", err + } + + if resp.Count > 0 { + return string(resp.Kvs[0].Value), nil + } + + return "", nil +} + +// Set do nothing and return an error +// I think write data to remote config center is not a good practice +func (e *EtcdConfiger) Set(key, val string) error { + return errors.New("Unsupported operation") +} + +// DIY return the original response from etcd +// be careful when you decide to use this +func (e *EtcdConfiger) DIY(key string) (interface{}, error) { + return get(e.client, key) +} + +// GetSection in this implementation, we use section as prefix +func (e *EtcdConfiger) GetSection(section string) (map[string]string, error) { + var ( + resp *clientv3.GetResponse + err error + ) + + resp, err = e.client.Get(context.TODO(), e.prefix+section, clientv3.WithPrefix()) + + if err != nil { + return nil, errors.WithMessage(err, "GetSection failed") + } + res := make(map[string]string, len(resp.Kvs)) + for _, kv := range resp.Kvs { + res[string(kv.Key)] = string(kv.Value) + } + return res, nil +} + +func (e *EtcdConfiger) SaveConfigFile(filename string) error { + return errors.New("Unsupported operation") +} + +// Unmarshaler is not very powerful because we lost the type information when we get configuration from etcd +// for example, when we got "5", we are not sure whether it's int 5, or it's string "5" +// TODO(support more complicated decoder) +func (e *EtcdConfiger) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error { + res, err := e.GetSection(prefix) + if err != nil { + return errors.WithMessage(err, fmt.Sprintf("could not read config with prefix: %s", prefix)) + } + + prefixLen := len(e.prefix + prefix) + m := make(map[string]string, len(res)) + for k, v := range res { + m[k[prefixLen:]] = v + } + return mapstructure.Decode(m, obj) +} + +// Sub return an sub configer. +func (e *EtcdConfiger) Sub(key string) (config.Configer, error) { + return newEtcdConfiger(e.client, e.prefix+key), nil +} + +// TODO remove this before release v2.0.0 +func (e *EtcdConfiger) OnChange(key string, fn func(value string)) { + + buildOptsFunc := func() []clientv3.OpOption { + return []clientv3.OpOption{} + } + + rch := e.client.Watch(context.Background(), e.prefix+key, buildOptsFunc()...) + go func() { + for { + for resp := range rch { + if err := resp.Err(); err != nil { + logs.Error("listen to key but got error callback", err) + break + } + + for _, e := range resp.Events { + if e.Kv == nil { + continue + } + fn(string(e.Kv.Value)) + } + } + time.Sleep(time.Second) + rch = e.client.Watch(context.Background(), e.prefix+key, buildOptsFunc()...) + } + }() + +} + +type EtcdConfigerProvider struct { +} + +// Parse = ParseData([]byte(key)) +// key must be json +func (provider *EtcdConfigerProvider) Parse(key string) (config.Configer, error) { + return provider.ParseData([]byte(key)) +} + +// ParseData try to parse key as clientv3.Config, using this to build etcdClient +func (provider *EtcdConfigerProvider) ParseData(data []byte) (config.Configer, error) { + cfg := &clientv3.Config{} + err := json.Unmarshal(data, cfg) + if err != nil { + return nil, errors.WithMessage(err, "parse data to etcd config failed, please check your input") + } + + cfg.DialOptions = []grpc.DialOption{ + grpc.WithBlock(), + grpc.WithUnaryInterceptor(grpc_prometheus.UnaryClientInterceptor), + grpc.WithStreamInterceptor(grpc_prometheus.StreamClientInterceptor), + } + client, err := clientv3.New(*cfg) + if err != nil { + return nil, errors.WithMessage(err, "create etcd client failed") + } + + return newEtcdConfiger(client, ""), nil +} + +func get(client *clientv3.Client, key string) (*clientv3.GetResponse, error) { + var ( + resp *clientv3.GetResponse + err error + ) + resp, err = client.Get(context.Background(), key) + + if err != nil { + return nil, errors.WithMessage(err, fmt.Sprintf("read config from etcd with key %s failed", key)) + } + return resp, err +} + +func init() { + config.Register("json", &EtcdConfigerProvider{}) +} diff --git a/core/config/etcd/config_test.go b/core/config/etcd/config_test.go new file mode 100644 index 00000000..6d0bb793 --- /dev/null +++ b/core/config/etcd/config_test.go @@ -0,0 +1,117 @@ +// Copyright 2020 +// +// Licensed 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 etcd + +import ( + "encoding/json" + "os" + "testing" + "time" + + "github.com/coreos/etcd/clientv3" + "github.com/stretchr/testify/assert" +) + +func TestEtcdConfigerProvider_Parse(t *testing.T) { + provider := &EtcdConfigerProvider{} + cfger, err := provider.Parse(readEtcdConfig()) + assert.Nil(t, err) + assert.NotNil(t, cfger) +} + +func TestEtcdConfiger(t *testing.T) { + + provider := &EtcdConfigerProvider{} + cfger, _ := provider.Parse(readEtcdConfig()) + + subCfger, err := cfger.Sub("sub.") + assert.Nil(t, err) + assert.NotNil(t, subCfger) + + subSubCfger, err := subCfger.Sub("sub.") + assert.NotNil(t, subSubCfger) + assert.Nil(t, err) + + str, err := subSubCfger.String("key1") + assert.Nil(t, err) + assert.Equal(t, "sub.sub.key", str) + + // we cannot test it + subSubCfger.OnChange("watch", func(value string) { + // do nothing + }) + + defStr := cfger.DefaultString("not_exit", "default value") + assert.Equal(t, "default value", defStr) + + defInt64 := cfger.DefaultInt64("not_exit", -1) + assert.Equal(t, int64(-1), defInt64) + + defInt := cfger.DefaultInt("not_exit", -2) + assert.Equal(t, -2, defInt) + + defFlt := cfger.DefaultFloat("not_exit", 12.3) + assert.Equal(t, 12.3, defFlt) + + defBl := cfger.DefaultBool("not_exit", true) + assert.True(t, defBl) + + defStrs := cfger.DefaultStrings("not_exit", []string{"hello"}) + assert.Equal(t, []string{"hello"}, defStrs) + + fl, err := cfger.Float("current.float") + assert.Nil(t, err) + assert.Equal(t, 1.23, fl) + + bl, err := cfger.Bool("current.bool") + assert.Nil(t, err) + assert.True(t, bl) + + it, err := cfger.Int("current.int") + assert.Nil(t, err) + assert.Equal(t, 11, it) + + str, err = cfger.String("current.string") + assert.Nil(t, err) + assert.Equal(t, "hello", str) + + tn := &TestEntity{} + err = cfger.Unmarshaler("current.serialize.", tn) + assert.Nil(t, err) + assert.Equal(t, "test", tn.Name) +} + +type TestEntity struct { + Name string `yaml:"name"` + Sub SubEntity `yaml:"sub"` +} + +type SubEntity struct { + SubName string `yaml:"subName"` +} + +func readEtcdConfig() string { + addr := os.Getenv("ETCD_ADDR") + if addr == "" { + addr = "localhost:2379" + } + + obj := clientv3.Config{ + Endpoints: []string{addr}, + DialTimeout: 3 * time.Second, + } + cfg, _ := json.Marshal(obj) + return string(cfg) +} diff --git a/config/fake.go b/core/config/fake.go similarity index 71% rename from config/fake.go rename to core/config/fake.go index d21ab820..3f6f4682 100644 --- a/config/fake.go +++ b/core/config/fake.go @@ -15,12 +15,14 @@ package config import ( + "context" "errors" "strconv" "strings" ) type fakeConfigContainer struct { + BaseConfiger data map[string]string } @@ -33,42 +35,14 @@ func (c *fakeConfigContainer) Set(key, val string) error { return nil } -func (c *fakeConfigContainer) String(key string) string { - return c.getData(key) -} - -func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string { - v := c.String(key) - if v == "" { - return defaultval - } - return v -} - -func (c *fakeConfigContainer) Strings(key string) []string { - v := c.String(key) - if v == "" { - return nil - } - return strings.Split(v, ";") -} - -func (c *fakeConfigContainer) DefaultStrings(key string, defaultval []string) []string { - v := c.Strings(key) - if v == nil { - return defaultval - } - return v -} - func (c *fakeConfigContainer) Int(key string) (int, error) { return strconv.Atoi(c.getData(key)) } -func (c *fakeConfigContainer) DefaultInt(key string, defaultval int) int { +func (c *fakeConfigContainer) DefaultInt(key string, defaultVal int) int { v, err := c.Int(key) if err != nil { - return defaultval + return defaultVal } return v } @@ -77,10 +51,10 @@ func (c *fakeConfigContainer) Int64(key string) (int64, error) { return strconv.ParseInt(c.getData(key), 10, 64) } -func (c *fakeConfigContainer) DefaultInt64(key string, defaultval int64) int64 { +func (c *fakeConfigContainer) DefaultInt64(key string, defaultVal int64) int64 { v, err := c.Int64(key) if err != nil { - return defaultval + return defaultVal } return v } @@ -89,10 +63,10 @@ func (c *fakeConfigContainer) Bool(key string) (bool, error) { return ParseBool(c.getData(key)) } -func (c *fakeConfigContainer) DefaultBool(key string, defaultval bool) bool { +func (c *fakeConfigContainer) DefaultBool(key string, defaultVal bool) bool { v, err := c.Bool(key) if err != nil { - return defaultval + return defaultVal } return v } @@ -101,10 +75,10 @@ func (c *fakeConfigContainer) Float(key string) (float64, error) { return strconv.ParseFloat(c.getData(key), 64) } -func (c *fakeConfigContainer) DefaultFloat(key string, defaultval float64) float64 { +func (c *fakeConfigContainer) DefaultFloat(key string, defaultVal float64) float64 { v, err := c.Float(key) if err != nil { - return defaultval + return defaultVal } return v } @@ -124,11 +98,19 @@ func (c *fakeConfigContainer) SaveConfigFile(filename string) error { return errors.New("not implement in the fakeConfigContainer") } +func (c *fakeConfigContainer) Unmarshaler(prefix string, obj interface{}, opt ...DecodeOption) error { + return errors.New("unsupported operation") +} + var _ Configer = new(fakeConfigContainer) // NewFakeConfig return a fake Configer func NewFakeConfig() Configer { - return &fakeConfigContainer{ + res := &fakeConfigContainer{ data: make(map[string]string), } + res.BaseConfiger = NewBaseConfiger(func(ctx context.Context, key string) (string, error) { + return res.getData(key), nil + }) + return res } diff --git a/core/config/global.go b/core/config/global.go new file mode 100644 index 00000000..5491fe2c --- /dev/null +++ b/core/config/global.go @@ -0,0 +1,103 @@ +// Copyright 2020 +// +// Licensed 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 config + +// We use this to simply application's development +// for most users, they only need to use those methods +var globalInstance Configer + + +// InitGlobalInstance will ini the global instance +// If you want to use specific implementation, don't forget to import it. +// e.g. _ import "github.com/astaxie/beego/core/config/etcd" +// err := InitGlobalInstance("etcd", "someconfig") +func InitGlobalInstance(name string, cfg string) error { + var err error + globalInstance, err = NewConfig(name, cfg) + return err +} + +// support section::key type in given key when using ini type. +func Set(key, val string) error { + return globalInstance.Set(key, val) +} + +// support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. +func String(key string) (string, error) { + return globalInstance.String(key) +} + +// get string slice +func Strings(key string) ([]string, error) { + return globalInstance.Strings(key) +} +func Int(key string) (int, error) { + return globalInstance.Int(key) +} +func Int64(key string) (int64, error) { + return globalInstance.Int64(key) +} +func Bool(key string) (bool, error) { + return globalInstance.Bool(key) +} +func Float(key string) (float64, error) { + return globalInstance.Float(key) +} + +// support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. +func DefaultString(key string, defaultVal string) string { + return globalInstance.DefaultString(key, defaultVal) +} + +// get string slice +func DefaultStrings(key string, defaultVal []string) []string { + return globalInstance.DefaultStrings(key, defaultVal) +} +func DefaultInt(key string, defaultVal int) int { + return globalInstance.DefaultInt(key, defaultVal) +} +func DefaultInt64(key string, defaultVal int64) int64 { + return globalInstance.DefaultInt64(key, defaultVal) +} +func DefaultBool(key string, defaultVal bool) bool { + return globalInstance.DefaultBool(key, defaultVal) +} +func DefaultFloat(key string, defaultVal float64) float64 { + return globalInstance.DefaultFloat(key, defaultVal) +} + +// DIY return the original value +func DIY(key string) (interface{}, error) { + return globalInstance.DIY(key) +} + +func GetSection(section string) (map[string]string, error) { + return globalInstance.GetSection(section) +} + +func Unmarshaler(prefix string, obj interface{}, opt ...DecodeOption) error { + return globalInstance.Unmarshaler(prefix, obj, opt...) +} +func Sub(key string) (Configer, error) { + return globalInstance.Sub(key) +} + +func OnChange(key string, fn func(value string)) { + globalInstance.OnChange(key, fn) +} + +func SaveConfigFile(filename string) error { + return globalInstance.SaveConfigFile(filename) +} diff --git a/core/config/global_test.go b/core/config/global_test.go new file mode 100644 index 00000000..ff01b043 --- /dev/null +++ b/core/config/global_test.go @@ -0,0 +1,104 @@ +// Copyright 2020 +// +// Licensed 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 config + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGlobalInstance(t *testing.T) { + cfgStr := ` +appname = beeapi +httpport = 8080 +mysqlport = 3600 +PI = 3.1415926 +runmode = "dev" +autorender = false +copyrequestbody = true +session= on +cookieon= off +newreg = OFF +needlogin = ON +enableSession = Y +enableCookie = N +developer="tom;jerry" +flag = 1 +path1 = ${GOPATH} +path2 = ${GOPATH||/home/go} +[demo] +key1="asta" +key2 = "xie" +CaseInsensitive = true +peers = one;two;three +password = ${GOPATH} +` + path := os.TempDir() + string(os.PathSeparator) + "test_global_instance.ini" + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + _, err = f.WriteString(cfgStr) + if err != nil { + f.Close() + t.Fatal(err) + } + f.Close() + defer os.Remove(path) + + err = InitGlobalInstance("ini", path) + assert.Nil(t, err) + + val, err := String("appname") + assert.Nil(t, err) + assert.Equal(t, "beeapi", val) + + val = DefaultString("appname__", "404") + assert.Equal(t, "404", val) + + vi, err := Int("httpport") + assert.Nil(t, err) + assert.Equal(t, 8080, vi) + vi = DefaultInt("httpport__", 404) + assert.Equal(t, 404, vi) + + vi64, err := Int64("mysqlport") + assert.Nil(t, err) + assert.Equal(t, int64(3600), vi64) + vi64 = DefaultInt64("mysqlport__", 404) + assert.Equal(t, int64(404), vi64) + + vf, err := Float("PI") + assert.Nil(t, err) + assert.Equal(t, 3.1415926, vf) + vf = DefaultFloat("PI__", 4.04) + assert.Equal(t, 4.04, vf) + + vb, err := Bool("copyrequestbody") + assert.Nil(t, err) + assert.True(t, vb) + + vb = DefaultBool("copyrequestbody__", false) + assert.False(t, vb) + + vss := DefaultStrings("developer__", []string{"tom", ""}) + assert.Equal(t, []string{"tom", ""}, vss) + + vss, err = Strings("developer") + assert.Nil(t, err) + assert.Equal(t, []string{"tom", "jerry"}, vss) +} diff --git a/config/ini.go b/core/config/ini.go similarity index 85% rename from config/ini.go rename to core/config/ini.go index 002e5e05..4d17fb7a 100644 --- a/config/ini.go +++ b/core/config/ini.go @@ -17,6 +17,7 @@ package config import ( "bufio" "bytes" + "context" "errors" "io" "io/ioutil" @@ -26,6 +27,10 @@ import ( "strconv" "strings" "sync" + + "github.com/mitchellh/mapstructure" + + "github.com/astaxie/beego/core/logs" ) var ( @@ -65,6 +70,10 @@ func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, e keyComment: make(map[string]string), RWMutex: sync.RWMutex{}, } + + cfg.BaseConfiger = NewBaseConfiger(func(ctx context.Context, key string) (string, error) { + return cfg.getdata(key), nil + }) cfg.Lock() defer cfg.Unlock() @@ -90,7 +99,7 @@ func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, e break } - //It might be a good idea to throw a error on all unknonw errors? + // It might be a good idea to throw a error on all unknonw errors? if _, ok := err.(*os.PathError); ok { return nil, err } @@ -222,9 +231,10 @@ func (ini *IniConfig) ParseData(data []byte) (Configer, error) { return ini.parseData(dir, data) } -// IniConfigContainer A Config represents the ini configuration. +// IniConfigContainer is a config which represents the ini configuration. // When set and get value, support key as section:name type. type IniConfigContainer struct { + BaseConfiger data map[string]map[string]string // section=> key:val sectionComment map[string]string // section : comment keyComment map[string]string // id: []{comment, key...}; id 1 is for main comment. @@ -237,11 +247,11 @@ func (c *IniConfigContainer) Bool(key string) (bool, error) { } // DefaultBool returns the boolean value for a given key. -// if err != nil return defaultval -func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool { +// if err != nil return defaultVal +func (c *IniConfigContainer) DefaultBool(key string, defaultVal bool) bool { v, err := c.Bool(key) if err != nil { - return defaultval + return defaultVal } return v } @@ -252,11 +262,11 @@ func (c *IniConfigContainer) Int(key string) (int, error) { } // DefaultInt returns the integer value for a given key. -// if err != nil return defaultval -func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int { +// if err != nil return defaultVal +func (c *IniConfigContainer) DefaultInt(key string, defaultVal int) int { v, err := c.Int(key) if err != nil { - return defaultval + return defaultVal } return v } @@ -267,11 +277,11 @@ func (c *IniConfigContainer) Int64(key string) (int64, error) { } // DefaultInt64 returns the int64 value for a given key. -// if err != nil return defaultval -func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 { +// if err != nil return defaultVal +func (c *IniConfigContainer) DefaultInt64(key string, defaultVal int64) int64 { v, err := c.Int64(key) if err != nil { - return defaultval + return defaultVal } return v } @@ -282,46 +292,46 @@ func (c *IniConfigContainer) Float(key string) (float64, error) { } // DefaultFloat returns the float64 value for a given key. -// if err != nil return defaultval -func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 { +// if err != nil return defaultVal +func (c *IniConfigContainer) DefaultFloat(key string, defaultVal float64) float64 { v, err := c.Float(key) if err != nil { - return defaultval + return defaultVal } return v } // String returns the string value for a given key. -func (c *IniConfigContainer) String(key string) string { - return c.getdata(key) +func (c *IniConfigContainer) String(key string) (string, error) { + return c.getdata(key), nil } // DefaultString returns the string value for a given key. -// if err != nil return defaultval -func (c *IniConfigContainer) DefaultString(key string, defaultval string) string { - v := c.String(key) - if v == "" { - return defaultval +// if err != nil return defaultVal +func (c *IniConfigContainer) DefaultString(key string, defaultVal string) string { + v, err := c.String(key) + if v == "" || err != nil { + return defaultVal } return v } // Strings returns the []string value for a given key. // Return nil if config value does not exist or is empty. -func (c *IniConfigContainer) Strings(key string) []string { - v := c.String(key) - if v == "" { - return nil +func (c *IniConfigContainer) Strings(key string) ([]string, error) { + v, err := c.String(key) + if v == "" || err != nil { + return nil, err } - return strings.Split(v, ";") + return strings.Split(v, ";"), nil } // DefaultStrings returns the []string value for a given key. -// if err != nil return defaultval -func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string { - v := c.Strings(key) - if v == nil { - return defaultval +// if err != nil return defaultVal +func (c *IniConfigContainer) DefaultStrings(key string, defaultVal []string) []string { + v, err := c.Strings(key) + if v == nil || err != nil { + return defaultVal } return v } @@ -437,7 +447,7 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) { // Set writes a new value for key. // if write to one section, the key need be "section::key". // if the section is not existed, it panics. -func (c *IniConfigContainer) Set(key, value string) error { +func (c *IniConfigContainer) Set(key, val string) error { c.Lock() defer c.Unlock() if len(key) == 0 { @@ -460,7 +470,7 @@ func (c *IniConfigContainer) Set(key, value string) error { if _, ok := c.data[section]; !ok { c.data[section] = make(map[string]string) } - c.data[section][k] = value + c.data[section][k] = val return nil } @@ -499,6 +509,20 @@ func (c *IniConfigContainer) getdata(key string) string { return "" } +func (c *IniConfigContainer) Unmarshaler(prefix string, obj interface{}, opt ...DecodeOption) error { + if len(prefix) > 0 { + return errors.New("unsupported prefix params") + } + return mapstructure.Decode(c.data, obj) +} + func init() { Register("ini", &IniConfig{}) + + err := InitGlobalInstance("ini", "config/app.conf") + if err != nil { + logs.Warn("init global config instance failed. If you donot use this, just ignore it. ", err) + } } + +// Ignore this error diff --git a/core/config/ini_test.go b/core/config/ini_test.go new file mode 100644 index 00000000..7daa0a6e --- /dev/null +++ b/core/config/ini_test.go @@ -0,0 +1,191 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 config + +import ( + "fmt" + "io/ioutil" + "os" + "strings" + "testing" +) + +func TestIni(t *testing.T) { + + var ( + inicontext = ` +;comment one +#comment two +appname = beeapi +httpport = 8080 +mysqlport = 3600 +PI = 3.1415976 +runmode = "dev" +autorender = false +copyrequestbody = true +session= on +cookieon= off +newreg = OFF +needlogin = ON +enableSession = Y +enableCookie = N +flag = 1 +path1 = ${GOPATH} +path2 = ${GOPATH||/home/go} +[demo] +key1="asta" +key2 = "xie" +CaseInsensitive = true +peers = one;two;three +password = ${GOPATH} +` + + keyValue = map[string]interface{}{ + "appname": "beeapi", + "httpport": 8080, + "mysqlport": int64(3600), + "pi": 3.1415976, + "runmode": "dev", + "autorender": false, + "copyrequestbody": true, + "session": true, + "cookieon": false, + "newreg": false, + "needlogin": true, + "enableSession": true, + "enableCookie": false, + "flag": true, + "path1": os.Getenv("GOPATH"), + "path2": os.Getenv("GOPATH"), + "demo::key1": "asta", + "demo::key2": "xie", + "demo::CaseInsensitive": true, + "demo::peers": []string{"one", "two", "three"}, + "demo::password": os.Getenv("GOPATH"), + "null": "", + "demo2::key1": "", + "error": "", + "emptystrings": []string{}, + } + ) + + f, err := os.Create("testini.conf") + if err != nil { + t.Fatal(err) + } + _, err = f.WriteString(inicontext) + if err != nil { + f.Close() + t.Fatal(err) + } + f.Close() + defer os.Remove("testini.conf") + iniconf, err := NewConfig("ini", "testini.conf") + if err != nil { + t.Fatal(err) + } + for k, v := range keyValue { + var err error + var value interface{} + switch v.(type) { + case int: + value, err = iniconf.Int(k) + case int64: + value, err = iniconf.Int64(k) + case float64: + value, err = iniconf.Float(k) + case bool: + value, err = iniconf.Bool(k) + case []string: + value, err = iniconf.Strings(k) + case string: + value, err = iniconf.String(k) + default: + value, err = iniconf.DIY(k) + } + if err != nil { + t.Fatalf("get key %q value fail,err %s", k, err) + } else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) { + t.Fatalf("get key %q value, want %v got %v .", k, v, value) + } + + } + if err = iniconf.Set("name", "astaxie"); err != nil { + t.Fatal(err) + } + res, _ := iniconf.String("name") + if res != "astaxie" { + t.Fatal("get name error") + } + +} + +func TestIniSave(t *testing.T) { + + const ( + inicontext = ` +app = app +;comment one +#comment two +# comment three +appname = beeapi +httpport = 8080 +# DB Info +# enable db +[dbinfo] +# db type name +# suport mysql,sqlserver +name = mysql +` + + saveResult = ` +app=app +#comment one +#comment two +# comment three +appname=beeapi +httpport=8080 + +# DB Info +# enable db +[dbinfo] +# db type name +# suport mysql,sqlserver +name=mysql +` + ) + cfg, err := NewConfigData("ini", []byte(inicontext)) + if err != nil { + t.Fatal(err) + } + name := "newIniConfig.ini" + if err := cfg.SaveConfigFile(name); err != nil { + t.Fatal(err) + } + defer os.Remove(name) + + if data, err := ioutil.ReadFile(name); err != nil { + t.Fatal(err) + } else { + cfgData := string(data) + datas := strings.Split(saveResult, "\n") + for _, line := range datas { + if !strings.Contains(cfgData, line+"\n") { + t.Fatalf("different after save ini config file. need contains %q", line) + } + } + + } +} diff --git a/config/json.go b/core/config/json/json.go similarity index 71% rename from config/json.go rename to core/config/json/json.go index c4ef25cd..672d2787 100644 --- a/config/json.go +++ b/core/config/json/json.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package config +package json import ( "encoding/json" @@ -23,6 +23,11 @@ import ( "strconv" "strings" "sync" + + "github.com/mitchellh/mapstructure" + + "github.com/astaxie/beego/core/config" + "github.com/astaxie/beego/core/logs" ) // JSONConfig is a json config parser and implements Config interface. @@ -30,7 +35,7 @@ type JSONConfig struct { } // Parse returns a ConfigContainer with parsed json config map. -func (js *JSONConfig) Parse(filename string) (Configer, error) { +func (js *JSONConfig) Parse(filename string) (config.Configer, error) { file, err := os.Open(filename) if err != nil { return nil, err @@ -45,7 +50,7 @@ func (js *JSONConfig) Parse(filename string) (Configer, error) { } // ParseData returns a ConfigContainer with json string -func (js *JSONConfig) ParseData(data []byte) (Configer, error) { +func (js *JSONConfig) ParseData(data []byte) (config.Configer, error) { x := &JSONConfigContainer{ data: make(map[string]interface{}), } @@ -59,34 +64,72 @@ func (js *JSONConfig) ParseData(data []byte) (Configer, error) { x.data["rootArray"] = wrappingArray } - x.data = ExpandValueEnvForMap(x.data) + x.data = config.ExpandValueEnvForMap(x.data) return x, nil } -// JSONConfigContainer A Config represents the json configuration. +// JSONConfigContainer is a config which represents the json configuration. // Only when get value, support key as section:name type. type JSONConfigContainer struct { data map[string]interface{} sync.RWMutex } +func (c *JSONConfigContainer) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error { + sub, err := c.sub(prefix) + if err != nil { + return err + } + return mapstructure.Decode(sub, obj) +} + +func (c *JSONConfigContainer) Sub(key string) (config.Configer, error) { + sub, err := c.sub(key) + if err != nil { + return nil, err + } + return &JSONConfigContainer{ + data: sub, + }, nil +} + +func (c *JSONConfigContainer) sub(key string) (map[string]interface{}, error) { + if key == "" { + return c.data, nil + } + value, ok := c.data[key] + if !ok { + return nil, errors.New(fmt.Sprintf("key is not found: %s", key)) + } + + res, ok := value.(map[string]interface{}) + if !ok { + return nil, errors.New(fmt.Sprintf("the type of value is invalid, key: %s", key)) + } + return res, nil +} + +func (c *JSONConfigContainer) OnChange(key string, fn func(value string)) { + logs.Warn("unsupported operation") +} + // Bool returns the boolean value for a given key. func (c *JSONConfigContainer) Bool(key string) (bool, error) { val := c.getData(key) if val != nil { - return ParseBool(val) + return config.ParseBool(val) } return false, fmt.Errorf("not exist key: %q", key) } // DefaultBool return the bool value if has no error // otherwise return the defaultval -func (c *JSONConfigContainer) DefaultBool(key string, defaultval bool) bool { +func (c *JSONConfigContainer) DefaultBool(key string, defaultVal bool) bool { if v, err := c.Bool(key); err == nil { return v } - return defaultval + return defaultVal } // Int returns the integer value for a given key. @@ -105,11 +148,11 @@ func (c *JSONConfigContainer) Int(key string) (int, error) { // DefaultInt returns the integer value for a given key. // if err != nil return defaultval -func (c *JSONConfigContainer) DefaultInt(key string, defaultval int) int { +func (c *JSONConfigContainer) DefaultInt(key string, defaultVal int) int { if v, err := c.Int(key); err == nil { return v } - return defaultval + return defaultVal } // Int64 returns the int64 value for a given key. @@ -126,11 +169,11 @@ func (c *JSONConfigContainer) Int64(key string) (int64, error) { // DefaultInt64 returns the int64 value for a given key. // if err != nil return defaultval -func (c *JSONConfigContainer) DefaultInt64(key string, defaultval int64) int64 { +func (c *JSONConfigContainer) DefaultInt64(key string, defaultVal int64) int64 { if v, err := c.Int64(key); err == nil { return v } - return defaultval + return defaultVal } // Float returns the float value for a given key. @@ -147,50 +190,50 @@ func (c *JSONConfigContainer) Float(key string) (float64, error) { // DefaultFloat returns the float64 value for a given key. // if err != nil return defaultval -func (c *JSONConfigContainer) DefaultFloat(key string, defaultval float64) float64 { +func (c *JSONConfigContainer) DefaultFloat(key string, defaultVal float64) float64 { if v, err := c.Float(key); err == nil { return v } - return defaultval + return defaultVal } // String returns the string value for a given key. -func (c *JSONConfigContainer) String(key string) string { +func (c *JSONConfigContainer) String(key string) (string, error) { val := c.getData(key) if val != nil { if v, ok := val.(string); ok { - return v + return v, nil } } - return "" + return "", nil } // DefaultString returns the string value for a given key. // if err != nil return defaultval -func (c *JSONConfigContainer) DefaultString(key string, defaultval string) string { +func (c *JSONConfigContainer) DefaultString(key string, defaultVal string) string { // TODO FIXME should not use "" to replace non existence - if v := c.String(key); v != "" { + if v, err := c.String(key); v != "" && err == nil { return v } - return defaultval + return defaultVal } // Strings returns the []string value for a given key. -func (c *JSONConfigContainer) Strings(key string) []string { - stringVal := c.String(key) - if stringVal == "" { - return nil +func (c *JSONConfigContainer) Strings(key string) ([]string, error) { + stringVal, err := c.String(key) + if stringVal == "" || err != nil { + return nil, err } - return strings.Split(c.String(key), ";") + return strings.Split(stringVal, ";"), nil } // DefaultStrings returns the []string value for a given key. // if err != nil return defaultval -func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string { - if v := c.Strings(key); v != nil { +func (c *JSONConfigContainer) DefaultStrings(key string, defaultVal []string) []string { + if v, err := c.Strings(key); v != nil && err == nil { return v } - return defaultval + return defaultVal } // GetSection returns map for the given section @@ -265,5 +308,5 @@ func (c *JSONConfigContainer) getData(key string) interface{} { } func init() { - Register("json", &JSONConfig{}) + config.Register("json", &JSONConfig{}) } diff --git a/core/config/json/json_test.go b/core/config/json/json_test.go new file mode 100644 index 00000000..386cfdf1 --- /dev/null +++ b/core/config/json/json_test.go @@ -0,0 +1,251 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 json + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/core/config" +) + +func TestJsonStartsWithArray(t *testing.T) { + + const jsoncontextwitharray = `[ + { + "url": "user", + "serviceAPI": "http://www.test.com/user" + }, + { + "url": "employee", + "serviceAPI": "http://www.test.com/employee" + } +]` + f, err := os.Create("testjsonWithArray.conf") + if err != nil { + t.Fatal(err) + } + _, err = f.WriteString(jsoncontextwitharray) + if err != nil { + f.Close() + t.Fatal(err) + } + f.Close() + defer os.Remove("testjsonWithArray.conf") + jsonconf, err := config.NewConfig("json", "testjsonWithArray.conf") + if err != nil { + t.Fatal(err) + } + rootArray, err := jsonconf.DIY("rootArray") + if err != nil { + t.Error("array does not exist as element") + } + rootArrayCasted := rootArray.([]interface{}) + if rootArrayCasted == nil { + t.Error("array from root is nil") + } else { + elem := rootArrayCasted[0].(map[string]interface{}) + if elem["url"] != "user" || elem["serviceAPI"] != "http://www.test.com/user" { + t.Error("array[0] values are not valid") + } + + elem2 := rootArrayCasted[1].(map[string]interface{}) + if elem2["url"] != "employee" || elem2["serviceAPI"] != "http://www.test.com/employee" { + t.Error("array[1] values are not valid") + } + } +} + +func TestJson(t *testing.T) { + + var ( + jsoncontext = `{ +"appname": "beeapi", +"testnames": "foo;bar", +"httpport": 8080, +"mysqlport": 3600, +"PI": 3.1415976, +"runmode": "dev", +"autorender": false, +"copyrequestbody": true, +"session": "on", +"cookieon": "off", +"newreg": "OFF", +"needlogin": "ON", +"enableSession": "Y", +"enableCookie": "N", +"flag": 1, +"path1": "${GOPATH}", +"path2": "${GOPATH||/home/go}", +"database": { + "host": "host", + "port": "port", + "database": "database", + "username": "username", + "password": "${GOPATH}", + "conns":{ + "maxconnection":12, + "autoconnect":true, + "connectioninfo":"info", + "root": "${GOPATH}" + } + } +}` + keyValue = map[string]interface{}{ + "appname": "beeapi", + "testnames": []string{"foo", "bar"}, + "httpport": 8080, + "mysqlport": int64(3600), + "PI": 3.1415976, + "runmode": "dev", + "autorender": false, + "copyrequestbody": true, + "session": true, + "cookieon": false, + "newreg": false, + "needlogin": true, + "enableSession": true, + "enableCookie": false, + "flag": true, + "path1": os.Getenv("GOPATH"), + "path2": os.Getenv("GOPATH"), + "database::host": "host", + "database::port": "port", + "database::database": "database", + "database::password": os.Getenv("GOPATH"), + "database::conns::maxconnection": 12, + "database::conns::autoconnect": true, + "database::conns::connectioninfo": "info", + "database::conns::root": os.Getenv("GOPATH"), + "unknown": "", + } + ) + + f, err := os.Create("testjson.conf") + if err != nil { + t.Fatal(err) + } + _, err = f.WriteString(jsoncontext) + if err != nil { + f.Close() + t.Fatal(err) + } + f.Close() + defer os.Remove("testjson.conf") + jsonconf, err := config.NewConfig("json", "testjson.conf") + if err != nil { + t.Fatal(err) + } + + for k, v := range keyValue { + var err error + var value interface{} + switch v.(type) { + case int: + value, err = jsonconf.Int(k) + case int64: + value, err = jsonconf.Int64(k) + case float64: + value, err = jsonconf.Float(k) + case bool: + value, err = jsonconf.Bool(k) + case []string: + value, err = jsonconf.Strings(k) + case string: + value, err = jsonconf.String(k) + default: + value, err = jsonconf.DIY(k) + } + if err != nil { + t.Fatalf("get key %q value fatal,%v err %s", k, v, err) + } else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) { + t.Fatalf("get key %q value, want %v got %v .", k, v, value) + } + + } + if err = jsonconf.Set("name", "astaxie"); err != nil { + t.Fatal(err) + } + + res, _ := jsonconf.String("name") + if res != "astaxie" { + t.Fatal("get name error") + } + + if db, err := jsonconf.DIY("database"); err != nil { + t.Fatal(err) + } else if m, ok := db.(map[string]interface{}); !ok { + t.Log(db) + t.Fatal("db not map[string]interface{}") + } else { + if m["host"].(string) != "host" { + t.Fatal("get host err") + } + } + + if _, err := jsonconf.Int("unknown"); err == nil { + t.Error("unknown keys should return an error when expecting an Int") + } + + if _, err := jsonconf.Int64("unknown"); err == nil { + t.Error("unknown keys should return an error when expecting an Int64") + } + + if _, err := jsonconf.Float("unknown"); err == nil { + t.Error("unknown keys should return an error when expecting a Float") + } + + if _, err := jsonconf.DIY("unknown"); err == nil { + t.Error("unknown keys should return an error when expecting an interface{}") + } + + if val, _ := jsonconf.String("unknown"); val != "" { + t.Error("unknown keys should return an empty string when expecting a String") + } + + if _, err := jsonconf.Bool("unknown"); err == nil { + t.Error("unknown keys should return an error when expecting a Bool") + } + + if !jsonconf.DefaultBool("unknown", true) { + t.Error("unknown keys with default value wrong") + } + + sub, err := jsonconf.Sub("database") + assert.Nil(t, err) + assert.NotNil(t, sub) + + sub, err = sub.Sub("conns") + assert.Nil(t, err) + + maxCon, _ := sub.Int("maxconnection") + assert.Equal(t, 12, maxCon) + + dbCfg := &DatabaseConfig{} + err = sub.Unmarshaler("", dbCfg) + assert.Nil(t, err) + assert.Equal(t, 12, dbCfg.MaxConnection) + assert.True(t, dbCfg.Autoconnect) + assert.Equal(t, "info", dbCfg.Connectioninfo) +} + +type DatabaseConfig struct { + MaxConnection int `json:"maxconnection"` + Autoconnect bool `json:"autoconnect"` + Connectioninfo string `json:"connectioninfo"` +} diff --git a/core/config/toml/toml.go b/core/config/toml/toml.go new file mode 100644 index 00000000..96e1a200 --- /dev/null +++ b/core/config/toml/toml.go @@ -0,0 +1,357 @@ +// Copyright 2020 +// +// Licensed 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 toml + +import ( + "io/ioutil" + "os" + "strings" + + "github.com/pelletier/go-toml" + + "github.com/astaxie/beego/core/config" +) + +const keySeparator = "." + +type Config struct { + tree *toml.Tree +} + +// Parse accepts filename as the parameter +func (c *Config) Parse(filename string) (config.Configer, error) { + ctx, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + return c.ParseData(ctx) +} + +func (c *Config) ParseData(data []byte) (config.Configer, error) { + t, err := toml.LoadBytes(data) + if err != nil { + return nil, err + } + return &configContainer{ + t: t, + }, nil + +} + +// configContainer support key looks like "a.b.c" +type configContainer struct { + t *toml.Tree +} + +// Set put key, val +func (c *configContainer) Set(key, val string) error { + path := strings.Split(key, keySeparator) + sub, err := subTree(c.t, path[0:len(path)-1]) + if err != nil { + return err + } + sub.Set(path[len(path)-1], val) + return nil +} + +// String return the value. +// return error if key not found or value is invalid type +func (c *configContainer) String(key string) (string, error) { + res, err := c.get(key) + + if err != nil { + return "", err + } + + if res == nil { + return "", config.KeyNotFoundError + } + + if str, ok := res.(string); ok { + return str, nil + } else { + return "", config.InvalidValueTypeError + } +} + +// Strings return []string +// return error if key not found or value is invalid type +func (c *configContainer) Strings(key string) ([]string, error) { + val, err := c.get(key) + + if err != nil { + return []string{}, err + } + if val == nil { + return []string{}, config.KeyNotFoundError + } + if arr, ok := val.([]interface{}); ok { + res := make([]string, 0, len(arr)) + for _, ele := range arr { + if str, ok := ele.(string); ok { + res = append(res, str) + } else { + return []string{}, config.InvalidValueTypeError + } + } + return res, nil + } else { + return []string{}, config.InvalidValueTypeError + } +} + +// Int return int value +// return error if key not found or value is invalid type +func (c *configContainer) Int(key string) (int, error) { + val, err := c.Int64(key) + return int(val), err +} + +// Int64 return int64 value +// return error if key not found or value is invalid type +func (c *configContainer) Int64(key string) (int64, error) { + res, err := c.get(key) + if err != nil { + return 0, err + } + if res == nil { + return 0, config.KeyNotFoundError + } + if i, ok := res.(int); ok { + return int64(i), nil + } else if i64, ok := res.(int64); ok { + return i64, nil + } else { + return 0, config.InvalidValueTypeError + } +} + +// bool return bool value +// return error if key not found or value is invalid type +func (c *configContainer) Bool(key string) (bool, error) { + + res, err := c.get(key) + + if err != nil { + return false, err + } + + if res == nil { + return false, config.KeyNotFoundError + } + if b, ok := res.(bool); ok { + return b, nil + } else { + return false, config.InvalidValueTypeError + } +} + +// Float return float value +// return error if key not found or value is invalid type +func (c *configContainer) Float(key string) (float64, error) { + res, err := c.get(key) + if err != nil { + return 0, err + } + + if res == nil { + return 0, config.KeyNotFoundError + } + + if f, ok := res.(float64); ok { + return f, nil + } else { + return 0, config.InvalidValueTypeError + } +} + +// DefaultString return string value +// return default value if key not found or value is invalid type +func (c *configContainer) DefaultString(key string, defaultVal string) string { + res, err := c.get(key) + if err != nil { + return defaultVal + } + if str, ok := res.(string); ok { + return str + } else { + return defaultVal + } +} + +// DefaultStrings return []string +// return default value if key not found or value is invalid type +func (c *configContainer) DefaultStrings(key string, defaultVal []string) []string { + val, err := c.get(key) + if err != nil { + return defaultVal + } + if arr, ok := val.([]interface{}); ok { + res := make([]string, 0, len(arr)) + for _, ele := range arr { + if str, ok := ele.(string); ok { + res = append(res, str) + } else { + return defaultVal + } + } + return res + } else { + return defaultVal + } +} + +// DefaultInt return int value +// return default value if key not found or value is invalid type +func (c *configContainer) DefaultInt(key string, defaultVal int) int { + return int(c.DefaultInt64(key, int64(defaultVal))) +} + +// DefaultInt64 return int64 value +// return default value if key not found or value is invalid type +func (c *configContainer) DefaultInt64(key string, defaultVal int64) int64 { + res, err := c.get(key) + if err != nil { + return defaultVal + } + if i, ok := res.(int); ok { + return int64(i) + } else if i64, ok := res.(int64); ok { + return i64 + } else { + return defaultVal + } +} + +// DefaultBool return bool value +// return default value if key not found or value is invalid type +func (c *configContainer) DefaultBool(key string, defaultVal bool) bool { + res, err := c.get(key) + if err != nil { + return defaultVal + } + if b, ok := res.(bool); ok { + return b + } else { + return defaultVal + } +} + +// DefaultFloat return float value +// return default value if key not found or value is invalid type +func (c *configContainer) DefaultFloat(key string, defaultVal float64) float64 { + res, err := c.get(key) + if err != nil { + return defaultVal + } + if f, ok := res.(float64); ok { + return f + } else { + return defaultVal + } +} + +// DIY returns the original value +func (c *configContainer) DIY(key string) (interface{}, error) { + return c.get(key) +} + +// GetSection return error if the value is not valid toml doc +func (c *configContainer) GetSection(section string) (map[string]string, error) { + val, err := subTree(c.t, strings.Split(section, keySeparator)) + if err != nil { + return map[string]string{}, err + } + m := val.ToMap() + res := make(map[string]string, len(m)) + for k, v := range m { + res[k] = config.ToString(v) + } + return res, nil +} + +func (c *configContainer) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error { + if len(prefix) > 0 { + t, err := subTree(c.t, strings.Split(prefix, keySeparator)) + if err != nil { + return err + } + return t.Unmarshal(obj) + } + return c.t.Unmarshal(obj) +} + +// Sub return sub configer +// return error if key not found or the value is not a sub doc +func (c *configContainer) Sub(key string) (config.Configer, error) { + val, err := subTree(c.t, strings.Split(key, keySeparator)) + if err != nil { + return nil, err + } + return &configContainer{ + t: val, + }, nil +} + +// OnChange do nothing +func (c *configContainer) OnChange(key string, fn func(value string)) { + // do nothing +} + +// SaveConfigFile create or override the file +func (c *configContainer) SaveConfigFile(filename string) error { + // Write configuration file by filename. + f, err := os.Create(filename) + if err != nil { + return err + } + defer f.Close() + + _, err = c.t.WriteTo(f) + return err +} + +func (c *configContainer) get(key string) (interface{}, error) { + if len(key) == 0 { + return nil, config.KeyNotFoundError + } + + segs := strings.Split(key, keySeparator) + t, err := subTree(c.t, segs[0:len(segs)-1]) + + if err != nil { + return nil, err + } + return t.Get(segs[len(segs)-1]), nil +} + +func subTree(t *toml.Tree, path []string) (*toml.Tree, error) { + res := t + for i := 0; i < len(path); i++ { + if subTree, ok := res.Get(path[i]).(*toml.Tree); ok { + res = subTree + } else { + return nil, config.InvalidValueTypeError + } + } + if res == nil { + return nil, config.KeyNotFoundError + } + return res, nil +} + +func init() { + config.Register("toml", &Config{}) +} diff --git a/core/config/toml/toml_test.go b/core/config/toml/toml_test.go new file mode 100644 index 00000000..20726f0d --- /dev/null +++ b/core/config/toml/toml_test.go @@ -0,0 +1,379 @@ +// Copyright 2020 +// +// Licensed 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 toml + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/core/config" +) + +func TestConfig_Parse(t *testing.T) { + // file not found + cfg := &Config{} + _, err := cfg.Parse("invalid_file_name.txt") + assert.NotNil(t, err) +} + +func TestConfig_ParseData(t *testing.T) { + data := ` +name="Tom" +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + assert.Nil(t, err) + assert.NotNil(t, c) +} + +func TestConfigContainer_Bool(t *testing.T) { + data := ` +Man=true +Woman="true" +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + assert.Nil(t, err) + assert.NotNil(t, c) + + val, err := c.Bool("Man") + assert.Nil(t, err) + assert.True(t, val) + + _, err = c.Bool("Woman") + assert.NotNil(t, err) + assert.Equal(t, config.InvalidValueTypeError, err) +} + +func TestConfigContainer_DefaultBool(t *testing.T) { + data := ` +Man=true +Woman="false" +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + assert.Nil(t, err) + assert.NotNil(t, c) + + val := c.DefaultBool("Man11", true) + assert.True(t, val) + + val = c.DefaultBool("Man", false) + assert.True(t, val) + + val = c.DefaultBool("Woman", true) + assert.True(t, val) +} + +func TestConfigContainer_DefaultFloat(t *testing.T) { + data := ` +Price=12.3 +PriceInvalid="12.3" +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + assert.Nil(t, err) + assert.NotNil(t, c) + + val := c.DefaultFloat("Price", 11.2) + assert.Equal(t, 12.3, val) + + val = c.DefaultFloat("Price11", 11.2) + assert.Equal(t, 11.2, val) + + val = c.DefaultFloat("PriceInvalid", 11.2) + assert.Equal(t, 11.2, val) +} + +func TestConfigContainer_DefaultInt(t *testing.T) { + data := ` +Age=12 +AgeInvalid="13" +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + assert.Nil(t, err) + assert.NotNil(t, c) + + val := c.DefaultInt("Age", 11) + assert.Equal(t, 12, val) + + val = c.DefaultInt("Price11", 11) + assert.Equal(t, 11, val) + + val = c.DefaultInt("PriceInvalid", 11) + assert.Equal(t, 11, val) +} + +func TestConfigContainer_DefaultString(t *testing.T) { + data := ` +Name="Tom" +NameInvalid=13 +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + assert.Nil(t, err) + assert.NotNil(t, c) + + val := c.DefaultString("Name", "Jerry") + assert.Equal(t, "Tom", val) + + val = c.DefaultString("Name11", "Jerry") + assert.Equal(t, "Jerry", val) + + val = c.DefaultString("NameInvalid", "Jerry") + assert.Equal(t, "Jerry", val) +} + +func TestConfigContainer_DefaultStrings(t *testing.T) { + data := ` +Name=["Tom", "Jerry"] +NameInvalid="Tom" +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + assert.Nil(t, err) + assert.NotNil(t, c) + + val := c.DefaultStrings("Name", []string{"Jerry"}) + assert.Equal(t, []string{"Tom", "Jerry"}, val) + + val = c.DefaultStrings("Name11", []string{"Jerry"}) + assert.Equal(t, []string{"Jerry"}, val) + + val = c.DefaultStrings("NameInvalid", []string{"Jerry"}) + assert.Equal(t, []string{"Jerry"}, val) +} + +func TestConfigContainer_DIY(t *testing.T) { + data := ` +Name=["Tom", "Jerry"] +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + assert.Nil(t, err) + assert.NotNil(t, c) + + _, err = c.DIY("Name") + assert.Nil(t, err) +} + +func TestConfigContainer_Float(t *testing.T) { + data := ` +Price=12.3 +PriceInvalid="12.3" +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + assert.Nil(t, err) + assert.NotNil(t, c) + + val, err := c.Float("Price") + assert.Nil(t, err) + assert.Equal(t, 12.3, val) + + _, err = c.Float("Price11") + assert.Equal(t, config.KeyNotFoundError, err) + + _, err = c.Float("PriceInvalid") + assert.Equal(t, config.InvalidValueTypeError, err) +} + +func TestConfigContainer_Int(t *testing.T) { + data := ` +Age=12 +AgeInvalid="13" +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + assert.Nil(t, err) + assert.NotNil(t, c) + + val, err := c.Int("Age") + assert.Nil(t, err) + assert.Equal(t, 12, val) + + _, err = c.Int("Age11") + assert.Equal(t, config.KeyNotFoundError, err) + + _, err = c.Int("AgeInvalid") + assert.Equal(t, config.InvalidValueTypeError, err) +} + +func TestConfigContainer_GetSection(t *testing.T) { + data := ` +[servers] + + # You can indent as you please. Tabs or spaces. TOML don't care. + [servers.alpha] + ip = "10.0.0.1" + dc = "eqdc10" + + [servers.beta] + ip = "10.0.0.2" + dc = "eqdc10" +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + assert.Nil(t, err) + assert.NotNil(t, c) + + m, err := c.GetSection("servers") + assert.Nil(t, err) + assert.NotNil(t, m) + assert.Equal(t, 2, len(m)) +} + +func TestConfigContainer_String(t *testing.T) { + data := ` +Name="Tom" +NameInvalid=13 +[Person] +Name="Jerry" +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + assert.Nil(t, err) + assert.NotNil(t, c) + + val, err := c.String("Name") + assert.Nil(t, err) + assert.Equal(t, "Tom", val) + + _, err = c.String("Name11") + assert.Equal(t, config.KeyNotFoundError, err) + + _, err = c.String("NameInvalid") + assert.Equal(t, config.InvalidValueTypeError, err) + + val, err = c.String("Person.Name") + assert.Nil(t, err) + assert.Equal(t, "Jerry", val) +} + +func TestConfigContainer_Strings(t *testing.T) { + data := ` +Name=["Tom", "Jerry"] +NameInvalid="Tom" +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + assert.Nil(t, err) + assert.NotNil(t, c) + + val, err := c.Strings("Name") + assert.Nil(t, err) + assert.Equal(t, []string{"Tom", "Jerry"}, val) + + _, err = c.Strings("Name11") + assert.Equal(t, config.KeyNotFoundError, err) + + _, err = c.Strings("NameInvalid") + assert.Equal(t, config.InvalidValueTypeError, err) +} + +func TestConfigContainer_Set(t *testing.T) { + data := ` +Name=["Tom", "Jerry"] +NameInvalid="Tom" +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + assert.Nil(t, err) + assert.NotNil(t, c) + + err = c.Set("Age", "11") + assert.Nil(t, err) + age, err := c.String("Age") + assert.Nil(t, err) + assert.Equal(t, "11", age) +} + +func TestConfigContainer_SubAndMushall(t *testing.T) { + data := ` +[servers] + + # You can indent as you please. Tabs or spaces. TOML don't care. + [servers.alpha] + ip = "10.0.0.1" + dc = "eqdc10" + + [servers.beta] + ip = "10.0.0.2" + dc = "eqdc10" +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + assert.Nil(t, err) + assert.NotNil(t, c) + + sub, err := c.Sub("servers") + assert.Nil(t, err) + assert.NotNil(t, sub) + + sub, err = sub.Sub("alpha") + assert.Nil(t, err) + assert.NotNil(t, sub) + ip, err := sub.String("ip") + assert.Nil(t, err) + assert.Equal(t, "10.0.0.1", ip) + + svr := &Server{} + err = sub.Unmarshaler("", svr) + assert.Nil(t, err) + assert.Equal(t, "10.0.0.1", svr.Ip) + + svr = &Server{} + err = c.Unmarshaler("servers.alpha", svr) + assert.Nil(t, err) + assert.Equal(t, "10.0.0.1", svr.Ip) +} + +func TestConfigContainer_SaveConfigFile(t *testing.T) { + filename := "test_config.toml" + path := os.TempDir() + string(os.PathSeparator) + filename + data := ` +[servers] + + # You can indent as you please. Tabs or spaces. TOML don't care. + [servers.alpha] + ip = "10.0.0.1" + dc = "eqdc10" + + [servers.beta] + ip = "10.0.0.2" + dc = "eqdc10" +` + cfg := &Config{} + c, err := cfg.ParseData([]byte(data)) + + fmt.Println(path) + + assert.Nil(t, err) + assert.NotNil(t, c) + + sub, err := c.Sub("servers") + assert.Nil(t, err) + + err = sub.SaveConfigFile(path) + assert.Nil(t, err) +} + +type Server struct { + Ip string `toml:"ip"` +} diff --git a/config/xml/xml.go b/core/config/xml/xml.go similarity index 66% rename from config/xml/xml.go rename to core/config/xml/xml.go index 494242d3..70f0c23c 100644 --- a/config/xml/xml.go +++ b/core/config/xml/xml.go @@ -26,7 +26,7 @@ // // cnf, err := config.NewConfig("xml", "config.xml") // -//More docs http://beego.me/docs/module/config.md +// More docs http://beego.me/docs/module/config.md package xml import ( @@ -39,7 +39,11 @@ import ( "strings" "sync" - "github.com/astaxie/beego/config" + "github.com/mitchellh/mapstructure" + + "github.com/astaxie/beego/core/config" + "github.com/astaxie/beego/core/logs" + "github.com/beego/x2j" ) @@ -72,12 +76,55 @@ func (xc *Config) ParseData(data []byte) (config.Configer, error) { return x, nil } -// ConfigContainer A Config represents the xml configuration. +// ConfigContainer is a Config which represents the xml configuration. type ConfigContainer struct { data map[string]interface{} sync.Mutex } +// Unmarshaler is a little be inconvenient since the xml library doesn't know type. +// So when you use +// 1 +// The "1" is a string, not int +func (c *ConfigContainer) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error { + sub, err := c.sub(prefix) + if err != nil { + return err + } + return mapstructure.Decode(sub, obj) +} + +func (c *ConfigContainer) Sub(key string) (config.Configer, error) { + sub, err := c.sub(key) + if err != nil { + return nil, err + } + + return &ConfigContainer{ + data: sub, + }, nil + +} + +func (c *ConfigContainer) sub(key string) (map[string]interface{}, error) { + if key == "" { + return c.data, nil + } + value, ok := c.data[key] + if !ok { + return nil, errors.New(fmt.Sprintf("the key is not found: %s", key)) + } + res, ok := value.(map[string]interface{}) + if !ok { + return nil, errors.New(fmt.Sprintf("the value of this key is not a structure: %s", key)) + } + return res, nil +} + +func (c *ConfigContainer) OnChange(key string, fn func(value string)) { + logs.Warn("Unsupported operation") +} + // Bool returns the boolean value for a given key. func (c *ConfigContainer) Bool(key string) (bool, error) { if v := c.data[key]; v != nil { @@ -87,11 +134,11 @@ func (c *ConfigContainer) Bool(key string) (bool, error) { } // DefaultBool return the bool value if has no error -// otherwise return the defaultval -func (c *ConfigContainer) DefaultBool(key string, defaultval bool) bool { +// otherwise return the defaultVal +func (c *ConfigContainer) DefaultBool(key string, defaultVal bool) bool { v, err := c.Bool(key) if err != nil { - return defaultval + return defaultVal } return v } @@ -102,11 +149,11 @@ func (c *ConfigContainer) Int(key string) (int, error) { } // DefaultInt returns the integer value for a given key. -// if err != nil return defaultval -func (c *ConfigContainer) DefaultInt(key string, defaultval int) int { +// if err != nil return defaultVal +func (c *ConfigContainer) DefaultInt(key string, defaultVal int) int { v, err := c.Int(key) if err != nil { - return defaultval + return defaultVal } return v } @@ -117,11 +164,11 @@ func (c *ConfigContainer) Int64(key string) (int64, error) { } // DefaultInt64 returns the int64 value for a given key. -// if err != nil return defaultval -func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 { +// if err != nil return defaultVal +func (c *ConfigContainer) DefaultInt64(key string, defaultVal int64) int64 { v, err := c.Int64(key) if err != nil { - return defaultval + return defaultVal } return v @@ -133,48 +180,48 @@ func (c *ConfigContainer) Float(key string) (float64, error) { } // DefaultFloat returns the float64 value for a given key. -// if err != nil return defaultval -func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 { +// if err != nil return defaultVal +func (c *ConfigContainer) DefaultFloat(key string, defaultVal float64) float64 { v, err := c.Float(key) if err != nil { - return defaultval + return defaultVal } return v } // String returns the string value for a given key. -func (c *ConfigContainer) String(key string) string { +func (c *ConfigContainer) String(key string) (string, error) { if v, ok := c.data[key].(string); ok { - return v + return v, nil } - return "" + return "", nil } // DefaultString returns the string value for a given key. -// if err != nil return defaultval -func (c *ConfigContainer) DefaultString(key string, defaultval string) string { - v := c.String(key) - if v == "" { - return defaultval +// if err != nil return defaultVal +func (c *ConfigContainer) DefaultString(key string, defaultVal string) string { + v, err := c.String(key) + if v == "" || err != nil { + return defaultVal } return v } // Strings returns the []string value for a given key. -func (c *ConfigContainer) Strings(key string) []string { - v := c.String(key) - if v == "" { - return nil +func (c *ConfigContainer) Strings(key string) ([]string, error) { + v, err := c.String(key) + if v == "" || err != nil { + return nil, err } - return strings.Split(v, ";") + return strings.Split(v, ";"), nil } // DefaultStrings returns the []string value for a given key. -// if err != nil return defaultval -func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string { - v := c.Strings(key) - if v == nil { - return defaultval +// if err != nil return defaultVal +func (c *ConfigContainer) DefaultStrings(key string, defaultVal []string) []string { + v, err := c.Strings(key) + if v == nil || err != nil { + return defaultVal } return v } diff --git a/core/config/xml/xml_test.go b/core/config/xml/xml_test.go new file mode 100644 index 00000000..c6cf970d --- /dev/null +++ b/core/config/xml/xml_test.go @@ -0,0 +1,157 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 xml + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/core/config" +) + +func TestXML(t *testing.T) { + + var ( + // xml parse should incluce in tags + xmlcontext = ` + +beeapi +8080 +3600 +3.1415976 +dev +false +true +${GOPATH} +${GOPATH||/home/go} + +1 +MySection + + +` + keyValue = map[string]interface{}{ + "appname": "beeapi", + "httpport": 8080, + "mysqlport": int64(3600), + "PI": 3.1415976, + "runmode": "dev", + "autorender": false, + "copyrequestbody": true, + "path1": os.Getenv("GOPATH"), + "path2": os.Getenv("GOPATH"), + "error": "", + "emptystrings": []string{}, + } + ) + + f, err := os.Create("testxml.conf") + if err != nil { + t.Fatal(err) + } + _, err = f.WriteString(xmlcontext) + if err != nil { + f.Close() + t.Fatal(err) + } + f.Close() + defer os.Remove("testxml.conf") + + xmlconf, err := config.NewConfig("xml", "testxml.conf") + if err != nil { + t.Fatal(err) + } + + var xmlsection map[string]string + xmlsection, err = xmlconf.GetSection("mysection") + if err != nil { + t.Fatal(err) + } + + if len(xmlsection) == 0 { + t.Error("section should not be empty") + } + + for k, v := range keyValue { + + var ( + value interface{} + err error + ) + + switch v.(type) { + case int: + value, err = xmlconf.Int(k) + case int64: + value, err = xmlconf.Int64(k) + case float64: + value, err = xmlconf.Float(k) + case bool: + value, err = xmlconf.Bool(k) + case []string: + value, err = xmlconf.Strings(k) + case string: + value, err = xmlconf.String(k) + default: + value, err = xmlconf.DIY(k) + } + if err != nil { + t.Errorf("get key %q value fatal,%v err %s", k, v, err) + } else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) { + t.Errorf("get key %q value, want %v got %v .", k, v, value) + } + + } + + if err = xmlconf.Set("name", "astaxie"); err != nil { + t.Fatal(err) + } + + res, _ := xmlconf.String("name") + if res != "astaxie" { + t.Fatal("get name error") + } + + sub, err := xmlconf.Sub("mysection") + assert.Nil(t, err) + assert.NotNil(t, sub) + name, err := sub.String("name") + assert.Nil(t, err) + assert.Equal(t, "MySection", name) + + id, err := sub.Int("id") + assert.Nil(t, err) + assert.Equal(t, 1, id) + + sec := &Section{} + + err = sub.Unmarshaler("", sec) + assert.Nil(t, err) + assert.Equal(t, "MySection", sec.Name) + + sec = &Section{} + + err = xmlconf.Unmarshaler("mysection", sec) + assert.Nil(t, err) + assert.Equal(t, "MySection", sec.Name) + +} + +type Section struct { + Name string `xml:"name"` +} diff --git a/config/yaml/yaml.go b/core/config/yaml/yaml.go similarity index 71% rename from config/yaml/yaml.go rename to core/config/yaml/yaml.go index 5def2da3..71daabee 100644 --- a/config/yaml/yaml.go +++ b/core/config/yaml/yaml.go @@ -26,7 +26,7 @@ // // cnf, err := config.NewConfig("yaml", "config.yaml") // -//More docs http://beego.me/docs/module/config.md +// More docs http://beego.me/docs/module/config.md package yaml import ( @@ -40,8 +40,11 @@ import ( "strings" "sync" - "github.com/astaxie/beego/config" "github.com/beego/goyaml2" + "gopkg.in/yaml.v2" + + "github.com/astaxie/beego/core/config" + "github.com/astaxie/beego/core/logs" ) // Config is a yaml config parser and implements Config interface. @@ -116,12 +119,63 @@ func parseYML(buf []byte) (cnf map[string]interface{}, err error) { return } -// ConfigContainer A Config represents the yaml configuration. +// ConfigContainer is a config which represents the yaml configuration. type ConfigContainer struct { data map[string]interface{} sync.RWMutex } +// Unmarshaler is similar to Sub +func (c *ConfigContainer) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error { + sub, err := c.sub(prefix) + if err != nil { + return err + } + + bytes, err := yaml.Marshal(sub) + if err != nil { + return err + } + return yaml.Unmarshal(bytes, obj) +} + +func (c *ConfigContainer) Sub(key string) (config.Configer, error) { + sub, err := c.sub(key) + if err != nil { + return nil, err + } + return &ConfigContainer{ + data: sub, + }, nil +} + +func (c *ConfigContainer) sub(key string) (map[string]interface{}, error) { + tmpData := c.data + keys := strings.Split(key, ".") + for idx, k := range keys { + if v, ok := tmpData[k]; ok { + switch v.(type) { + case map[string]interface{}: + { + tmpData = v.(map[string]interface{}) + if idx == len(keys)-1 { + return tmpData, nil + } + } + default: + return nil, errors.New(fmt.Sprintf("the key is invalid: %s", key)) + } + } + } + + return tmpData, nil +} + +func (c *ConfigContainer) OnChange(key string, fn func(value string)) { + // do nothing + logs.Warn("Unsupported operation: OnChange") +} + // Bool returns the boolean value for a given key. func (c *ConfigContainer) Bool(key string) (bool, error) { v, err := c.getData(key) @@ -132,11 +186,11 @@ func (c *ConfigContainer) Bool(key string) (bool, error) { } // DefaultBool return the bool value if has no error -// otherwise return the defaultval -func (c *ConfigContainer) DefaultBool(key string, defaultval bool) bool { +// otherwise return the defaultVal +func (c *ConfigContainer) DefaultBool(key string, defaultVal bool) bool { v, err := c.Bool(key) if err != nil { - return defaultval + return defaultVal } return v } @@ -154,11 +208,11 @@ func (c *ConfigContainer) Int(key string) (int, error) { } // DefaultInt returns the integer value for a given key. -// if err != nil return defaultval -func (c *ConfigContainer) DefaultInt(key string, defaultval int) int { +// if err != nil return defaultVal +func (c *ConfigContainer) DefaultInt(key string, defaultVal int) int { v, err := c.Int(key) if err != nil { - return defaultval + return defaultVal } return v } @@ -174,11 +228,11 @@ func (c *ConfigContainer) Int64(key string) (int64, error) { } // DefaultInt64 returns the int64 value for a given key. -// if err != nil return defaultval -func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 { +// if err != nil return defaultVal +func (c *ConfigContainer) DefaultInt64(key string, defaultVal int64) int64 { v, err := c.Int64(key) if err != nil { - return defaultval + return defaultVal } return v } @@ -198,50 +252,50 @@ func (c *ConfigContainer) Float(key string) (float64, error) { } // DefaultFloat returns the float64 value for a given key. -// if err != nil return defaultval -func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 { +// if err != nil return defaultVal +func (c *ConfigContainer) DefaultFloat(key string, defaultVal float64) float64 { v, err := c.Float(key) if err != nil { - return defaultval + return defaultVal } return v } // String returns the string value for a given key. -func (c *ConfigContainer) String(key string) string { +func (c *ConfigContainer) String(key string) (string, error) { if v, err := c.getData(key); err == nil { if vv, ok := v.(string); ok { - return vv + return vv, nil } } - return "" + return "", nil } // DefaultString returns the string value for a given key. -// if err != nil return defaultval -func (c *ConfigContainer) DefaultString(key string, defaultval string) string { - v := c.String(key) - if v == "" { - return defaultval +// if err != nil return defaultVal +func (c *ConfigContainer) DefaultString(key string, defaultVal string) string { + v, err := c.String(key) + if v == "" || err != nil { + return defaultVal } return v } // Strings returns the []string value for a given key. -func (c *ConfigContainer) Strings(key string) []string { - v := c.String(key) - if v == "" { - return nil +func (c *ConfigContainer) Strings(key string) ([]string, error) { + v, err := c.String(key) + if v == "" || err != nil { + return nil, err } - return strings.Split(v, ";") + return strings.Split(v, ";"), nil } // DefaultStrings returns the []string value for a given key. -// if err != nil return defaultval -func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string { - v := c.Strings(key) - if v == nil { - return defaultval +// if err != nil return defaultVal +func (c *ConfigContainer) DefaultStrings(key string, defaultVal []string) []string { + v, err := c.Strings(key) + if v == nil || err != nil { + return defaultVal } return v } @@ -288,7 +342,7 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) { c.RLock() defer c.RUnlock() - keys := strings.Split(key, ".") + keys := strings.Split(c.key(key), ".") tmpData := c.data for idx, k := range keys { if v, ok := tmpData[k]; ok { @@ -296,7 +350,7 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) { case map[string]interface{}: { tmpData = v.(map[string]interface{}) - if idx == len(keys) - 1 { + if idx == len(keys)-1 { return tmpData, nil } } @@ -311,6 +365,10 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) { return nil, fmt.Errorf("not exist key %q", key) } +func (c *ConfigContainer) key(key string) string { + return key +} + func init() { config.Register("yaml", &Config{}) } diff --git a/core/config/yaml/yaml_test.go b/core/config/yaml/yaml_test.go new file mode 100644 index 00000000..d18317db --- /dev/null +++ b/core/config/yaml/yaml_test.go @@ -0,0 +1,151 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 yaml + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/core/config" +) + +func TestYaml(t *testing.T) { + + var ( + yamlcontext = ` +"appname": beeapi +"httpport": 8080 +"mysqlport": 3600 +"PI": 3.1415976 +"runmode": dev +"autorender": false +"copyrequestbody": true +"PATH": GOPATH +"path1": ${GOPATH} +"path2": ${GOPATH||/home/go} +"empty": "" +"user": + "name": "tom" + "age": 13 +` + + keyValue = map[string]interface{}{ + "appname": "beeapi", + "httpport": 8080, + "mysqlport": int64(3600), + "PI": 3.1415976, + "runmode": "dev", + "autorender": false, + "copyrequestbody": true, + "PATH": "GOPATH", + "path1": os.Getenv("GOPATH"), + "path2": os.Getenv("GOPATH"), + "error": "", + "emptystrings": []string{}, + } + ) + f, err := os.Create("testyaml.conf") + if err != nil { + t.Fatal(err) + } + _, err = f.WriteString(yamlcontext) + if err != nil { + f.Close() + t.Fatal(err) + } + f.Close() + defer os.Remove("testyaml.conf") + yamlconf, err := config.NewConfig("yaml", "testyaml.conf") + if err != nil { + t.Fatal(err) + } + + res, _ := yamlconf.String("appname") + if res != "beeapi" { + t.Fatal("appname not equal to beeapi") + } + + for k, v := range keyValue { + + var ( + value interface{} + err error + ) + + switch v.(type) { + case int: + value, err = yamlconf.Int(k) + case int64: + value, err = yamlconf.Int64(k) + case float64: + value, err = yamlconf.Float(k) + case bool: + value, err = yamlconf.Bool(k) + case []string: + value, err = yamlconf.Strings(k) + case string: + value, err = yamlconf.String(k) + default: + value, err = yamlconf.DIY(k) + } + if err != nil { + t.Errorf("get key %q value fatal,%v err %s", k, v, err) + } else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) { + t.Errorf("get key %q value, want %v got %v .", k, v, value) + } + + } + + if err = yamlconf.Set("name", "astaxie"); err != nil { + t.Fatal(err) + } + res, _ = yamlconf.String("name") + if res != "astaxie" { + t.Fatal("get name error") + } + + sub, err := yamlconf.Sub("user") + assert.Nil(t, err) + assert.NotNil(t, sub) + name, err := sub.String("name") + assert.Nil(t, err) + assert.Equal(t, "tom", name) + + age, err := sub.Int("age") + assert.Nil(t, err) + assert.Equal(t, 13, age) + + user := &User{} + + err = sub.Unmarshaler("", user) + assert.Nil(t, err) + assert.Equal(t, "tom", user.Name) + assert.Equal(t, 13, user.Age) + + user = &User{} + + err = yamlconf.Unmarshaler("user", user) + assert.Nil(t, err) + assert.Equal(t, "tom", user.Name) + assert.Equal(t, 13, user.Age) +} + +type User struct { + Name string `yaml:"name"` + Age int `yaml:"age"` +} diff --git a/core/governor/command.go b/core/governor/command.go new file mode 100644 index 00000000..75df5815 --- /dev/null +++ b/core/governor/command.go @@ -0,0 +1,87 @@ +// Copyright 2020 +// +// Licensed 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 governor + +import ( + "github.com/pkg/errors" +) + +// Command is an experimental interface +// We try to use this to decouple modules +// All other modules depends on this, and they register the command they support +// We may change the API in the future, so be careful about this. +type Command interface { + Execute(params ...interface{}) *Result +} + +var CommandNotFound = errors.New("Command not found") + +type Result struct { + // Status is the same as http.Status + Status int + Error error + Content interface{} +} + +func (r *Result) IsSuccess() bool { + return r.Status >= 200 && r.Status < 300 +} + +// CommandRegistry stores all commands +// name => command +type moduleCommands map[string]Command + +// Get returns command with the name +func (m moduleCommands) Get(name string) Command { + c, ok := m[name] + if ok { + return c + } + return &doNothingCommand{} +} + +// module name => moduleCommand +type commandRegistry map[string]moduleCommands + +// Get returns module's commands +func (c commandRegistry) Get(moduleName string) moduleCommands { + if mcs, ok := c[moduleName]; ok { + return mcs + } + res := make(moduleCommands) + c[moduleName] = res + return res +} + +var cmdRegistry = make(commandRegistry) + +// RegisterCommand is not thread-safe +// do not use it in concurrent case +func RegisterCommand(module string, commandName string, command Command) { + cmdRegistry.Get(module)[commandName] = command +} + +func GetCommand(module string, cmdName string) Command { + return cmdRegistry.Get(module).Get(cmdName) +} + +type doNothingCommand struct{} + +func (d *doNothingCommand) Execute(params ...interface{}) *Result { + return &Result{ + Status: 404, + Error: CommandNotFound, + } +} diff --git a/toolbox/healthcheck.go b/core/governor/healthcheck.go similarity index 96% rename from toolbox/healthcheck.go rename to core/governor/healthcheck.go index e3544b3a..a91f09fa 100644 --- a/toolbox/healthcheck.go +++ b/core/governor/healthcheck.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package toolbox healthcheck +// Package governor healthcheck // // type DatabaseCheck struct { // } @@ -28,7 +28,7 @@ // AddHealthCheck("database",&DatabaseCheck{}) // // more docs: http://beego.me/docs/module/toolbox.md -package toolbox +package governor // AdminCheckList holds health checker map var AdminCheckList map[string]HealthChecker diff --git a/toolbox/profile.go b/core/governor/profile.go similarity index 82% rename from toolbox/profile.go rename to core/governor/profile.go index 06e40ede..17f1f375 100644 --- a/toolbox/profile.go +++ b/core/governor/profile.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package toolbox +package governor import ( "fmt" @@ -25,6 +25,8 @@ import ( "runtime/pprof" "strconv" "time" + + "github.com/astaxie/beego/core/utils" ) var startTime = time.Now() @@ -112,15 +114,15 @@ func printGC(memStats *runtime.MemStats, gcstats *debug.GCStats, w io.Writer) { fmt.Fprintf(w, "NumGC:%d Pause:%s Pause(Avg):%s Overhead:%3.2f%% Alloc:%s Sys:%s Alloc(Rate):%s/s Histogram:%s %s %s \n", gcstats.NumGC, - toS(lastPause), - toS(avg(gcstats.Pause)), + utils.ToShortTimeFormat(lastPause), + utils.ToShortTimeFormat(avg(gcstats.Pause)), overhead, toH(memStats.Alloc), toH(memStats.Sys), toH(uint64(allocatedRate)), - toS(gcstats.PauseQuantiles[94]), - toS(gcstats.PauseQuantiles[98]), - toS(gcstats.PauseQuantiles[99])) + utils.ToShortTimeFormat(gcstats.PauseQuantiles[94]), + utils.ToShortTimeFormat(gcstats.PauseQuantiles[98]), + utils.ToShortTimeFormat(gcstats.PauseQuantiles[99])) } else { // while GC has disabled elapsed := time.Now().Sub(startTime) @@ -154,31 +156,3 @@ func toH(bytes uint64) string { return fmt.Sprintf("%.2fG", float64(bytes)/1024/1024/1024) } } - -// short string format -func toS(d time.Duration) string { - - u := uint64(d) - if u < uint64(time.Second) { - switch { - case u == 0: - return "0" - case u < uint64(time.Microsecond): - return fmt.Sprintf("%.2fns", float64(u)) - case u < uint64(time.Millisecond): - return fmt.Sprintf("%.2fus", float64(u)/1000) - default: - return fmt.Sprintf("%.2fms", float64(u)/1000/1000) - } - } else { - switch { - case u < uint64(time.Minute): - return fmt.Sprintf("%.2fs", float64(u)/1000/1000/1000) - case u < uint64(time.Hour): - return fmt.Sprintf("%.2fm", float64(u)/1000/1000/1000/60) - default: - return fmt.Sprintf("%.2fh", float64(u)/1000/1000/1000/60/60) - } - } - -} diff --git a/core/governor/profile_test.go b/core/governor/profile_test.go new file mode 100644 index 00000000..530b0637 --- /dev/null +++ b/core/governor/profile_test.go @@ -0,0 +1,28 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 governor + +import ( + "os" + "testing" +) + +func TestProcessInput(t *testing.T) { + ProcessInput("lookup goroutine", os.Stdout) + ProcessInput("lookup heap", os.Stdout) + ProcessInput("lookup threadcreate", os.Stdout) + ProcessInput("lookup block", os.Stdout) + ProcessInput("gc summary", os.Stdout) +} diff --git a/logs/README.md b/core/logs/README.md similarity index 100% rename from logs/README.md rename to core/logs/README.md diff --git a/logs/accesslog.go b/core/logs/access_log.go similarity index 89% rename from logs/accesslog.go rename to core/logs/access_log.go index 3ff9e20f..10455fe9 100644 --- a/logs/accesslog.go +++ b/core/logs/access_log.go @@ -16,9 +16,9 @@ package logs import ( "bytes" - "strings" "encoding/json" "fmt" + "strings" "time" ) @@ -28,7 +28,7 @@ const ( jsonFormat = "JSON_FORMAT" ) -// AccessLogRecord struct for holding access log data. +// AccessLogRecord is astruct for holding access log data. type AccessLogRecord struct { RemoteAddr string `json:"remote_addr"` RequestTime time.Time `json:"request_time"` @@ -63,7 +63,17 @@ func disableEscapeHTML(i interface{}) { // AccessLog - Format and print access log. func AccessLog(r *AccessLogRecord, format string) { - var msg string + msg := r.format(format) + lm := &LogMsg{ + Msg: strings.TrimSpace(msg), + When: time.Now(), + Level: levelLoggerImpl, + } + beeLogger.writeMsg(lm) +} + +func (r *AccessLogRecord) format(format string) string { + msg := "" switch format { case apacheFormat: timeFormatted := r.RequestTime.Format("02/Jan/2006 03:04:05") @@ -79,5 +89,5 @@ func AccessLog(r *AccessLogRecord, format string) { msg = string(jsonData) } } - beeLogger.writeMsg(levelLoggerImpl, strings.TrimSpace(msg)) + return msg } diff --git a/core/logs/access_log_test.go b/core/logs/access_log_test.go new file mode 100644 index 00000000..f78a00a0 --- /dev/null +++ b/core/logs/access_log_test.go @@ -0,0 +1,38 @@ +// Copyright 2020 +// +// Licensed 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 logs + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestAccessLog_format(t *testing.T) { + alc := &AccessLogRecord{ + RequestTime: time.Date(2020, 9, 19, 21, 21, 21, 11, time.UTC), + } + + res := alc.format(apacheFormat) + println(res) + assert.Equal(t, " - - [19/Sep/2020 09:21:21] \" 0 0\" 0.000000 ", res) + + res = alc.format(jsonFormat) + assert.Equal(t, + "{\"remote_addr\":\"\",\"request_time\":\"2020-09-19T21:21:21.000000011Z\",\"request_method\":\"\",\"request\":\"\",\"server_protocol\":\"\",\"host\":\"\",\"status\":0,\"body_bytes_sent\":0,\"elapsed_time\":0,\"http_referrer\":\"\",\"http_user_agent\":\"\",\"remote_user\":\"\"}\n", res) + + AccessLog(alc, jsonFormat) +} diff --git a/logs/alils/alils.go b/core/logs/alils/alils.go similarity index 69% rename from logs/alils/alils.go rename to core/logs/alils/alils.go index 867ff4cb..484d31e4 100644 --- a/logs/alils/alils.go +++ b/core/logs/alils/alils.go @@ -2,18 +2,20 @@ package alils import ( "encoding/json" + "fmt" "strings" "sync" - "time" - "github.com/astaxie/beego/logs" "github.com/gogo/protobuf/proto" + "github.com/pkg/errors" + + "github.com/astaxie/beego/core/logs" ) const ( - // CacheSize set the flush size + // CacheSize sets the flush size CacheSize int = 64 - // Delimiter define the topic delimiter + // Delimiter defines the topic delimiter Delimiter string = "##" ) @@ -28,10 +30,11 @@ type Config struct { Source string `json:"source"` Level int `json:"level"` FlushWhen int `json:"flush_when"` + Formatter string `json:"formatter"` } // aliLSWriter implements LoggerInterface. -// it writes messages in keep-live tcp connection. +// Writes messages in keep-live tcp connection. type aliLSWriter struct { store *LogStore group []*LogGroup @@ -39,19 +42,23 @@ type aliLSWriter struct { groupMap map[string]*LogGroup lock *sync.Mutex Config + formatter logs.LogFormatter } -// NewAliLS create a new Logger +// NewAliLS creates a new Logger func NewAliLS() logs.Logger { alils := new(aliLSWriter) alils.Level = logs.LevelTrace + alils.formatter = alils return alils } -// Init parse config and init struct -func (c *aliLSWriter) Init(jsonConfig string) (err error) { - - json.Unmarshal([]byte(jsonConfig), c) +// Init parses config and initializes struct +func (c *aliLSWriter) Init(config string) error { + err := json.Unmarshal([]byte(config), c) + if err != nil { + return err + } if c.FlushWhen > CacheSize { c.FlushWhen = CacheSize @@ -64,11 +71,13 @@ func (c *aliLSWriter) Init(jsonConfig string) (err error) { AccessKeySecret: c.KeySecret, } - c.store, err = prj.GetLogStore(c.LogStore) + store, err := prj.GetLogStore(c.LogStore) if err != nil { return err } + c.store = store + // Create default Log Group c.group = append(c.group, &LogGroup{ Topic: proto.String(""), @@ -98,14 +107,29 @@ func (c *aliLSWriter) Init(jsonConfig string) (err error) { c.lock = &sync.Mutex{} + if len(c.Formatter) > 0 { + fmtr, ok := logs.GetFormatter(c.Formatter) + if !ok { + return errors.New(fmt.Sprintf("the formatter with name: %s not found", c.Formatter)) + } + c.formatter = fmtr + } + return nil } -// WriteMsg write message in connection. -// if connection is down, try to re-connect. -func (c *aliLSWriter) WriteMsg(when time.Time, msg string, level int) (err error) { +func (c *aliLSWriter) Format(lm *logs.LogMsg) string { + return lm.OldStyleFormat() +} - if level > c.Level { +func (c *aliLSWriter) SetFormatter(f logs.LogFormatter) { + c.formatter = f +} + +// WriteMsg writes a message in connection. +// If connection is down, try to re-connect. +func (c *aliLSWriter) WriteMsg(lm *logs.LogMsg) error { + if lm.Level > c.Level { return nil } @@ -115,31 +139,30 @@ func (c *aliLSWriter) WriteMsg(when time.Time, msg string, level int) (err error if c.withMap { // Topic,LogGroup - strs := strings.SplitN(msg, Delimiter, 2) + strs := strings.SplitN(lm.Msg, Delimiter, 2) if len(strs) == 2 { pos := strings.LastIndex(strs[0], " ") topic = strs[0][pos+1 : len(strs[0])] - content = strs[0][0:pos] + strs[1] lg = c.groupMap[topic] } // send to empty Topic if lg == nil { - content = msg lg = c.group[0] } } else { - content = msg lg = c.group[0] } + content = c.formatter.Format(lm) + c1 := &LogContent{ Key: proto.String("msg"), Value: proto.String(content), } l := &Log{ - Time: proto.Uint32(uint32(when.Unix())), + Time: proto.Uint32(uint32(lm.When.Unix())), Contents: []*LogContent{ c1, }, @@ -152,7 +175,6 @@ func (c *aliLSWriter) WriteMsg(when time.Time, msg string, level int) (err error if len(lg.Logs) >= c.FlushWhen { c.flush(lg) } - return nil } diff --git a/logs/alils/config.go b/core/logs/alils/config.go similarity index 61% rename from logs/alils/config.go rename to core/logs/alils/config.go index e8c24448..d0b67c24 100755 --- a/logs/alils/config.go +++ b/core/logs/alils/config.go @@ -4,10 +4,10 @@ const ( version = "0.5.0" // SDK version signatureMethod = "hmac-sha1" // Signature method - // OffsetNewest stands for the log head offset, i.e. the offset that will be + // OffsetNewest is the log head offset, i.e. the offset that will be // assigned to the next message that will be produced to the shard. OffsetNewest = "end" - // OffsetOldest stands for the oldest offset available on the logstore for a + // OffsetOldest is the the oldest offset available on the logstore for a // shard. OffsetOldest = "begin" ) diff --git a/logs/alils/log.pb.go b/core/logs/alils/log.pb.go similarity index 95% rename from logs/alils/log.pb.go rename to core/logs/alils/log.pb.go index 601b0d78..b18fb9b7 100755 --- a/logs/alils/log.pb.go +++ b/core/logs/alils/log.pb.go @@ -31,13 +31,13 @@ type Log struct { // Reset the Log func (m *Log) Reset() { *m = Log{} } -// String return the Compact Log +// String returns the Compact Log func (m *Log) String() string { return proto.CompactTextString(m) } // ProtoMessage not implemented func (*Log) ProtoMessage() {} -// GetTime return the Log's Time +// GetTime returns the Log's Time func (m *Log) GetTime() uint32 { if m != nil && m.Time != nil { return *m.Time @@ -45,7 +45,7 @@ func (m *Log) GetTime() uint32 { return 0 } -// GetContents return the Log's Contents +// GetContents returns the Log's Contents func (m *Log) GetContents() []*LogContent { if m != nil { return m.Contents @@ -53,7 +53,7 @@ func (m *Log) GetContents() []*LogContent { return nil } -// LogContent define the Log content struct +// LogContent defines the Log content struct type LogContent struct { Key *string `protobuf:"bytes,1,req,name=Key" json:"Key,omitempty"` Value *string `protobuf:"bytes,2,req,name=Value" json:"Value,omitempty"` @@ -63,13 +63,13 @@ type LogContent struct { // Reset LogContent func (m *LogContent) Reset() { *m = LogContent{} } -// String return the compact text +// String returns the compact text func (m *LogContent) String() string { return proto.CompactTextString(m) } // ProtoMessage not implemented func (*LogContent) ProtoMessage() {} -// GetKey return the Key +// GetKey returns the key func (m *LogContent) GetKey() string { if m != nil && m.Key != nil { return *m.Key @@ -77,7 +77,7 @@ func (m *LogContent) GetKey() string { return "" } -// GetValue return the Value +// GetValue returns the value func (m *LogContent) GetValue() string { if m != nil && m.Value != nil { return *m.Value @@ -85,7 +85,7 @@ func (m *LogContent) GetValue() string { return "" } -// LogGroup define the logs struct +// LogGroup defines the logs struct type LogGroup struct { Logs []*Log `protobuf:"bytes,1,rep,name=Logs" json:"Logs,omitempty"` Reserved *string `protobuf:"bytes,2,opt,name=Reserved" json:"Reserved,omitempty"` @@ -97,13 +97,13 @@ type LogGroup struct { // Reset LogGroup func (m *LogGroup) Reset() { *m = LogGroup{} } -// String return the compact text +// String returns the compact text func (m *LogGroup) String() string { return proto.CompactTextString(m) } // ProtoMessage not implemented func (*LogGroup) ProtoMessage() {} -// GetLogs return the loggroup logs +// GetLogs returns the loggroup logs func (m *LogGroup) GetLogs() []*Log { if m != nil { return m.Logs @@ -111,7 +111,8 @@ func (m *LogGroup) GetLogs() []*Log { return nil } -// GetReserved return Reserved +// GetReserved returns Reserved. An empty string is returned +// if an error occurs func (m *LogGroup) GetReserved() string { if m != nil && m.Reserved != nil { return *m.Reserved @@ -119,7 +120,8 @@ func (m *LogGroup) GetReserved() string { return "" } -// GetTopic return Topic +// GetTopic returns Topic. An empty string is returned +// if an error occurs func (m *LogGroup) GetTopic() string { if m != nil && m.Topic != nil { return *m.Topic @@ -127,7 +129,8 @@ func (m *LogGroup) GetTopic() string { return "" } -// GetSource return Source +// GetSource returns source. An empty string is returned +// if an error occurs func (m *LogGroup) GetSource() string { if m != nil && m.Source != nil { return *m.Source @@ -135,7 +138,7 @@ func (m *LogGroup) GetSource() string { return "" } -// LogGroupList define the LogGroups +// LogGroupList defines the LogGroups type LogGroupList struct { LogGroups []*LogGroup `protobuf:"bytes,1,rep,name=logGroups" json:"logGroups,omitempty"` XXXUnrecognized []byte `json:"-"` @@ -144,13 +147,13 @@ type LogGroupList struct { // Reset LogGroupList func (m *LogGroupList) Reset() { *m = LogGroupList{} } -// String return compact text +// String returns compact text func (m *LogGroupList) String() string { return proto.CompactTextString(m) } // ProtoMessage not implemented func (*LogGroupList) ProtoMessage() {} -// GetLogGroups return the LogGroups +// GetLogGroups returns the LogGroups func (m *LogGroupList) GetLogGroups() []*LogGroup { if m != nil { return m.LogGroups @@ -158,7 +161,7 @@ func (m *LogGroupList) GetLogGroups() []*LogGroup { return nil } -// Marshal the logs to byte slice +// Marshal marshals the logs to byte slice func (m *Log) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -353,7 +356,7 @@ func encodeVarintLog(data []byte, offset int, v uint64) int { return offset + 1 } -// Size return the log's size +// Size returns the log's size func (m *Log) Size() (n int) { var l int _ = l @@ -372,7 +375,7 @@ func (m *Log) Size() (n int) { return n } -// Size return LogContent size based on Key and Value +// Size returns LogContent size based on Key and Value func (m *LogContent) Size() (n int) { var l int _ = l @@ -390,7 +393,7 @@ func (m *LogContent) Size() (n int) { return n } -// Size return LogGroup size based on Logs +// Size returns LogGroup size based on Logs func (m *LogGroup) Size() (n int) { var l int _ = l @@ -418,7 +421,7 @@ func (m *LogGroup) Size() (n int) { return n } -// Size return LogGroupList size +// Size returns LogGroupList size func (m *LogGroupList) Size() (n int) { var l int _ = l @@ -448,7 +451,7 @@ func sozLog(x uint64) (n int) { return sovLog((x << 1) ^ (x >> 63)) } -// Unmarshal data to log +// Unmarshal unmarshals data to log func (m *Log) Unmarshal(data []byte) error { var hasFields [1]uint64 l := len(data) @@ -557,7 +560,7 @@ func (m *Log) Unmarshal(data []byte) error { return nil } -// Unmarshal data to LogContent +// Unmarshal unmarshals data to LogContent func (m *LogContent) Unmarshal(data []byte) error { var hasFields [1]uint64 l := len(data) @@ -679,7 +682,7 @@ func (m *LogContent) Unmarshal(data []byte) error { return nil } -// Unmarshal data to LogGroup +// Unmarshal unmarshals data to LogGroup func (m *LogGroup) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -853,7 +856,7 @@ func (m *LogGroup) Unmarshal(data []byte) error { return nil } -// Unmarshal data to LogGroupList +// Unmarshal unmarshals data to LogGroupList func (m *LogGroupList) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 diff --git a/logs/alils/log_config.go b/core/logs/alils/log_config.go similarity index 91% rename from logs/alils/log_config.go rename to core/logs/alils/log_config.go index e8564efb..7daeb864 100755 --- a/logs/alils/log_config.go +++ b/core/logs/alils/log_config.go @@ -1,6 +1,6 @@ package alils -// InputDetail define log detail +// InputDetail defines log detail type InputDetail struct { LogType string `json:"logType"` LogPath string `json:"logPath"` @@ -15,13 +15,13 @@ type InputDetail struct { TopicFormat string `json:"topicFormat"` } -// OutputDetail define the output detail +// OutputDetail defines the output detail type OutputDetail struct { Endpoint string `json:"endpoint"` LogStoreName string `json:"logstoreName"` } -// LogConfig define Log Config +// LogConfig defines Log Config type LogConfig struct { Name string `json:"configName"` InputType string `json:"inputType"` diff --git a/logs/alils/log_project.go b/core/logs/alils/log_project.go similarity index 99% rename from logs/alils/log_project.go rename to core/logs/alils/log_project.go index 59db8cbf..7ede3fef 100755 --- a/logs/alils/log_project.go +++ b/core/logs/alils/log_project.go @@ -20,7 +20,7 @@ type errorMessage struct { Message string `json:"errorMessage"` } -// LogProject Define the Ali Project detail +// LogProject defines the Ali Project detail type LogProject struct { Name string // Project name Endpoint string // IP or hostname of SLS endpoint diff --git a/logs/alils/log_store.go b/core/logs/alils/log_store.go similarity index 98% rename from logs/alils/log_store.go rename to core/logs/alils/log_store.go index fa502736..d5ff25e2 100755 --- a/logs/alils/log_store.go +++ b/core/logs/alils/log_store.go @@ -12,7 +12,7 @@ import ( "github.com/gogo/protobuf/proto" ) -// LogStore Store the logs +// LogStore stores the logs type LogStore struct { Name string `json:"logstoreName"` TTL int @@ -24,7 +24,7 @@ type LogStore struct { project *LogProject } -// Shard define the Log Shard +// Shard defines the Log Shard type Shard struct { ShardID int `json:"shardID"` } @@ -71,7 +71,7 @@ func (s *LogStore) ListShards() (shardIDs []int, err error) { return } -// PutLogs put logs into logstore. +// PutLogs puts logs into logstore. // The callers should transform user logs into LogGroup. func (s *LogStore) PutLogs(lg *LogGroup) (err error) { body, err := proto.Marshal(lg) diff --git a/logs/alils/machine_group.go b/core/logs/alils/machine_group.go similarity index 88% rename from logs/alils/machine_group.go rename to core/logs/alils/machine_group.go index b6c69a14..101faeb4 100755 --- a/logs/alils/machine_group.go +++ b/core/logs/alils/machine_group.go @@ -8,13 +8,13 @@ import ( "net/http/httputil" ) -// MachineGroupAttribute define the Attribute +// MachineGroupAttribute defines the Attribute type MachineGroupAttribute struct { ExternalName string `json:"externalName"` TopicName string `json:"groupTopic"` } -// MachineGroup define the machine Group +// MachineGroup defines the machine Group type MachineGroup struct { Name string `json:"groupName"` Type string `json:"groupType"` @@ -29,20 +29,20 @@ type MachineGroup struct { project *LogProject } -// Machine define the Machine +// Machine defines the Machine type Machine struct { IP string UniqueID string `json:"machine-uniqueid"` UserdefinedID string `json:"userdefined-id"` } -// MachineList define the Machine List +// MachineList defines the Machine List type MachineList struct { Total int Machines []*Machine } -// ListMachines returns machine list of this machine group. +// ListMachines returns the machine list of this machine group. func (m *MachineGroup) ListMachines() (ms []*Machine, total int, err error) { h := map[string]string{ "x-sls-bodyrawsize": "0", diff --git a/logs/alils/request.go b/core/logs/alils/request.go similarity index 100% rename from logs/alils/request.go rename to core/logs/alils/request.go diff --git a/logs/alils/signature.go b/core/logs/alils/signature.go similarity index 100% rename from logs/alils/signature.go rename to core/logs/alils/signature.go diff --git a/logs/conn.go b/core/logs/conn.go similarity index 65% rename from logs/conn.go rename to core/logs/conn.go index afe0cbb7..1fd71be7 100644 --- a/logs/conn.go +++ b/core/logs/conn.go @@ -16,16 +16,20 @@ package logs import ( "encoding/json" + "fmt" "io" "net" - "time" + + "github.com/pkg/errors" ) // connWriter implements LoggerInterface. -// it writes messages in keep-live tcp connection. +// Writes messages in keep-live tcp connection. type connWriter struct { lg *logWriter innerWriter io.WriteCloser + formatter LogFormatter + Formatter string `json:"formatter"` ReconnectOnMsg bool `json:"reconnectOnMsg"` Reconnect bool `json:"reconnect"` Net string `json:"net"` @@ -33,23 +37,40 @@ type connWriter struct { Level int `json:"level"` } -// NewConn create new ConnWrite returning as LoggerInterface. +// NewConn creates new ConnWrite returning as LoggerInterface. func NewConn() Logger { conn := new(connWriter) conn.Level = LevelTrace + conn.formatter = conn return conn } -// Init init connection writer with json config. -// json config only need key "level". -func (c *connWriter) Init(jsonConfig string) error { - return json.Unmarshal([]byte(jsonConfig), c) +func (c *connWriter) Format(lm *LogMsg) string { + return lm.OldStyleFormat() } -// WriteMsg write message in connection. -// if connection is down, try to re-connect. -func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error { - if level > c.Level { +// Init initializes a connection writer with json config. +// json config only needs they "level" key +func (c *connWriter) Init(config string) error { + res := json.Unmarshal([]byte(config), c) + if res == nil && len(c.Formatter) > 0 { + fmtr, ok := GetFormatter(c.Formatter) + if !ok { + return errors.New(fmt.Sprintf("the formatter with name: %s not found", c.Formatter)) + } + c.formatter = fmtr + } + return res +} + +func (c *connWriter) SetFormatter(f LogFormatter) { + c.formatter = f +} + +// WriteMsg writes message in connection. +// If connection is down, try to re-connect. +func (c *connWriter) WriteMsg(lm *LogMsg) error { + if lm.Level > c.Level { return nil } if c.needToConnectOnMsg() { @@ -63,7 +84,12 @@ func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error { defer c.innerWriter.Close() } - c.lg.writeln(when, msg) + msg := c.formatter.Format(lm) + + _, err := c.lg.writeln(msg) + if err != nil { + return err + } return nil } @@ -101,7 +127,6 @@ func (c *connWriter) connect() error { func (c *connWriter) needToConnectOnMsg() bool { if c.Reconnect { - c.Reconnect = false return true } diff --git a/core/logs/conn_test.go b/core/logs/conn_test.go new file mode 100644 index 00000000..ca9ea1c7 --- /dev/null +++ b/core/logs/conn_test.go @@ -0,0 +1,97 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 logs + +import ( + "net" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +// ConnTCPListener takes a TCP listener and accepts n TCP connections +// Returns connections using connChan +func connTCPListener(t *testing.T, n int, ln net.Listener, connChan chan<- net.Conn) { + + // Listen and accept n incoming connections + for i := 0; i < n; i++ { + conn, err := ln.Accept() + if err != nil { + t.Log("Error accepting connection: ", err.Error()) + os.Exit(1) + } + + // Send accepted connection to channel + connChan <- conn + } + ln.Close() + close(connChan) +} + +func TestConn(t *testing.T) { + log := NewLogger(1000) + log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`) + log.Informational("informational") +} + +// need to rewrite this test, it's not stable +func TestReconnect(t *testing.T) { + // Setup connection listener + newConns := make(chan net.Conn) + connNum := 2 + ln, err := net.Listen("tcp", ":6002") + if err != nil { + t.Log("Error listening:", err.Error()) + os.Exit(1) + } + go connTCPListener(t, connNum, ln, newConns) + + // Setup logger + log := NewLogger(1000) + log.SetPrefix("test") + log.SetLogger(AdapterConn, `{"net":"tcp","reconnect":true,"level":6,"addr":":6002"}`) + log.Informational("informational 1") + + // Refuse first connection + first := <-newConns + first.Close() + + // Send another log after conn closed + log.Informational("informational 2") + + // Check if there was a second connection attempt + select { + case second := <-newConns: + second.Close() + default: + t.Error("Did not reconnect") + } +} + +func TestConnWriter_Format(t *testing.T) { + lg := &LogMsg{ + Level: LevelDebug, + Msg: "Hello, world", + When: time.Date(2020, 9, 19, 20, 12, 37, 9, time.UTC), + FilePath: "/user/home/main.go", + LineNumber: 13, + Prefix: "Cus", + } + cw := NewConn().(*connWriter) + res := cw.Format(lg) + assert.Equal(t, "[D] Cus Hello, world", res) +} diff --git a/logs/console.go b/core/logs/console.go similarity index 57% rename from logs/console.go rename to core/logs/console.go index 3dcaee1d..66e2c7ea 100644 --- a/logs/console.go +++ b/core/logs/console.go @@ -16,17 +16,18 @@ package logs import ( "encoding/json" + "fmt" "os" "strings" - "time" + "github.com/pkg/errors" "github.com/shiena/ansicolor" ) // brush is a color join function type brush func(string) string -// newBrush return a fix color Brush +// newBrush returns a fix color Brush func newBrush(color string) brush { pre := "\033[" reset := "\033[0m" @@ -48,39 +49,68 @@ var colors = []brush{ // consoleWriter implements LoggerInterface and writes messages to terminal. type consoleWriter struct { - lg *logWriter - Level int `json:"level"` - Colorful bool `json:"color"` //this filed is useful only when system's terminal supports color + lg *logWriter + formatter LogFormatter + Formatter string `json:"formatter"` + Level int `json:"level"` + Colorful bool `json:"color"` // this filed is useful only when system's terminal supports color } -// NewConsole create ConsoleWriter returning as LoggerInterface. +func (c *consoleWriter) Format(lm *LogMsg) string { + msg := lm.OldStyleFormat() + if c.Colorful { + msg = strings.Replace(msg, levelPrefix[lm.Level], colors[lm.Level](levelPrefix[lm.Level]), 1) + } + h, _, _ := formatTimeHeader(lm.When) + bytes := append(append(h, msg...), '\n') + return string(bytes) +} + +func (c *consoleWriter) SetFormatter(f LogFormatter) { + c.formatter = f +} + +// NewConsole creates ConsoleWriter returning as LoggerInterface. func NewConsole() Logger { + return newConsole() +} + +func newConsole() *consoleWriter { cw := &consoleWriter{ lg: newLogWriter(ansicolor.NewAnsiColorWriter(os.Stdout)), Level: LevelDebug, Colorful: true, } + cw.formatter = cw return cw } -// Init init console logger. -// jsonConfig like '{"level":LevelTrace}'. -func (c *consoleWriter) Init(jsonConfig string) error { - if len(jsonConfig) == 0 { +// Init initianlizes the console logger. +// jsonConfig must be in the format '{"level":LevelTrace}' +func (c *consoleWriter) Init(config string) error { + + if len(config) == 0 { return nil } - return json.Unmarshal([]byte(jsonConfig), c) + + res := json.Unmarshal([]byte(config), c) + if res == nil && len(c.Formatter) > 0 { + fmtr, ok := GetFormatter(c.Formatter) + if !ok { + return errors.New(fmt.Sprintf("the formatter with name: %s not found", c.Formatter)) + } + c.formatter = fmtr + } + return res } -// WriteMsg write message in console. -func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error { - if level > c.Level { +// WriteMsg writes message in console. +func (c *consoleWriter) WriteMsg(lm *LogMsg) error { + if lm.Level > c.Level { return nil } - if c.Colorful { - msg = strings.Replace(msg, levelPrefix[level], colors[level](levelPrefix[level]), 1) - } - c.lg.writeln(when, msg) + msg := c.formatter.Format(lm) + c.lg.writeln(msg) return nil } diff --git a/logs/console_test.go b/core/logs/console_test.go similarity index 78% rename from logs/console_test.go rename to core/logs/console_test.go index 4bc45f57..e345ba40 100644 --- a/logs/console_test.go +++ b/core/logs/console_test.go @@ -17,6 +17,8 @@ package logs import ( "testing" "time" + + "github.com/stretchr/testify/assert" ) // Try each log level in decreasing order of priority. @@ -62,3 +64,19 @@ func TestConsoleAsync(t *testing.T) { time.Sleep(1 * time.Millisecond) } } + +func TestFormat(t *testing.T) { + log := newConsole() + lm := &LogMsg{ + Level: LevelDebug, + Msg: "Hello, world", + When: time.Date(2020, 9, 19, 20, 12, 37, 9, time.UTC), + FilePath: "/user/home/main.go", + LineNumber: 13, + Prefix: "Cus", + } + res := log.Format(lm) + assert.Equal(t, "2020/09/19 20:12:37.000 \x1b[1;44m[D]\x1b[0m Cus Hello, world\n", res) + err := log.WriteMsg(lm) + assert.Nil(t, err) +} diff --git a/logs/es/es.go b/core/logs/es/es.go similarity index 56% rename from logs/es/es.go rename to core/logs/es/es.go index 2b7b1710..6175f253 100644 --- a/logs/es/es.go +++ b/core/logs/es/es.go @@ -12,13 +12,14 @@ import ( "github.com/elastic/go-elasticsearch/v6" "github.com/elastic/go-elasticsearch/v6/esapi" - "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/core/logs" ) -// NewES return a LoggerInterface +// NewES returns a LoggerInterface func NewES() logs.Logger { cw := &esLogger{ - Level: logs.LevelDebug, + Level: logs.LevelDebug, + indexNaming: indexNaming, } return cw } @@ -31,13 +32,36 @@ func NewES() logs.Logger { // import _ "github.com/astaxie/beego/logs/es" type esLogger struct { *elasticsearch.Client - DSN string `json:"dsn"` - Level int `json:"level"` + DSN string `json:"dsn"` + Level int `json:"level"` + formatter logs.LogFormatter + Formatter string `json:"formatter"` + + indexNaming IndexNaming +} + +func (el *esLogger) Format(lm *logs.LogMsg) string { + + msg := lm.OldStyleFormat() + idx := LogDocument{ + Timestamp: lm.When.Format(time.RFC3339), + Msg: msg, + } + body, err := json.Marshal(idx) + if err != nil { + return msg + } + return string(body) +} + +func (el *esLogger) SetFormatter(f logs.LogFormatter) { + el.formatter = f } // {"dsn":"http://localhost:9200/","level":1} -func (el *esLogger) Init(jsonconfig string) error { - err := json.Unmarshal([]byte(jsonconfig), el) +func (el *esLogger) Init(config string) error { + + err := json.Unmarshal([]byte(config), el) if err != nil { return err } @@ -56,30 +80,30 @@ func (el *esLogger) Init(jsonconfig string) error { } el.Client = conn } + if len(el.Formatter) > 0 { + fmtr, ok := logs.GetFormatter(el.Formatter) + if !ok { + return errors.New(fmt.Sprintf("the formatter with name: %s not found", el.Formatter)) + } + el.formatter = fmtr + } return nil } -// WriteMsg will write the msg and level into es -func (el *esLogger) WriteMsg(when time.Time, msg string, level int) error { - if level > el.Level { +// WriteMsg writes the msg and level into es +func (el *esLogger) WriteMsg(lm *logs.LogMsg) error { + if lm.Level > el.Level { return nil } - idx := LogDocument{ - Timestamp: when.Format(time.RFC3339), - Msg: msg, - } + msg := el.formatter.Format(lm) - body, err := json.Marshal(idx) - if err != nil { - return err - } req := esapi.IndexRequest{ - Index: fmt.Sprintf("%04d.%02d.%02d", when.Year(), when.Month(), when.Day()), + Index: indexNaming.IndexName(lm), DocumentType: "logs", - Body: strings.NewReader(string(body)), + Body: strings.NewReader(msg), } - _, err = req.Do(context.Background(), el.Client) + _, err := req.Do(context.Background(), el.Client) return err } diff --git a/core/logs/es/index.go b/core/logs/es/index.go new file mode 100644 index 00000000..0dafef4c --- /dev/null +++ b/core/logs/es/index.go @@ -0,0 +1,39 @@ +// Copyright 2020 +// +// Licensed 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 es + +import ( + "fmt" + + "github.com/astaxie/beego/core/logs" +) + +// IndexNaming generate the index name +type IndexNaming interface { + IndexName(lm *logs.LogMsg) string +} + +var indexNaming IndexNaming = &defaultIndexNaming{} + +// SetIndexNaming will register global IndexNaming +func SetIndexNaming(i IndexNaming) { + indexNaming = i +} + +type defaultIndexNaming struct{} + +func (d *defaultIndexNaming) IndexName(lm *logs.LogMsg) string { + return fmt.Sprintf("%04d.%02d.%02d", lm.When.Year(), lm.When.Month(), lm.When.Day()) +} diff --git a/core/logs/es/index_test.go b/core/logs/es/index_test.go new file mode 100644 index 00000000..03e7a911 --- /dev/null +++ b/core/logs/es/index_test.go @@ -0,0 +1,34 @@ +// Copyright 2020 +// +// Licensed 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 es + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/core/logs" +) + +func TestDefaultIndexNaming_IndexName(t *testing.T) { + tm := time.Date(2020, 9, 12, 1, 34, 45, 234, time.UTC) + lm := &logs.LogMsg{ + When: tm, + } + + res := (&defaultIndexNaming{}).IndexName(lm) + assert.Equal(t, "2020.09.12", res) +} diff --git a/logs/file.go b/core/logs/file.go similarity index 80% rename from logs/file.go rename to core/logs/file.go index 222db989..b01be357 100644 --- a/logs/file.go +++ b/core/logs/file.go @@ -30,7 +30,7 @@ import ( ) // fileLogWriter implements LoggerInterface. -// It writes messages by lines limit, file size limit, or time frequency. +// Writes messages by lines limit, file size limit, or time frequency. type fileLogWriter struct { sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize // The opened file @@ -69,9 +69,12 @@ type fileLogWriter struct { RotatePerm string `json:"rotateperm"` fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix + + formatter LogFormatter + Formatter string `json:"formatter"` } -// newFileWriter create a FileLogWriter returning as LoggerInterface. +// newFileWriter creates a FileLogWriter returning as LoggerInterface. func newFileWriter() Logger { w := &fileLogWriter{ Daily: true, @@ -86,9 +89,21 @@ func newFileWriter() Logger { MaxFiles: 999, MaxSize: 1 << 28, } + w.formatter = w return w } +func (w *fileLogWriter) Format(lm *LogMsg) string { + msg := lm.OldStyleFormat() + hd, _, _ := formatTimeHeader(lm.When) + msg = fmt.Sprintf("%s %s\n", string(hd), msg) + return msg +} + +func (w *fileLogWriter) SetFormatter(f LogFormatter) { + w.formatter = f +} + // Init file logger with json config. // jsonConfig like: // { @@ -100,8 +115,9 @@ func newFileWriter() Logger { // "rotate":true, // "perm":"0600" // } -func (w *fileLogWriter) Init(jsonConfig string) error { - err := json.Unmarshal([]byte(jsonConfig), w) +func (w *fileLogWriter) Init(config string) error { + + err := json.Unmarshal([]byte(config), w) if err != nil { return err } @@ -113,6 +129,14 @@ func (w *fileLogWriter) Init(jsonConfig string) error { if w.suffix == "" { w.suffix = ".log" } + + if len(w.Formatter) > 0 { + fmtr, ok := GetFormatter(w.Formatter) + if !ok { + return errors.New(fmt.Sprintf("the formatter with name: %s not found", w.Formatter)) + } + w.formatter = fmtr + } err = w.startLogger() return err } @@ -130,42 +154,44 @@ func (w *fileLogWriter) startLogger() error { return w.initFd() } -func (w *fileLogWriter) needRotateDaily(size int, day int) bool { +func (w *fileLogWriter) needRotateDaily(day int) bool { return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) || (w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) || (w.Daily && day != w.dailyOpenDate) } -func (w *fileLogWriter) needRotateHourly(size int, hour int) bool { +func (w *fileLogWriter) needRotateHourly(hour int) bool { return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) || (w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) || (w.Hourly && hour != w.hourlyOpenDate) } -// WriteMsg write logger message into file. -func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error { - if level > w.Level { +// WriteMsg writes logger message into file. +func (w *fileLogWriter) WriteMsg(lm *LogMsg) error { + if lm.Level > w.Level { return nil } - hd, d, h := formatTimeHeader(when) - msg = string(hd) + msg + "\n" + + _, d, h := formatTimeHeader(lm.When) + + msg := w.formatter.Format(lm) if w.Rotate { w.RLock() - if w.needRotateHourly(len(msg), h) { + if w.needRotateHourly(h) { w.RUnlock() w.Lock() - if w.needRotateHourly(len(msg), h) { - if err := w.doRotate(when); err != nil { + if w.needRotateHourly(h) { + if err := w.doRotate(lm.When); err != nil { fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) } } w.Unlock() - } else if w.needRotateDaily(len(msg), d) { + } else if w.needRotateDaily(d) { w.RUnlock() w.Lock() - if w.needRotateDaily(len(msg), d) { - if err := w.doRotate(when); err != nil { + if w.needRotateDaily(d) { + if err := w.doRotate(lm.When); err != nil { fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) } } @@ -236,7 +262,7 @@ func (w *fileLogWriter) dailyRotate(openTime time.Time) { tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100)) <-tm.C w.Lock() - if w.needRotateDaily(0, time.Now().Day()) { + if w.needRotateDaily(time.Now().Day()) { if err := w.doRotate(time.Now()); err != nil { fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) } @@ -251,7 +277,7 @@ func (w *fileLogWriter) hourlyRotate(openTime time.Time) { tm := time.NewTimer(time.Duration(nextHour.UnixNano() - openTime.UnixNano() + 100)) <-tm.C w.Lock() - if w.needRotateHourly(0, time.Now().Hour()) { + if w.needRotateHourly(time.Now().Hour()) { if err := w.doRotate(time.Now()); err != nil { fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) } @@ -286,7 +312,7 @@ func (w *fileLogWriter) lines() (int, error) { return count, nil } -// DoRotate means it need to write file in new file. +// DoRotate means it needs to write logs into a new file. // new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size) func (w *fileLogWriter) doRotate(logTime time.Time) error { // file exists @@ -302,7 +328,7 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error { _, err = os.Lstat(w.Filename) if err != nil { - //even if the file is not exist or other ,we should RESTART the logger + // even if the file is not exist or other ,we should RESTART the logger goto RESTART_LOGGER } @@ -373,21 +399,21 @@ func (w *fileLogWriter) deleteOldLog() { if info == nil { return } - if w.Hourly { - if !info.IsDir() && info.ModTime().Add(1 * time.Hour * time.Duration(w.MaxHours)).Before(time.Now()) { - if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) && - strings.HasSuffix(filepath.Base(path), w.suffix) { - os.Remove(path) - } - } - } else if w.Daily { - if !info.IsDir() && info.ModTime().Add(24 * time.Hour * time.Duration(w.MaxDays)).Before(time.Now()) { - if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) && - strings.HasSuffix(filepath.Base(path), w.suffix) { - os.Remove(path) - } - } - } + if w.Hourly { + if !info.IsDir() && info.ModTime().Add(1*time.Hour*time.Duration(w.MaxHours)).Before(time.Now()) { + if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) && + strings.HasSuffix(filepath.Base(path), w.suffix) { + os.Remove(path) + } + } + } else if w.Daily { + if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) { + if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) && + strings.HasSuffix(filepath.Base(path), w.suffix) { + os.Remove(path) + } + } + } return }) } @@ -397,7 +423,7 @@ func (w *fileLogWriter) Destroy() { w.fileWriter.Close() } -// Flush flush file logger. +// Flush flushes file logger. // there are no buffering messages in file logger in memory. // flush file means sync file from disk. func (w *fileLogWriter) Flush() { diff --git a/logs/file_test.go b/core/logs/file_test.go similarity index 89% rename from logs/file_test.go rename to core/logs/file_test.go index e7c2ca9a..6612ebe6 100644 --- a/logs/file_test.go +++ b/core/logs/file_test.go @@ -22,6 +22,8 @@ import ( "strconv" "testing" "time" + + "github.com/stretchr/testify/assert" ) func TestFilePerm(t *testing.T) { @@ -186,7 +188,7 @@ func TestFileDailyRotate_06(t *testing.T) { //test file mode func TestFileHourlyRotate_01(t *testing.T) { log := NewLogger(10000) - log.SetLogger("file", `{"filename":"test3.log","hourly":true,"maxlines":4}`) + log.SetLogger("file", `{"filename":"test3.log","hourly":true,"maxlines":4}`) log.Debug("debug") log.Info("info") log.Notice("notice") @@ -237,7 +239,7 @@ func TestFileHourlyRotate_05(t *testing.T) { func TestFileHourlyRotate_06(t *testing.T) { //test file mode log := NewLogger(10000) - log.SetLogger("file", `{"filename":"test3.log", "hourly":true, "maxlines":4}`) + log.SetLogger("file", `{"filename":"test3.log", "hourly":true, "maxlines":4}`) log.Debug("debug") log.Info("info") log.Notice("notice") @@ -268,20 +270,26 @@ func testFileRotate(t *testing.T, fn1, fn2 string, daily, hourly bool) { Perm: "0660", RotatePerm: "0440", } + fw.formatter = fw - if daily { - fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1)) - fw.dailyOpenTime = time.Now().Add(-24 * time.Hour) - fw.dailyOpenDate = fw.dailyOpenTime.Day() - } + if daily { + fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1)) + fw.dailyOpenTime = time.Now().Add(-24 * time.Hour) + fw.dailyOpenDate = fw.dailyOpenTime.Day() + } - if hourly { - fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1)) - fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour) - fw.hourlyOpenDate = fw.hourlyOpenTime.Day() - } + if hourly { + fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1)) + fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour) + fw.hourlyOpenDate = fw.hourlyOpenTime.Day() + } + lm := &LogMsg{ + Msg: "Test message", + Level: LevelDebug, + When: time.Now(), + } - fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug) + fw.WriteMsg(lm) for _, file := range []string{fn1, fn2} { _, err := os.Stat(file) @@ -303,6 +311,8 @@ func testFileDailyRotate(t *testing.T, fn1, fn2 string) { Perm: "0660", RotatePerm: "0440", } + fw.formatter = fw + fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1)) fw.dailyOpenTime = time.Now().Add(-24 * time.Hour) fw.dailyOpenDate = fw.dailyOpenTime.Day() @@ -328,13 +338,15 @@ func testFileDailyRotate(t *testing.T, fn1, fn2 string) { func testFileHourlyRotate(t *testing.T, fn1, fn2 string) { fw := &fileLogWriter{ - Hourly: true, - MaxHours: 168, + Hourly: true, + MaxHours: 168, Rotate: true, Level: LevelTrace, Perm: "0660", RotatePerm: "0440", } + + fw.formatter = fw fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1)) fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour) fw.hourlyOpenDate = fw.hourlyOpenTime.Hour() @@ -418,3 +430,18 @@ func BenchmarkFileOnGoroutine(b *testing.B) { } os.Remove("test4.log") } + +func TestFileLogWriter_Format(t *testing.T) { + lg := &LogMsg{ + Level: LevelDebug, + Msg: "Hello, world", + When: time.Date(2020, 9, 19, 20, 12, 37, 9, time.UTC), + FilePath: "/user/home/main.go", + LineNumber: 13, + Prefix: "Cus", + } + + fw := newFileWriter().(*fileLogWriter) + res := fw.Format(lg) + assert.Equal(t, "2020/09/19 20:12:37.000 [D] Cus Hello, world\n", res) +} diff --git a/core/logs/formatter.go b/core/logs/formatter.go new file mode 100644 index 00000000..67500b2b --- /dev/null +++ b/core/logs/formatter.go @@ -0,0 +1,89 @@ +// Copyright 2020 +// +// Licensed 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 logs + +import ( + "path" + "strconv" +) + +var formatterMap = make(map[string]LogFormatter, 4) + +type LogFormatter interface { + Format(lm *LogMsg) string +} + +// PatternLogFormatter provides a quick format method +// for example: +// tes := &PatternLogFormatter{Pattern: "%F:%n|%w %t>> %m", WhenFormat: "2006-01-02"} +// RegisterFormatter("tes", tes) +// SetGlobalFormatter("tes") +type PatternLogFormatter struct { + Pattern string + WhenFormat string +} + +func (p *PatternLogFormatter) getWhenFormatter() string { + s := p.WhenFormat + if s == "" { + s = "2006/01/02 15:04:05.123" // default style + } + return s +} + +func (p *PatternLogFormatter) Format(lm *LogMsg) string { + return p.ToString(lm) +} + +// RegisterFormatter register an formatter. Usually you should use this to extend your custom formatter +// for example: +// RegisterFormatter("my-fmt", &MyFormatter{}) +// logs.SetFormatter(Console, `{"formatter": "my-fmt"}`) +func RegisterFormatter(name string, fmtr LogFormatter) { + formatterMap[name] = fmtr +} + +func GetFormatter(name string) (LogFormatter, bool) { + res, ok := formatterMap[name] + return res, ok +} + +// 'w' when, 'm' msg,'f' filename,'F' full path,'n' line number +// 'l' level number, 't' prefix of level type, 'T' full name of level type +func (p *PatternLogFormatter) ToString(lm *LogMsg) string { + s := []rune(p.Pattern) + m := map[rune]string{ + 'w': lm.When.Format(p.getWhenFormatter()), + 'm': lm.Msg, + 'n': strconv.Itoa(lm.LineNumber), + 'l': strconv.Itoa(lm.Level), + 't': levelPrefix[lm.Level-1], + 'T': levelNames[lm.Level-1], + 'F': lm.FilePath, + } + _, m['f'] = path.Split(lm.FilePath) + res := "" + for i := 0; i < len(s)-1; i++ { + if s[i] == '%' { + if k, ok := m[s[i+1]]; ok { + res += k + i++ + continue + } + } + res += string(s[i]) + } + return res +} diff --git a/core/logs/formatter_test.go b/core/logs/formatter_test.go new file mode 100644 index 00000000..a97765ac --- /dev/null +++ b/core/logs/formatter_test.go @@ -0,0 +1,95 @@ +// Copyright 2020 +// +// Licensed 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 logs + +import ( + "encoding/json" + "errors" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +type CustomFormatter struct{} + +func (c *CustomFormatter) Format(lm *LogMsg) string { + return "hello, msg: " + lm.Msg +} + +type TestLogger struct { + Formatter string `json:"formatter"` + Expected string + formatter LogFormatter +} + +func (t *TestLogger) Init(config string) error { + er := json.Unmarshal([]byte(config), t) + t.formatter, _ = GetFormatter(t.Formatter) + return er +} + +func (t *TestLogger) WriteMsg(lm *LogMsg) error { + msg := t.formatter.Format(lm) + if msg != t.Expected { + return errors.New("not equal") + } + return nil +} + +func (t *TestLogger) Destroy() { + panic("implement me") +} + +func (t *TestLogger) Flush() { + panic("implement me") +} + +func (t *TestLogger) SetFormatter(f LogFormatter) { + panic("implement me") +} + +func TestCustomFormatter(t *testing.T) { + RegisterFormatter("custom", &CustomFormatter{}) + tl := &TestLogger{ + Expected: "hello, msg: world", + } + assert.Nil(t, tl.Init(`{"formatter": "custom"}`)) + assert.Nil(t, tl.WriteMsg(&LogMsg{ + Msg: "world", + })) +} + +func TestPatternLogFormatter(t *testing.T) { + tes := &PatternLogFormatter{ + Pattern: "%F:%n|%w%t>> %m", + WhenFormat: "2006-01-02", + } + when := time.Now() + lm := &LogMsg{ + Msg: "message", + FilePath: "/User/go/beego/main.go", + Level: LevelWarn, + LineNumber: 10, + When: when, + } + got := tes.ToString(lm) + want := lm.FilePath + ":" + strconv.Itoa(lm.LineNumber) + "|" + + when.Format(tes.WhenFormat) + levelPrefix[lm.Level-1] + ">> " + lm.Msg + if got != want { + t.Errorf("want %s, got %s", want, got) + } +} diff --git a/logs/jianliao.go b/core/logs/jianliao.go similarity index 56% rename from logs/jianliao.go rename to core/logs/jianliao.go index 88ba0f9a..c82a0957 100644 --- a/logs/jianliao.go +++ b/core/logs/jianliao.go @@ -5,7 +5,8 @@ import ( "fmt" "net/http" "net/url" - "time" + + "github.com/pkg/errors" ) // JLWriter implements beego LoggerInterface and is used to send jiaoliao webhook @@ -16,26 +17,50 @@ type JLWriter struct { RedirectURL string `json:"redirecturl,omitempty"` ImageURL string `json:"imageurl,omitempty"` Level int `json:"level"` + + formatter LogFormatter + Formatter string `json:"formatter"` } -// newJLWriter create jiaoliao writer. +// newJLWriter creates jiaoliao writer. func newJLWriter() Logger { - return &JLWriter{Level: LevelTrace} + res := &JLWriter{Level: LevelTrace} + res.formatter = res + return res } // Init JLWriter with json config string -func (s *JLWriter) Init(jsonconfig string) error { - return json.Unmarshal([]byte(jsonconfig), s) +func (s *JLWriter) Init(config string) error { + + res := json.Unmarshal([]byte(config), s) + if res == nil && len(s.Formatter) > 0 { + fmtr, ok := GetFormatter(s.Formatter) + if !ok { + return errors.New(fmt.Sprintf("the formatter with name: %s not found", s.Formatter)) + } + s.formatter = fmtr + } + return res } -// WriteMsg write message in smtp writer. -// it will send an email with subject and only this message. -func (s *JLWriter) WriteMsg(when time.Time, msg string, level int) error { - if level > s.Level { +func (s *JLWriter) Format(lm *LogMsg) string { + msg := lm.OldStyleFormat() + msg = fmt.Sprintf("%s %s", lm.When.Format("2006-01-02 15:04:05"), msg) + return msg +} + +func (s *JLWriter) SetFormatter(f LogFormatter) { + s.formatter = f +} + +// WriteMsg writes message in smtp writer. +// Sends an email with subject and only this message. +func (s *JLWriter) WriteMsg(lm *LogMsg) error { + if lm.Level > s.Level { return nil } - text := fmt.Sprintf("%s %s", when.Format("2006-01-02 15:04:05"), msg) + text := s.formatter.Format(lm) form := url.Values{} form.Add("authorName", s.AuthorName) diff --git a/core/logs/jianliao_test.go b/core/logs/jianliao_test.go new file mode 100644 index 00000000..a1b2d076 --- /dev/null +++ b/core/logs/jianliao_test.go @@ -0,0 +1,36 @@ +// Copyright 2020 +// +// Licensed 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 logs + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestJLWriter_Format(t *testing.T) { + lg := &LogMsg{ + Level: LevelDebug, + Msg: "Hello, world", + When: time.Date(2020, 9, 19, 20, 12, 37, 9, time.UTC), + FilePath: "/user/home/main.go", + LineNumber: 13, + Prefix: "Cus", + } + jl := newJLWriter().(*JLWriter) + res := jl.Format(lg) + assert.Equal(t, "2020-09-19 20:12:37 [D] Cus Hello, world", res) +} diff --git a/logs/log.go b/core/logs/log.go similarity index 78% rename from logs/log.go rename to core/logs/log.go index 39c006d2..d5953dfb 100644 --- a/logs/log.go +++ b/core/logs/log.go @@ -37,12 +37,12 @@ import ( "fmt" "log" "os" - "path" "runtime" - "strconv" "strings" "sync" "time" + + "github.com/pkg/errors" ) // RFC5424 log message levels. @@ -86,9 +86,10 @@ type newLoggerFunc func() Logger // Logger defines the behavior of a log provider. type Logger interface { Init(config string) error - WriteMsg(when time.Time, msg string, level int) error + WriteMsg(lm *LogMsg) error Destroy() Flush() + SetFormatter(f LogFormatter) } var adapters = make(map[string]newLoggerFunc) @@ -108,20 +109,22 @@ func Register(name string, log newLoggerFunc) { } // BeeLogger is default logger in beego application. -// it can contain several providers and log message into all providers. +// Can contain several providers and log message into all providers. type BeeLogger struct { lock sync.Mutex level int init bool enableFuncCallDepth bool loggerFuncCallDepth int + enableFullFilePath bool asynchronous bool prefix string msgChanLen int64 - msgChan chan *logMsg + msgChan chan *LogMsg signalChan chan string wg sync.WaitGroup outputs []*nameLogger + globalFormatter string } const defaultAsyncMsgLen = 1e3 @@ -131,21 +134,15 @@ type nameLogger struct { name string } -type logMsg struct { - level int - msg string - when time.Time -} - var logMsgPool *sync.Pool // NewLogger returns a new BeeLogger. -// channelLen means the number of messages in chan(used where asynchronous is true). +// channelLen: the number of messages in chan(used where asynchronous is true). // if the buffering chan is full, logger adapters write to file or other way. func NewLogger(channelLens ...int64) *BeeLogger { bl := new(BeeLogger) bl.level = LevelDebug - bl.loggerFuncCallDepth = 2 + bl.loggerFuncCallDepth = 3 bl.msgChanLen = append(channelLens, 0)[0] if bl.msgChanLen <= 0 { bl.msgChanLen = defaultAsyncMsgLen @@ -155,7 +152,7 @@ func NewLogger(channelLens ...int64) *BeeLogger { return bl } -// Async set the log to asynchronous and start the goroutine +// Async sets the log to asynchronous and start the goroutine func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger { bl.lock.Lock() defer bl.lock.Unlock() @@ -166,10 +163,10 @@ func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger { if len(msgLen) > 0 && msgLen[0] > 0 { bl.msgChanLen = msgLen[0] } - bl.msgChan = make(chan *logMsg, bl.msgChanLen) + bl.msgChan = make(chan *LogMsg, bl.msgChanLen) logMsgPool = &sync.Pool{ New: func() interface{} { - return &logMsg{} + return &LogMsg{} }, } bl.wg.Add(1) @@ -178,7 +175,7 @@ func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger { } // SetLogger provides a given logger adapter into BeeLogger with config string. -// config need to be correct JSON as string: {"interval":360}. +// config must in in JSON format like {"interval":360}} func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error { config := append(configs, "{}")[0] for _, l := range bl.outputs { @@ -193,7 +190,18 @@ func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error { } lg := logAdapter() + + // Global formatter overrides the default set formatter + if len(bl.globalFormatter) > 0 { + fmtr, ok := GetFormatter(bl.globalFormatter) + if !ok { + return errors.New(fmt.Sprintf("the formatter with name: %s not found", bl.globalFormatter)) + } + lg.SetFormatter(fmtr) + } + err := lg.Init(config) + if err != nil { fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error()) return err @@ -203,7 +211,7 @@ func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error { } // SetLogger provides a given logger adapter into BeeLogger with config string. -// config need to be correct JSON as string: {"interval":360}. +// config must in in JSON format like {"interval":360}} func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error { bl.lock.Lock() defer bl.lock.Unlock() @@ -214,7 +222,7 @@ func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error { return bl.setLogger(adapterName, configs...) } -// DelLogger remove a logger adapter in BeeLogger. +// DelLogger removes a logger adapter in BeeLogger. func (bl *BeeLogger) DelLogger(adapterName string) error { bl.lock.Lock() defer bl.lock.Unlock() @@ -233,9 +241,9 @@ func (bl *BeeLogger) DelLogger(adapterName string) error { return nil } -func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) { +func (bl *BeeLogger) writeToLoggers(lm *LogMsg) { for _, l := range bl.outputs { - err := l.WriteMsg(when, msg, level) + err := l.WriteMsg(lm) if err != nil { fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err) } @@ -250,65 +258,72 @@ func (bl *BeeLogger) Write(p []byte) (n int, err error) { if p[len(p)-1] == '\n' { p = p[0 : len(p)-1] } + lm := &LogMsg{ + Msg: string(p), + Level: levelLoggerImpl, + } + // set levelLoggerImpl to ensure all log message will be write out - err = bl.writeMsg(levelLoggerImpl, string(p)) + err = bl.writeMsg(lm) if err == nil { return len(p), err } return 0, err } -func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error { +func (bl *BeeLogger) writeMsg(lm *LogMsg) error { if !bl.init { bl.lock.Lock() bl.setLogger(AdapterConsole) bl.lock.Unlock() } - if len(v) > 0 { - msg = fmt.Sprintf(msg, v...) + var ( + file string + line int + ok bool + ) + + _, file, line, ok = runtime.Caller(bl.loggerFuncCallDepth) + if !ok { + file = "???" + line = 0 } + lm.FilePath = file + lm.LineNumber = line - msg = bl.prefix + " " + msg + lm.enableFullFilePath = bl.enableFullFilePath + lm.enableFuncCallDepth = bl.enableFuncCallDepth - when := time.Now() - if bl.enableFuncCallDepth { - _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) - if !ok { - file = "???" - line = 0 - } - _, filename := path.Split(file) - msg = "[" + filename + ":" + strconv.Itoa(line) + "] " + msg - } - - //set level info in front of filename info - if logLevel == levelLoggerImpl { + // set level info in front of filename info + if lm.Level == levelLoggerImpl { // set to emergency to ensure all log will be print out correctly - logLevel = LevelEmergency - } else { - msg = levelPrefix[logLevel] + " " + msg + lm.Level = LevelEmergency } if bl.asynchronous { - lm := logMsgPool.Get().(*logMsg) - lm.level = logLevel - lm.msg = msg - lm.when = when + logM := logMsgPool.Get().(*LogMsg) + logM.Level = lm.Level + logM.Msg = lm.Msg + logM.When = lm.When + logM.Args = lm.Args + logM.FilePath = lm.FilePath + logM.LineNumber = lm.LineNumber + logM.Prefix = lm.Prefix if bl.outputs != nil { bl.msgChan <- lm } else { logMsgPool.Put(lm) } } else { - bl.writeToLoggers(when, msg, logLevel) + bl.writeToLoggers(lm) } return nil } -// SetLevel Set log message level. +// SetLevel sets log message level. // If message level (such as LevelDebug) is higher than logger level (such as LevelWarning), -// log providers will not even be sent the message. +// log providers will not be sent the message. func (bl *BeeLogger) SetLevel(l int) { bl.level = l } @@ -345,7 +360,7 @@ func (bl *BeeLogger) startLogger() { for { select { case bm := <-bl.msgChan: - bl.writeToLoggers(bm.when, bm.msg, bm.level) + bl.writeToLoggers(bm) logMsgPool.Put(bm) case sg := <-bl.signalChan: // Now should only send "flush" or "close" to bl.signalChan @@ -365,12 +380,33 @@ func (bl *BeeLogger) startLogger() { } } +func (bl *BeeLogger) setGlobalFormatter(fmtter string) error { + bl.globalFormatter = fmtter + return nil +} + +// SetGlobalFormatter sets the global formatter for all log adapters +// don't forget to register the formatter by invoking RegisterFormatter +func SetGlobalFormatter(fmtter string) error { + return beeLogger.setGlobalFormatter(fmtter) +} + // Emergency Log EMERGENCY level message. func (bl *BeeLogger) Emergency(format string, v ...interface{}) { if LevelEmergency > bl.level { return } - bl.writeMsg(LevelEmergency, format, v...) + + lm := &LogMsg{ + Level: LevelEmergency, + Msg: format, + When: time.Now(), + } + if len(v) > 0 { + lm.Msg = fmt.Sprintf(lm.Msg, v...) + } + + bl.writeMsg(lm) } // Alert Log ALERT level message. @@ -378,7 +414,14 @@ func (bl *BeeLogger) Alert(format string, v ...interface{}) { if LevelAlert > bl.level { return } - bl.writeMsg(LevelAlert, format, v...) + + lm := &LogMsg{ + Level: LevelAlert, + Msg: format, + When: time.Now(), + Args: v, + } + bl.writeMsg(lm) } // Critical Log CRITICAL level message. @@ -386,7 +429,14 @@ func (bl *BeeLogger) Critical(format string, v ...interface{}) { if LevelCritical > bl.level { return } - bl.writeMsg(LevelCritical, format, v...) + lm := &LogMsg{ + Level: LevelCritical, + Msg: format, + When: time.Now(), + Args: v, + } + + bl.writeMsg(lm) } // Error Log ERROR level message. @@ -394,7 +444,14 @@ func (bl *BeeLogger) Error(format string, v ...interface{}) { if LevelError > bl.level { return } - bl.writeMsg(LevelError, format, v...) + lm := &LogMsg{ + Level: LevelError, + Msg: format, + When: time.Now(), + Args: v, + } + + bl.writeMsg(lm) } // Warning Log WARNING level message. @@ -402,7 +459,14 @@ func (bl *BeeLogger) Warning(format string, v ...interface{}) { if LevelWarn > bl.level { return } - bl.writeMsg(LevelWarn, format, v...) + lm := &LogMsg{ + Level: LevelWarn, + Msg: format, + When: time.Now(), + Args: v, + } + + bl.writeMsg(lm) } // Notice Log NOTICE level message. @@ -410,7 +474,14 @@ func (bl *BeeLogger) Notice(format string, v ...interface{}) { if LevelNotice > bl.level { return } - bl.writeMsg(LevelNotice, format, v...) + lm := &LogMsg{ + Level: LevelNotice, + Msg: format, + When: time.Now(), + Args: v, + } + + bl.writeMsg(lm) } // Informational Log INFORMATIONAL level message. @@ -418,7 +489,14 @@ func (bl *BeeLogger) Informational(format string, v ...interface{}) { if LevelInfo > bl.level { return } - bl.writeMsg(LevelInfo, format, v...) + lm := &LogMsg{ + Level: LevelInfo, + Msg: format, + When: time.Now(), + Args: v, + } + + bl.writeMsg(lm) } // Debug Log DEBUG level message. @@ -426,7 +504,14 @@ func (bl *BeeLogger) Debug(format string, v ...interface{}) { if LevelDebug > bl.level { return } - bl.writeMsg(LevelDebug, format, v...) + lm := &LogMsg{ + Level: LevelDebug, + Msg: format, + When: time.Now(), + Args: v, + } + + bl.writeMsg(lm) } // Warn Log WARN level message. @@ -435,7 +520,14 @@ func (bl *BeeLogger) Warn(format string, v ...interface{}) { if LevelWarn > bl.level { return } - bl.writeMsg(LevelWarn, format, v...) + lm := &LogMsg{ + Level: LevelWarn, + Msg: format, + When: time.Now(), + Args: v, + } + + bl.writeMsg(lm) } // Info Log INFO level message. @@ -444,7 +536,14 @@ func (bl *BeeLogger) Info(format string, v ...interface{}) { if LevelInfo > bl.level { return } - bl.writeMsg(LevelInfo, format, v...) + lm := &LogMsg{ + Level: LevelInfo, + Msg: format, + When: time.Now(), + Args: v, + } + + bl.writeMsg(lm) } // Trace Log TRACE level message. @@ -453,7 +552,14 @@ func (bl *BeeLogger) Trace(format string, v ...interface{}) { if LevelDebug > bl.level { return } - bl.writeMsg(LevelDebug, format, v...) + lm := &LogMsg{ + Level: LevelDebug, + Msg: format, + When: time.Now(), + Args: v, + } + + bl.writeMsg(lm) } // Flush flush all chan data. @@ -497,7 +603,7 @@ func (bl *BeeLogger) flush() { for { if len(bl.msgChan) > 0 { bm := <-bl.msgChan - bl.writeToLoggers(bm.when, bm.msg, bm.level) + bl.writeToLoggers(bm) logMsgPool.Put(bm) continue } @@ -547,6 +653,12 @@ func GetLogger(prefixes ...string) *log.Logger { return l } +// EnableFullFilePath enables full file path logging. Disabled by default +// e.g "/home/Documents/GitHub/beego/mainapp/" instead of "mainapp" +func EnableFullFilePath(b bool) { + beeLogger.enableFullFilePath = b +} + // Reset will remove all the adapter func Reset() { beeLogger.Reset() @@ -575,7 +687,7 @@ func EnableFuncCallDepth(b bool) { // SetLogFuncCall set the CallDepth, default is 4 func SetLogFuncCall(b bool) { beeLogger.EnableFuncCallDepth(b) - beeLogger.SetLogFuncCallDepth(4) + beeLogger.SetLogFuncCallDepth(3) } // SetLogFuncCallDepth set log funcCallDepth @@ -653,9 +765,9 @@ func formatLog(f interface{}, v ...interface{}) string { return msg } if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") { - //format string + // format string } else { - //do not contain format char + // do not contain format char msg += strings.Repeat(" %v", len(v)) } default: diff --git a/core/logs/log_msg.go b/core/logs/log_msg.go new file mode 100644 index 00000000..f96fa72f --- /dev/null +++ b/core/logs/log_msg.go @@ -0,0 +1,55 @@ +// Copyright 2020 +// +// Licensed 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 logs + +import ( + "fmt" + "path" + "time" +) + +type LogMsg struct { + Level int + Msg string + When time.Time + FilePath string + LineNumber int + Args []interface{} + Prefix string + enableFullFilePath bool + enableFuncCallDepth bool +} + +// OldStyleFormat you should never invoke this +func (lm *LogMsg) OldStyleFormat() string { + msg := lm.Msg + + if len(lm.Args) > 0 { + lm.Msg = fmt.Sprintf(lm.Msg, lm.Args...) + } + + msg = lm.Prefix + " " + msg + + if lm.enableFuncCallDepth { + filePath := lm.FilePath + if !lm.enableFullFilePath { + _, filePath = path.Split(filePath) + } + msg = fmt.Sprintf("[%s:%d] %s", filePath, lm.LineNumber, msg) + } + + msg = levelPrefix[lm.Level] + " " + msg + return msg +} diff --git a/core/logs/log_msg_test.go b/core/logs/log_msg_test.go new file mode 100644 index 00000000..f213ed42 --- /dev/null +++ b/core/logs/log_msg_test.go @@ -0,0 +1,44 @@ +// Copyright 2020 +// +// Licensed 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 logs + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestLogMsg_OldStyleFormat(t *testing.T) { + lg := &LogMsg{ + Level: LevelDebug, + Msg: "Hello, world", + When: time.Date(2020, 9, 19, 20, 12, 37, 9, time.UTC), + FilePath: "/user/home/main.go", + LineNumber: 13, + Prefix: "Cus", + } + res := lg.OldStyleFormat() + assert.Equal(t, "[D] Cus Hello, world", res) + + lg.enableFuncCallDepth = true + res = lg.OldStyleFormat() + assert.Equal(t, "[D] [main.go:13] Cus Hello, world", res) + + lg.enableFullFilePath = true + + res = lg.OldStyleFormat() + assert.Equal(t, "[D] [/user/home/main.go:13] Cus Hello, world", res) +} diff --git a/core/logs/log_test.go b/core/logs/log_test.go new file mode 100644 index 00000000..66f59108 --- /dev/null +++ b/core/logs/log_test.go @@ -0,0 +1,27 @@ +// Copyright 2020 +// +// Licensed 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 logs + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBeeLogger_DelLogger(t *testing.T) { + prefix := "My-Cus" + l := GetLogger(prefix) + assert.NotNil(t, l) +} diff --git a/logs/logger.go b/core/logs/logger.go similarity index 96% rename from logs/logger.go rename to core/logs/logger.go index c7cf8a56..d8b334d4 100644 --- a/logs/logger.go +++ b/core/logs/logger.go @@ -30,11 +30,12 @@ func newLogWriter(wr io.Writer) *logWriter { return &logWriter{writer: wr} } -func (lg *logWriter) writeln(when time.Time, msg string) { +func (lg *logWriter) writeln(msg string) (int, error) { lg.Lock() - h, _, _ := formatTimeHeader(when) - lg.writer.Write(append(append(h, msg...), '\n')) + msg += "\n" + n, err := lg.writer.Write([]byte(msg)) lg.Unlock() + return n, err } const ( diff --git a/logs/logger_test.go b/core/logs/logger_test.go similarity index 100% rename from logs/logger_test.go rename to core/logs/logger_test.go diff --git a/logs/multifile.go b/core/logs/multifile.go similarity index 83% rename from logs/multifile.go rename to core/logs/multifile.go index 90168274..79178211 100644 --- a/logs/multifile.go +++ b/core/logs/multifile.go @@ -16,7 +16,6 @@ package logs import ( "encoding/json" - "time" ) // A filesLogWriter manages several fileLogWriter @@ -46,6 +45,7 @@ var levelNames = [...]string{"emergency", "alert", "critical", "error", "warning // } func (f *multiFileLogWriter) Init(config string) error { + writer := newFileWriter().(*fileLogWriter) err := writer.Init(config) if err != nil { @@ -54,11 +54,17 @@ func (f *multiFileLogWriter) Init(config string) error { f.fullLogWriter = writer f.writers[LevelDebug+1] = writer - //unmarshal "separate" field to f.Separate - json.Unmarshal([]byte(config), f) + // unmarshal "separate" field to f.Separate + err = json.Unmarshal([]byte(config), f) + if err != nil { + return err + } jsonMap := map[string]interface{}{} - json.Unmarshal([]byte(config), &jsonMap) + err = json.Unmarshal([]byte(config), &jsonMap) + if err != nil { + return err + } for i := LevelEmergency; i < LevelDebug+1; i++ { for _, v := range f.Separate { @@ -75,10 +81,17 @@ func (f *multiFileLogWriter) Init(config string) error { } } } - return nil } +func (f *multiFileLogWriter) Format(lm *LogMsg) string { + return lm.OldStyleFormat() +} + +func (f *multiFileLogWriter) SetFormatter(fmt LogFormatter) { + f.fullLogWriter.SetFormatter(f) +} + func (f *multiFileLogWriter) Destroy() { for i := 0; i < len(f.writers); i++ { if f.writers[i] != nil { @@ -87,14 +100,14 @@ func (f *multiFileLogWriter) Destroy() { } } -func (f *multiFileLogWriter) WriteMsg(when time.Time, msg string, level int) error { +func (f *multiFileLogWriter) WriteMsg(lm *LogMsg) error { if f.fullLogWriter != nil { - f.fullLogWriter.WriteMsg(when, msg, level) + f.fullLogWriter.WriteMsg(lm) } for i := 0; i < len(f.writers)-1; i++ { if f.writers[i] != nil { - if level == f.writers[i].Level { - f.writers[i].WriteMsg(when, msg, level) + if lm.Level == f.writers[i].Level { + f.writers[i].WriteMsg(lm) } } } @@ -111,7 +124,8 @@ func (f *multiFileLogWriter) Flush() { // newFilesWriter create a FileLogWriter returning as LoggerInterface. func newFilesWriter() Logger { - return &multiFileLogWriter{} + res := &multiFileLogWriter{} + return res } func init() { diff --git a/logs/multifile_test.go b/core/logs/multifile_test.go similarity index 100% rename from logs/multifile_test.go rename to core/logs/multifile_test.go diff --git a/core/logs/slack.go b/core/logs/slack.go new file mode 100644 index 00000000..b6e2f170 --- /dev/null +++ b/core/logs/slack.go @@ -0,0 +1,82 @@ +package logs + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/pkg/errors" +) + +// SLACKWriter implements beego LoggerInterface and is used to send jiaoliao webhook +type SLACKWriter struct { + WebhookURL string `json:"webhookurl"` + Level int `json:"level"` + formatter LogFormatter + Formatter string `json:"formatter"` +} + +// newSLACKWriter creates jiaoliao writer. +func newSLACKWriter() Logger { + res := &SLACKWriter{Level: LevelTrace} + res.formatter = res + return res +} + +func (s *SLACKWriter) Format(lm *LogMsg) string { + text := fmt.Sprintf("{\"text\": \"%s %s\"}", lm.When.Format("2006-01-02 15:04:05"), lm.OldStyleFormat()) + return text +} + +func (s *SLACKWriter) SetFormatter(f LogFormatter) { + s.formatter = f +} + +// Init SLACKWriter with json config string +func (s *SLACKWriter) Init(config string) error { + res := json.Unmarshal([]byte(config), s) + + if res == nil && len(s.Formatter) > 0 { + fmtr, ok := GetFormatter(s.Formatter) + if !ok { + return errors.New(fmt.Sprintf("the formatter with name: %s not found", s.Formatter)) + } + s.formatter = fmtr + } + + return res +} + +// WriteMsg write message in smtp writer. +// Sends an email with subject and only this message. +func (s *SLACKWriter) WriteMsg(lm *LogMsg) error { + if lm.Level > s.Level { + return nil + } + msg := s.Format(lm) + form := url.Values{} + form.Add("payload", msg) + + resp, err := http.PostForm(s.WebhookURL, form) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode) + } + return nil +} + +// Flush implementing method. empty. +func (s *SLACKWriter) Flush() { +} + +// Destroy implementing method. empty. +func (s *SLACKWriter) Destroy() { +} + +func init() { + Register(AdapterSlack, newSLACKWriter) +} diff --git a/logs/smtp.go b/core/logs/smtp.go similarity index 77% rename from logs/smtp.go rename to core/logs/smtp.go index 6208d7b8..40891a7c 100644 --- a/logs/smtp.go +++ b/core/logs/smtp.go @@ -21,7 +21,8 @@ import ( "net" "net/smtp" "strings" - "time" + + "github.com/pkg/errors" ) // SMTPWriter implements LoggerInterface and is used to send emails via given SMTP-server. @@ -33,11 +34,15 @@ type SMTPWriter struct { FromAddress string `json:"fromAddress"` RecipientAddresses []string `json:"sendTos"` Level int `json:"level"` + formatter LogFormatter + Formatter string `json:"formatter"` } -// NewSMTPWriter create smtp writer. +// NewSMTPWriter creates the smtp writer. func newSMTPWriter() Logger { - return &SMTPWriter{Level: LevelTrace} + res := &SMTPWriter{Level: LevelTrace} + res.formatter = res + return res } // Init smtp writer with json config. @@ -51,8 +56,16 @@ func newSMTPWriter() Logger { // "sendTos":["email1","email2"], // "level":LevelError // } -func (s *SMTPWriter) Init(jsonconfig string) error { - return json.Unmarshal([]byte(jsonconfig), s) +func (s *SMTPWriter) Init(config string) error { + res := json.Unmarshal([]byte(config), s) + if res == nil && len(s.Formatter) > 0 { + fmtr, ok := GetFormatter(s.Formatter) + if !ok { + return errors.New(fmt.Sprintf("the formatter with name: %s not found", s.Formatter)) + } + s.formatter = fmtr + } + return res } func (s *SMTPWriter) getSMTPAuth(host string) smtp.Auth { @@ -67,6 +80,10 @@ func (s *SMTPWriter) getSMTPAuth(host string) smtp.Auth { ) } +func (s *SMTPWriter) SetFormatter(f LogFormatter) { + s.formatter = f +} + func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error { client, err := smtp.Dial(hostAddressWithPort) if err != nil { @@ -115,10 +132,14 @@ func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAd return client.Quit() } -// WriteMsg write message in smtp writer. -// it will send an email with subject and only this message. -func (s *SMTPWriter) WriteMsg(when time.Time, msg string, level int) error { - if level > s.Level { +func (s *SMTPWriter) Format(lm *LogMsg) string { + return lm.OldStyleFormat() +} + +// WriteMsg writes message in smtp writer. +// Sends an email with subject and only this message. +func (s *SMTPWriter) WriteMsg(lm *LogMsg) error { + if lm.Level > s.Level { return nil } @@ -127,11 +148,13 @@ func (s *SMTPWriter) WriteMsg(when time.Time, msg string, level int) error { // Set up authentication information. auth := s.getSMTPAuth(hp[0]) + msg := s.Format(lm) + // Connect to the server, authenticate, set the sender and recipient, // and send the email all in one step. contentType := "Content-Type: text/plain" + "; charset=UTF-8" mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress + - ">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", when.Format("2006-01-02 15:04:05")) + msg) + ">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", lm.When.Format("2006-01-02 15:04:05")) + msg) return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg) } diff --git a/logs/smtp_test.go b/core/logs/smtp_test.go similarity index 100% rename from logs/smtp_test.go rename to core/logs/smtp_test.go diff --git a/utils/caller.go b/core/utils/caller.go similarity index 100% rename from utils/caller.go rename to core/utils/caller.go diff --git a/core/utils/caller_test.go b/core/utils/caller_test.go new file mode 100644 index 00000000..0675f0aa --- /dev/null +++ b/core/utils/caller_test.go @@ -0,0 +1,28 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 utils + +import ( + "strings" + "testing" +) + +func TestGetFuncName(t *testing.T) { + name := GetFuncName(TestGetFuncName) + t.Log(name) + if !strings.HasSuffix(name, ".TestGetFuncName") { + t.Error("get func name error") + } +} diff --git a/utils/debug.go b/core/utils/debug.go similarity index 100% rename from utils/debug.go rename to core/utils/debug.go diff --git a/core/utils/debug_test.go b/core/utils/debug_test.go new file mode 100644 index 00000000..efb8924e --- /dev/null +++ b/core/utils/debug_test.go @@ -0,0 +1,46 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 utils + +import ( + "testing" +) + +type mytype struct { + next *mytype + prev *mytype +} + +func TestPrint(t *testing.T) { + Display("v1", 1, "v2", 2, "v3", 3) +} + +func TestPrintPoint(t *testing.T) { + var v1 = new(mytype) + var v2 = new(mytype) + + v1.prev = nil + v1.next = v2 + + v2.prev = v1 + v2.next = nil + + Display("v1", v1, "v2", v2) +} + +func TestPrintString(t *testing.T) { + str := GetDisplayString("v1", 1, "v2", 2) + println(str) +} diff --git a/utils/file.go b/core/utils/file.go similarity index 100% rename from utils/file.go rename to core/utils/file.go diff --git a/utils/file_test.go b/core/utils/file_test.go similarity index 100% rename from utils/file_test.go rename to core/utils/file_test.go diff --git a/core/utils/kv.go b/core/utils/kv.go new file mode 100644 index 00000000..f4e6c4d4 --- /dev/null +++ b/core/utils/kv.go @@ -0,0 +1,87 @@ +// Copyright 2020 beego-dev +// +// Licensed 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 utils + +type KV interface { + GetKey() interface{} + GetValue() interface{} +} + +// SimpleKV is common structure to store key-value pairs. +// When you need something like Pair, you can use this +type SimpleKV struct { + Key interface{} + Value interface{} +} + +var _ KV = new(SimpleKV) + +func (s *SimpleKV) GetKey() interface{} { + return s.Key +} + +func (s *SimpleKV) GetValue() interface{} { + return s.Value +} + +// KVs interface +type KVs interface { + GetValueOr(key interface{}, defValue interface{}) interface{} + Contains(key interface{}) bool + IfContains(key interface{}, action func(value interface{})) KVs +} + +// SimpleKVs will store SimpleKV collection as map +type SimpleKVs struct { + kvs map[interface{}]interface{} +} + +var _ KVs = new(SimpleKVs) + +// GetValueOr returns the value for a given key, if non-existant +// it returns defValue +func (kvs *SimpleKVs) GetValueOr(key interface{}, defValue interface{}) interface{} { + v, ok := kvs.kvs[key] + if ok { + return v + } + return defValue +} + +// Contains checks if a key exists +func (kvs *SimpleKVs) Contains(key interface{}) bool { + _, ok := kvs.kvs[key] + return ok +} + +// IfContains invokes the action on a key if it exists +func (kvs *SimpleKVs) IfContains(key interface{}, action func(value interface{})) KVs { + v, ok := kvs.kvs[key] + if ok { + action(v) + } + return kvs +} + +// NewKVs creates the *KVs instance +func NewKVs(kvs ...KV) KVs { + res := &SimpleKVs{ + kvs: make(map[interface{}]interface{}, len(kvs)), + } + for _, kv := range kvs { + res.kvs[kv.GetKey()] = kv.GetValue() + } + return res +} diff --git a/core/utils/kv_test.go b/core/utils/kv_test.go new file mode 100644 index 00000000..4c9643dc --- /dev/null +++ b/core/utils/kv_test.go @@ -0,0 +1,38 @@ +// Copyright 2020 beego-dev +// +// Licensed 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 utils + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestKVs(t *testing.T) { + key := "my-key" + kvs := NewKVs(&SimpleKV{ + Key: key, + Value: 12, + }) + + assert.True(t, kvs.Contains(key)) + + v := kvs.GetValueOr(key, 13) + assert.Equal(t, 12, v) + + v = kvs.GetValueOr(`key-not-exists`, 8546) + assert.Equal(t, 8546, v) + +} diff --git a/utils/mail.go b/core/utils/mail.go similarity index 100% rename from utils/mail.go rename to core/utils/mail.go diff --git a/core/utils/mail_test.go b/core/utils/mail_test.go new file mode 100644 index 00000000..c38356a2 --- /dev/null +++ b/core/utils/mail_test.go @@ -0,0 +1,41 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 utils + +import "testing" + +func TestMail(t *testing.T) { + config := `{"username":"astaxie@gmail.com","password":"astaxie","host":"smtp.gmail.com","port":587}` + mail := NewEMail(config) + if mail.Username != "astaxie@gmail.com" { + t.Fatal("email parse get username error") + } + if mail.Password != "astaxie" { + t.Fatal("email parse get password error") + } + if mail.Host != "smtp.gmail.com" { + t.Fatal("email parse get host error") + } + if mail.Port != 587 { + t.Fatal("email parse get port error") + } + mail.To = []string{"xiemengjun@gmail.com"} + mail.From = "astaxie@gmail.com" + mail.Subject = "hi, just from beego!" + mail.Text = "Text Body is, of course, supported!" + mail.HTML = "

Fancy Html is supported, too!

" + mail.AttachFile("/Users/astaxie/github/beego/beego.go") + mail.Send() +} diff --git a/core/utils/pagination/doc.go b/core/utils/pagination/doc.go new file mode 100644 index 00000000..b9c604b9 --- /dev/null +++ b/core/utils/pagination/doc.go @@ -0,0 +1,58 @@ +/* +Package pagination provides utilities to setup a paginator within the +context of a http request. + +Usage + +In your beego.Controller: + + package controllers + + import "github.com/astaxie/beego/core/utils/pagination" + + type PostsController struct { + beego.Controller + } + + func (this *PostsController) ListAllPosts() { + // sets this.Data["paginator"] with the current offset (from the url query param) + postsPerPage := 20 + paginator := pagination.SetPaginator(this.Ctx, postsPerPage, CountPosts()) + + // fetch the next 20 posts + this.Data["posts"] = ListPostsByOffsetAndLimit(paginator.Offset(), postsPerPage) + } + + +In your view templates: + + {{if .paginator.HasPages}} + + {{end}} + +See also + +http://beego.me/docs/mvc/view/page.md + +*/ +package pagination diff --git a/utils/pagination/paginator.go b/core/utils/pagination/paginator.go similarity index 100% rename from utils/pagination/paginator.go rename to core/utils/pagination/paginator.go diff --git a/utils/pagination/utils.go b/core/utils/pagination/utils.go similarity index 100% rename from utils/pagination/utils.go rename to core/utils/pagination/utils.go diff --git a/utils/rand.go b/core/utils/rand.go similarity index 100% rename from utils/rand.go rename to core/utils/rand.go diff --git a/core/utils/rand_test.go b/core/utils/rand_test.go new file mode 100644 index 00000000..6c238b5e --- /dev/null +++ b/core/utils/rand_test.go @@ -0,0 +1,33 @@ +// Copyright 2016 beego Author. All Rights Reserved. +// +// Licensed 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 utils + +import "testing" + +func TestRand_01(t *testing.T) { + bs0 := RandomCreateBytes(16) + bs1 := RandomCreateBytes(16) + + t.Log(string(bs0), string(bs1)) + if string(bs0) == string(bs1) { + t.FailNow() + } + + bs0 = RandomCreateBytes(4, []byte(`a`)...) + + if string(bs0) != "aaaa" { + t.FailNow() + } +} diff --git a/utils/safemap.go b/core/utils/safemap.go similarity index 100% rename from utils/safemap.go rename to core/utils/safemap.go diff --git a/core/utils/safemap_test.go b/core/utils/safemap_test.go new file mode 100644 index 00000000..65085195 --- /dev/null +++ b/core/utils/safemap_test.go @@ -0,0 +1,89 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 utils + +import "testing" + +var safeMap *BeeMap + +func TestNewBeeMap(t *testing.T) { + safeMap = NewBeeMap() + if safeMap == nil { + t.Fatal("expected to return non-nil BeeMap", "got", safeMap) + } +} + +func TestSet(t *testing.T) { + safeMap = NewBeeMap() + if ok := safeMap.Set("astaxie", 1); !ok { + t.Error("expected", true, "got", false) + } +} + +func TestReSet(t *testing.T) { + safeMap := NewBeeMap() + if ok := safeMap.Set("astaxie", 1); !ok { + t.Error("expected", true, "got", false) + } + // set diff value + if ok := safeMap.Set("astaxie", -1); !ok { + t.Error("expected", true, "got", false) + } + + // set same value + if ok := safeMap.Set("astaxie", -1); ok { + t.Error("expected", false, "got", true) + } +} + +func TestCheck(t *testing.T) { + if exists := safeMap.Check("astaxie"); !exists { + t.Error("expected", true, "got", false) + } +} + +func TestGet(t *testing.T) { + if val := safeMap.Get("astaxie"); val.(int) != 1 { + t.Error("expected value", 1, "got", val) + } +} + +func TestDelete(t *testing.T) { + safeMap.Delete("astaxie") + if exists := safeMap.Check("astaxie"); exists { + t.Error("expected element to be deleted") + } +} + +func TestItems(t *testing.T) { + safeMap := NewBeeMap() + safeMap.Set("astaxie", "hello") + for k, v := range safeMap.Items() { + key := k.(string) + value := v.(string) + if key != "astaxie" { + t.Error("expected the key should be astaxie") + } + if value != "hello" { + t.Error("expected the value should be hello") + } + } +} + +func TestCount(t *testing.T) { + if count := safeMap.Count(); count != 0 { + t.Error("expected count to be", 0, "got", count) + } +} diff --git a/utils/slice.go b/core/utils/slice.go similarity index 100% rename from utils/slice.go rename to core/utils/slice.go diff --git a/core/utils/slice_test.go b/core/utils/slice_test.go new file mode 100644 index 00000000..142dec96 --- /dev/null +++ b/core/utils/slice_test.go @@ -0,0 +1,29 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 utils + +import ( + "testing" +) + +func TestInSlice(t *testing.T) { + sl := []string{"A", "b"} + if !InSlice("A", sl) { + t.Error("should be true") + } + if InSlice("B", sl) { + t.Error("should be false") + } +} diff --git a/utils/testdata/grepe.test b/core/utils/testdata/grepe.test similarity index 100% rename from utils/testdata/grepe.test rename to core/utils/testdata/grepe.test diff --git a/core/utils/time.go b/core/utils/time.go new file mode 100644 index 00000000..579b292a --- /dev/null +++ b/core/utils/time.go @@ -0,0 +1,48 @@ +// Copyright 2020 +// +// Licensed 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 utils + +import ( + "fmt" + "time" +) + +// short string format +func ToShortTimeFormat(d time.Duration) string { + + u := uint64(d) + if u < uint64(time.Second) { + switch { + case u == 0: + return "0" + case u < uint64(time.Microsecond): + return fmt.Sprintf("%.2fns", float64(u)) + case u < uint64(time.Millisecond): + return fmt.Sprintf("%.2fus", float64(u)/1000) + default: + return fmt.Sprintf("%.2fms", float64(u)/1000/1000) + } + } else { + switch { + case u < uint64(time.Minute): + return fmt.Sprintf("%.2fs", float64(u)/1000/1000/1000) + case u < uint64(time.Hour): + return fmt.Sprintf("%.2fm", float64(u)/1000/1000/1000/60) + default: + return fmt.Sprintf("%.2fh", float64(u)/1000/1000/1000/60/60) + } + } + +} diff --git a/utils/utils.go b/core/utils/utils.go similarity index 100% rename from utils/utils.go rename to core/utils/utils.go diff --git a/utils/utils_test.go b/core/utils/utils_test.go similarity index 100% rename from utils/utils_test.go rename to core/utils/utils_test.go diff --git a/validation/README.md b/core/validation/README.md similarity index 100% rename from validation/README.md rename to core/validation/README.md diff --git a/validation/util.go b/core/validation/util.go similarity index 99% rename from validation/util.go rename to core/validation/util.go index 82206f4f..918b206c 100644 --- a/validation/util.go +++ b/core/validation/util.go @@ -213,7 +213,7 @@ func parseFunc(vfunc, key string, label string) (v ValidFunc, err error) { return } - tParams, err := trim(name, key+"."+ name + "." + label, params) + tParams, err := trim(name, key+"."+name+"."+label, params) if err != nil { return } diff --git a/validation/util_test.go b/core/validation/util_test.go similarity index 100% rename from validation/util_test.go rename to core/validation/util_test.go diff --git a/validation/validation.go b/core/validation/validation.go similarity index 99% rename from validation/validation.go rename to core/validation/validation.go index 190e0f0e..134e750e 100644 --- a/validation/validation.go +++ b/core/validation/validation.go @@ -269,6 +269,11 @@ func (v *Validation) apply(chk Validator, obj interface{}) *Result { Field := "" Label := "" parts := strings.Split(key, ".") + if len(parts) == 2 { + Field = parts[0] + Name = parts[1] + Label = Field + } if len(parts) == 3 { Field = parts[0] Name = parts[1] diff --git a/core/validation/validation_test.go b/core/validation/validation_test.go new file mode 100644 index 00000000..bca4f560 --- /dev/null +++ b/core/validation/validation_test.go @@ -0,0 +1,634 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 validation + +import ( + "regexp" + "testing" + "time" +) + +func TestRequired(t *testing.T) { + valid := Validation{} + + if valid.Required(nil, "nil").Ok { + t.Error("nil object should be false") + } + if !valid.Required(true, "bool").Ok { + t.Error("Bool value should always return true") + } + if !valid.Required(false, "bool").Ok { + t.Error("Bool value should always return true") + } + if valid.Required("", "string").Ok { + t.Error("\"'\" string should be false") + } + if valid.Required(" ", "string").Ok { + t.Error("\" \" string should be false") // For #2361 + } + if valid.Required("\n", "string").Ok { + t.Error("new line string should be false") // For #2361 + } + if !valid.Required("astaxie", "string").Ok { + t.Error("string should be true") + } + if valid.Required(0, "zero").Ok { + t.Error("Integer should not be equal 0") + } + if !valid.Required(1, "int").Ok { + t.Error("Integer except 0 should be true") + } + if !valid.Required(time.Now(), "time").Ok { + t.Error("time should be true") + } + if valid.Required([]string{}, "emptySlice").Ok { + t.Error("empty slice should be false") + } + if !valid.Required([]interface{}{"ok"}, "slice").Ok { + t.Error("slice should be true") + } +} + +func TestMin(t *testing.T) { + valid := Validation{} + + if valid.Min(-1, 0, "min0").Ok { + t.Error("-1 is less than the minimum value of 0 should be false") + } + if !valid.Min(1, 0, "min0").Ok { + t.Error("1 is greater or equal than the minimum value of 0 should be true") + } +} + +func TestMax(t *testing.T) { + valid := Validation{} + + if valid.Max(1, 0, "max0").Ok { + t.Error("1 is greater than the minimum value of 0 should be false") + } + if !valid.Max(-1, 0, "max0").Ok { + t.Error("-1 is less or equal than the maximum value of 0 should be true") + } +} + +func TestRange(t *testing.T) { + valid := Validation{} + + if valid.Range(-1, 0, 1, "range0_1").Ok { + t.Error("-1 is between 0 and 1 should be false") + } + if !valid.Range(1, 0, 1, "range0_1").Ok { + t.Error("1 is between 0 and 1 should be true") + } +} + +func TestMinSize(t *testing.T) { + valid := Validation{} + + if valid.MinSize("", 1, "minSize1").Ok { + t.Error("the length of \"\" is less than the minimum value of 1 should be false") + } + if !valid.MinSize("ok", 1, "minSize1").Ok { + t.Error("the length of \"ok\" is greater or equal than the minimum value of 1 should be true") + } + if valid.MinSize([]string{}, 1, "minSize1").Ok { + t.Error("the length of empty slice is less than the minimum value of 1 should be false") + } + if !valid.MinSize([]interface{}{"ok"}, 1, "minSize1").Ok { + t.Error("the length of [\"ok\"] is greater or equal than the minimum value of 1 should be true") + } +} + +func TestMaxSize(t *testing.T) { + valid := Validation{} + + if valid.MaxSize("ok", 1, "maxSize1").Ok { + t.Error("the length of \"ok\" is greater than the maximum value of 1 should be false") + } + if !valid.MaxSize("", 1, "maxSize1").Ok { + t.Error("the length of \"\" is less or equal than the maximum value of 1 should be true") + } + if valid.MaxSize([]interface{}{"ok", false}, 1, "maxSize1").Ok { + t.Error("the length of [\"ok\", false] is greater than the maximum value of 1 should be false") + } + if !valid.MaxSize([]string{}, 1, "maxSize1").Ok { + t.Error("the length of empty slice is less or equal than the maximum value of 1 should be true") + } +} + +func TestLength(t *testing.T) { + valid := Validation{} + + if valid.Length("", 1, "length1").Ok { + t.Error("the length of \"\" must equal 1 should be false") + } + if !valid.Length("1", 1, "length1").Ok { + t.Error("the length of \"1\" must equal 1 should be true") + } + if valid.Length([]string{}, 1, "length1").Ok { + t.Error("the length of empty slice must equal 1 should be false") + } + if !valid.Length([]interface{}{"ok"}, 1, "length1").Ok { + t.Error("the length of [\"ok\"] must equal 1 should be true") + } +} + +func TestAlpha(t *testing.T) { + valid := Validation{} + + if valid.Alpha("a,1-@ $", "alpha").Ok { + t.Error("\"a,1-@ $\" are valid alpha characters should be false") + } + if !valid.Alpha("abCD", "alpha").Ok { + t.Error("\"abCD\" are valid alpha characters should be true") + } +} + +func TestNumeric(t *testing.T) { + valid := Validation{} + + if valid.Numeric("a,1-@ $", "numeric").Ok { + t.Error("\"a,1-@ $\" are valid numeric characters should be false") + } + if !valid.Numeric("1234", "numeric").Ok { + t.Error("\"1234\" are valid numeric characters should be true") + } +} + +func TestAlphaNumeric(t *testing.T) { + valid := Validation{} + + if valid.AlphaNumeric("a,1-@ $", "alphaNumeric").Ok { + t.Error("\"a,1-@ $\" are valid alpha or numeric characters should be false") + } + if !valid.AlphaNumeric("1234aB", "alphaNumeric").Ok { + t.Error("\"1234aB\" are valid alpha or numeric characters should be true") + } +} + +func TestMatch(t *testing.T) { + valid := Validation{} + + if valid.Match("suchuangji@gmail", regexp.MustCompile(`^\w+@\w+\.\w+$`), "match").Ok { + t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be false") + } + if !valid.Match("suchuangji@gmail.com", regexp.MustCompile(`^\w+@\w+\.\w+$`), "match").Ok { + t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be true") + } +} + +func TestNoMatch(t *testing.T) { + valid := Validation{} + + if valid.NoMatch("123@gmail", regexp.MustCompile(`[^\w\d]`), "nomatch").Ok { + t.Error("\"123@gmail\" not match \"[^\\w\\d]\" should be false") + } + if !valid.NoMatch("123gmail", regexp.MustCompile(`[^\w\d]`), "match").Ok { + t.Error("\"123@gmail\" not match \"[^\\w\\d@]\" should be true") + } +} + +func TestAlphaDash(t *testing.T) { + valid := Validation{} + + if valid.AlphaDash("a,1-@ $", "alphaDash").Ok { + t.Error("\"a,1-@ $\" are valid alpha or numeric or dash(-_) characters should be false") + } + if !valid.AlphaDash("1234aB-_", "alphaDash").Ok { + t.Error("\"1234aB\" are valid alpha or numeric or dash(-_) characters should be true") + } +} + +func TestEmail(t *testing.T) { + valid := Validation{} + + if valid.Email("not@a email", "email").Ok { + t.Error("\"not@a email\" is a valid email address should be false") + } + if !valid.Email("suchuangji@gmail.com", "email").Ok { + t.Error("\"suchuangji@gmail.com\" is a valid email address should be true") + } + if valid.Email("@suchuangji@gmail.com", "email").Ok { + t.Error("\"@suchuangji@gmail.com\" is a valid email address should be false") + } + if valid.Email("suchuangji@gmail.com ok", "email").Ok { + t.Error("\"suchuangji@gmail.com ok\" is a valid email address should be false") + } +} + +func TestIP(t *testing.T) { + valid := Validation{} + + if valid.IP("11.255.255.256", "IP").Ok { + t.Error("\"11.255.255.256\" is a valid ip address should be false") + } + if !valid.IP("01.11.11.11", "IP").Ok { + t.Error("\"suchuangji@gmail.com\" is a valid ip address should be true") + } +} + +func TestBase64(t *testing.T) { + valid := Validation{} + + if valid.Base64("suchuangji@gmail.com", "base64").Ok { + t.Error("\"suchuangji@gmail.com\" are a valid base64 characters should be false") + } + if !valid.Base64("c3VjaHVhbmdqaUBnbWFpbC5jb20=", "base64").Ok { + t.Error("\"c3VjaHVhbmdqaUBnbWFpbC5jb20=\" are a valid base64 characters should be true") + } +} + +func TestMobile(t *testing.T) { + valid := Validation{} + + validMobiles := []string{ + "19800008888", + "18800008888", + "18000008888", + "8618300008888", + "+8614700008888", + "17300008888", + "+8617100008888", + "8617500008888", + "8617400008888", + "16200008888", + "16500008888", + "16600008888", + "16700008888", + "13300008888", + "14900008888", + "15300008888", + "17300008888", + "17700008888", + "18000008888", + "18900008888", + "19100008888", + "19900008888", + "19300008888", + "13000008888", + "13100008888", + "13200008888", + "14500008888", + "15500008888", + "15600008888", + "16600008888", + "17100008888", + "17500008888", + "17600008888", + "18500008888", + "18600008888", + "13400008888", + "13500008888", + "13600008888", + "13700008888", + "13800008888", + "13900008888", + "14700008888", + "15000008888", + "15100008888", + "15200008888", + "15800008888", + "15900008888", + "17200008888", + "17800008888", + "18200008888", + "18300008888", + "18400008888", + "18700008888", + "18800008888", + "19800008888", + } + + for _, m := range validMobiles { + if !valid.Mobile(m, "mobile").Ok { + t.Error(m + " is a valid mobile phone number should be true") + } + } +} + +func TestTel(t *testing.T) { + valid := Validation{} + + if valid.Tel("222-00008888", "telephone").Ok { + t.Error("\"222-00008888\" is a valid telephone number should be false") + } + if !valid.Tel("022-70008888", "telephone").Ok { + t.Error("\"022-70008888\" is a valid telephone number should be true") + } + if !valid.Tel("02270008888", "telephone").Ok { + t.Error("\"02270008888\" is a valid telephone number should be true") + } + if !valid.Tel("70008888", "telephone").Ok { + t.Error("\"70008888\" is a valid telephone number should be true") + } +} + +func TestPhone(t *testing.T) { + valid := Validation{} + + if valid.Phone("222-00008888", "phone").Ok { + t.Error("\"222-00008888\" is a valid phone number should be false") + } + if !valid.Mobile("+8614700008888", "phone").Ok { + t.Error("\"+8614700008888\" is a valid phone number should be true") + } + if !valid.Tel("02270008888", "phone").Ok { + t.Error("\"02270008888\" is a valid phone number should be true") + } +} + +func TestZipCode(t *testing.T) { + valid := Validation{} + + if valid.ZipCode("", "zipcode").Ok { + t.Error("\"00008888\" is a valid zipcode should be false") + } + if !valid.ZipCode("536000", "zipcode").Ok { + t.Error("\"536000\" is a valid zipcode should be true") + } +} + +func TestValid(t *testing.T) { + type user struct { + ID int + Name string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"` + Age int `valid:"Required;Range(1, 140)"` + } + valid := Validation{} + + u := user{Name: "test@/test/;com", Age: 40} + b, err := valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if !b { + t.Error("validation should be passed") + } + + uptr := &user{Name: "test", Age: 40} + valid.Clear() + b, err = valid.Valid(uptr) + if err != nil { + t.Fatal(err) + } + if b { + t.Error("validation should not be passed") + } + if len(valid.Errors) != 1 { + t.Fatalf("valid errors len should be 1 but got %d", len(valid.Errors)) + } + if valid.Errors[0].Key != "Name.Match" { + t.Errorf("Message key should be `Name.Match` but got %s", valid.Errors[0].Key) + } + + u = user{Name: "test@/test/;com", Age: 180} + valid.Clear() + b, err = valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Error("validation should not be passed") + } + if len(valid.Errors) != 1 { + t.Fatalf("valid errors len should be 1 but got %d", len(valid.Errors)) + } + if valid.Errors[0].Key != "Age.Range." { + t.Errorf("Message key should be `Age.Range` but got %s", valid.Errors[0].Key) + } +} + +func TestRecursiveValid(t *testing.T) { + type User struct { + ID int + Name string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"` + Age int `valid:"Required;Range(1, 140)"` + } + + type AnonymouseUser struct { + ID2 int + Name2 string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"` + Age2 int `valid:"Required;Range(1, 140)"` + } + + type Account struct { + Password string `valid:"Required"` + U User + AnonymouseUser + } + valid := Validation{} + + u := Account{Password: "abc123_", U: User{}} + b, err := valid.RecursiveValid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Error("validation should not be passed") + } +} + +func TestSkipValid(t *testing.T) { + type User struct { + ID int + + Email string `valid:"Email"` + ReqEmail string `valid:"Required;Email"` + + IP string `valid:"IP"` + ReqIP string `valid:"Required;IP"` + + Mobile string `valid:"Mobile"` + ReqMobile string `valid:"Required;Mobile"` + + Tel string `valid:"Tel"` + ReqTel string `valid:"Required;Tel"` + + Phone string `valid:"Phone"` + ReqPhone string `valid:"Required;Phone"` + + ZipCode string `valid:"ZipCode"` + ReqZipCode string `valid:"Required;ZipCode"` + } + + u := User{ + ReqEmail: "a@a.com", + ReqIP: "127.0.0.1", + ReqMobile: "18888888888", + ReqTel: "02088888888", + ReqPhone: "02088888888", + ReqZipCode: "510000", + } + + valid := Validation{} + b, err := valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Fatal("validation should not be passed") + } + + valid = Validation{RequiredFirst: true} + b, err = valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if !b { + t.Fatal("validation should be passed") + } +} + +func TestPointer(t *testing.T) { + type User struct { + ID int + + Email *string `valid:"Email"` + ReqEmail *string `valid:"Required;Email"` + } + + u := User{ + ReqEmail: nil, + Email: nil, + } + + valid := Validation{} + b, err := valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Fatal("validation should not be passed") + } + + validEmail := "a@a.com" + u = User{ + ReqEmail: &validEmail, + Email: nil, + } + + valid = Validation{RequiredFirst: true} + b, err = valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if !b { + t.Fatal("validation should be passed") + } + + u = User{ + ReqEmail: &validEmail, + Email: nil, + } + + valid = Validation{} + b, err = valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Fatal("validation should not be passed") + } + + invalidEmail := "a@a" + u = User{ + ReqEmail: &validEmail, + Email: &invalidEmail, + } + + valid = Validation{RequiredFirst: true} + b, err = valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Fatal("validation should not be passed") + } + + u = User{ + ReqEmail: &validEmail, + Email: &invalidEmail, + } + + valid = Validation{} + b, err = valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Fatal("validation should not be passed") + } +} + +func TestCanSkipAlso(t *testing.T) { + type User struct { + ID int + + Email string `valid:"Email"` + ReqEmail string `valid:"Required;Email"` + MatchRange int `valid:"Range(10, 20)"` + } + + u := User{ + ReqEmail: "a@a.com", + Email: "", + MatchRange: 0, + } + + valid := Validation{RequiredFirst: true} + b, err := valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Fatal("validation should not be passed") + } + + valid = Validation{RequiredFirst: true} + valid.CanSkipAlso("Range") + b, err = valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if !b { + t.Fatal("validation should be passed") + } + +} + +func TestFieldNoEmpty(t *testing.T) { + type User struct { + Name string `json:"name" valid:"Match(/^[a-zA-Z][a-zA-Z0-9._-]{0,31}$/)"` + } + u := User{ + Name: "*", + } + + valid := Validation{} + b, err := valid.Valid(u) + if err != nil { + t.Fatal(err) + } + if b { + t.Fatal("validation should be passed") + } + if len(valid.Errors) == 0 { + t.Fatal("validation should be passed") + } + validErr := valid.Errors[0] + if len(validErr.Field) == 0 { + t.Fatal("validation should be passed") + } +} diff --git a/validation/validators.go b/core/validation/validators.go similarity index 99% rename from validation/validators.go rename to core/validation/validators.go index 38b6f1aa..ec422d86 100644 --- a/validation/validators.go +++ b/core/validation/validators.go @@ -16,13 +16,14 @@ package validation import ( "fmt" - "github.com/astaxie/beego/logs" "reflect" "regexp" "strings" "sync" "time" "unicode/utf8" + + "github.com/astaxie/beego/core/logs" ) // CanSkipFuncs will skip valid if RequiredFirst is true and the struct field's value is empty diff --git a/doc.go b/doc.go index 8825bd29..6975885a 100644 --- a/doc.go +++ b/doc.go @@ -1,17 +1,15 @@ -/* -Package beego provide a MVC framework -beego: an open-source, high-performance, modular, full-stack web framework +// Copyright 2020 +// +// Licensed 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. -It is used for rapid development of RESTful APIs, web apps and backend services in Go. -beego is inspired by Tornado, Sinatra and Flask with the added benefit of some Go-specific features such as interfaces and struct embedding. - - package main - import "github.com/astaxie/beego" - - func main() { - beego.Run() - } - -more information: http://beego.me -*/ package beego diff --git a/go.mod b/go.mod index ec500f51..7527aa47 100644 --- a/go.mod +++ b/go.mod @@ -7,34 +7,53 @@ require ( github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 github.com/casbin/casbin v1.7.0 github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 + github.com/coreos/etcd v3.3.25+incompatible + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect + github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808 // indirect github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a // indirect github.com/elastic/go-elasticsearch/v6 v6.8.5 github.com/elazarl/go-bindata-assetfs v1.0.0 + github.com/go-kit/kit v0.9.0 github.com/go-redis/redis v6.14.2+incompatible + github.com/go-redis/redis/v7 v7.4.0 github.com/go-sql-driver/mysql v1.5.0 - github.com/gogo/protobuf v1.1.1 + github.com/gogo/protobuf v1.3.1 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect github.com/gomodule/redigo v2.0.0+incompatible + github.com/google/go-cmp v0.5.0 // indirect + github.com/google/uuid v1.1.1 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/hashicorp/golang-lru v0.5.4 github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6 github.com/lib/pq v1.0.0 github.com/mattn/go-sqlite3 v2.0.3+incompatible - github.com/pelletier/go-toml v1.2.0 // indirect + github.com/mitchellh/mapstructure v1.3.3 + github.com/opentracing/opentracing-go v1.2.0 + github.com/pelletier/go-toml v1.8.1 + github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.7.0 github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c // indirect github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b // indirect - golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 + go.etcd.io/etcd v3.3.25+incompatible // indirect + go.uber.org/zap v1.15.0 // indirect + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect + golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 // indirect + golang.org/x/text v0.3.3 // indirect golang.org/x/tools v0.0.0-20200117065230-39095c1d176c + google.golang.org/grpc v1.26.0 gopkg.in/yaml.v2 v2.2.8 + honnef.co/go/tools v0.0.1-2020.1.5 // indirect ) replace golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 => github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85 replace gopkg.in/yaml.v2 v2.2.1 => github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d -go 1.13 +go 1.14 diff --git a/go.sum b/go.sum index c7b861ac..994d1ec4 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= @@ -20,10 +21,22 @@ github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVx github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= github.com/casbin/casbin v1.7.0 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ= github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/coreos/etcd v0.5.0-alpha.5 h1:0Qi6Jzjk2CDuuGlIeecpu+em2nrjhOgz2wsIwCmQHmc= +github.com/coreos/etcd v3.3.25+incompatible h1:0GQEw6h3YnuOVdtwygkIfJ+Omx0tZ8/QkVyXI4LkbeY= +github.com/coreos/etcd v3.3.25+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d h1:OMrhQqj1QCyDT2sxHCDjE+k8aMdn2ngTCGG7g4wrdLo= github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U= github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808 h1:8s2l8TVUwMXl6tZMe3+hPCRJ25nQXiA3d1x622JtOqc= @@ -41,30 +54,45 @@ github.com/elastic/go-elasticsearch/v6 v6.8.5 h1:U2HtkBseC1FNBmDr0TR2tKltL6FxoY+ github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0= github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= +github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d h1:xy93KVe+KrIIwWDEAfQBdIfsiHJkepbYsDr+VY3g9/o= github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -72,11 +100,18 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pO github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -84,7 +119,10 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -98,6 +136,8 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -106,19 +146,26 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= -github.com/pingcap/tidb v2.0.11+incompatible/go.mod h1:I8C6jrPINP2rrVunTRd7C9fRRhQrtR43S1/CL5ix/yQ= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -127,6 +174,7 @@ github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -136,6 +184,7 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo= github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0 h1:QIF48X1cihydXibm+4wfAc0r/qyPyuFiPFRNphdMpEE= @@ -159,52 +208,137 @@ github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2K github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b h1:0Ve0/CCjiAiyKddUMUn3RwIGlq2iTW4GuVzyoKBYO/8= github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU= +go.etcd.io/etcd v0.5.0-alpha.5 h1:VOolFSo3XgsmnYDLozjvZ6JL6AAwIDu1Yx1y+4EYLDo= +go.etcd.io/etcd v3.3.25+incompatible h1:V1RzkZJj9LqsJRy+TUBgpWSbZXITLB819lstuTFoZOY= +go.etcd.io/etcd v3.3.25+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= +go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4= +golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200117065230-39095c1d176c h1:FodBYPZKH5tAN2O60HlglMwXGAeV/4k+NKbli79M/2c= golang.org/x/tools v0.0.0-20200117065230-39095c1d176c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200815165600-90abf76919f3 h1:0aScV/0rLmANzEYIhjCOi2pTvDyhZNduBUMD2q3iqs4= +golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1 h1:SfXqXS5hkufcdZ/mHtYCh53P2b+92WQq/DZcKLgsFRs= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= @@ -215,3 +349,9 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.5 h1:nI5egYTGJakVyOryqLs1cQO5dO0ksin5XXs2pspk75k= +honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= diff --git a/logs/slack.go b/logs/slack.go deleted file mode 100644 index 1cd2e5ae..00000000 --- a/logs/slack.go +++ /dev/null @@ -1,60 +0,0 @@ -package logs - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - "time" -) - -// SLACKWriter implements beego LoggerInterface and is used to send jiaoliao webhook -type SLACKWriter struct { - WebhookURL string `json:"webhookurl"` - Level int `json:"level"` -} - -// newSLACKWriter create jiaoliao writer. -func newSLACKWriter() Logger { - return &SLACKWriter{Level: LevelTrace} -} - -// Init SLACKWriter with json config string -func (s *SLACKWriter) Init(jsonconfig string) error { - return json.Unmarshal([]byte(jsonconfig), s) -} - -// WriteMsg write message in smtp writer. -// it will send an email with subject and only this message. -func (s *SLACKWriter) WriteMsg(when time.Time, msg string, level int) error { - if level > s.Level { - return nil - } - - text := fmt.Sprintf("{\"text\": \"%s %s\"}", when.Format("2006-01-02 15:04:05"), msg) - - form := url.Values{} - form.Add("payload", text) - - resp, err := http.PostForm(s.WebhookURL, form) - if err != nil { - return err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode) - } - return nil -} - -// Flush implementing method. empty. -func (s *SLACKWriter) Flush() { -} - -// Destroy implementing method. empty. -func (s *SLACKWriter) Destroy() { -} - -func init() { - Register(AdapterSlack, newSLACKWriter) -} diff --git a/orm/cmd_utils.go b/orm/cmd_utils.go deleted file mode 100644 index 61f17346..00000000 --- a/orm/cmd_utils.go +++ /dev/null @@ -1,320 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "fmt" - "os" - "strings" -) - -type dbIndex struct { - Table string - Name string - SQL string -} - -// create database drop sql. -func getDbDropSQL(al *alias) (sqls []string) { - if len(modelCache.cache) == 0 { - fmt.Println("no Model found, need register your model") - os.Exit(2) - } - - Q := al.DbBaser.TableQuote() - - for _, mi := range modelCache.allOrdered() { - sqls = append(sqls, fmt.Sprintf(`DROP TABLE IF EXISTS %s%s%s`, Q, mi.table, Q)) - } - return sqls -} - -// get database column type string. -func getColumnTyp(al *alias, fi *fieldInfo) (col string) { - T := al.DbBaser.DbTypes() - fieldType := fi.fieldType - fieldSize := fi.size - -checkColumn: - switch fieldType { - case TypeBooleanField: - col = T["bool"] - case TypeVarCharField: - if al.Driver == DRPostgres && fi.toText { - col = T["string-text"] - } else { - col = fmt.Sprintf(T["string"], fieldSize) - } - case TypeCharField: - col = fmt.Sprintf(T["string-char"], fieldSize) - case TypeTextField: - col = T["string-text"] - case TypeTimeField: - col = T["time.Time-clock"] - case TypeDateField: - col = T["time.Time-date"] - case TypeDateTimeField: - col = T["time.Time"] - case TypeBitField: - col = T["int8"] - case TypeSmallIntegerField: - col = T["int16"] - case TypeIntegerField: - col = T["int32"] - case TypeBigIntegerField: - if al.Driver == DRSqlite { - fieldType = TypeIntegerField - goto checkColumn - } - col = T["int64"] - case TypePositiveBitField: - col = T["uint8"] - case TypePositiveSmallIntegerField: - col = T["uint16"] - case TypePositiveIntegerField: - col = T["uint32"] - case TypePositiveBigIntegerField: - col = T["uint64"] - case TypeFloatField: - col = T["float64"] - case TypeDecimalField: - s := T["float64-decimal"] - if !strings.Contains(s, "%d") { - col = s - } else { - col = fmt.Sprintf(s, fi.digits, fi.decimals) - } - case TypeJSONField: - if al.Driver != DRPostgres { - fieldType = TypeVarCharField - goto checkColumn - } - col = T["json"] - case TypeJsonbField: - if al.Driver != DRPostgres { - fieldType = TypeVarCharField - goto checkColumn - } - col = T["jsonb"] - case RelForeignKey, RelOneToOne: - fieldType = fi.relModelInfo.fields.pk.fieldType - fieldSize = fi.relModelInfo.fields.pk.size - goto checkColumn - } - - return -} - -// create alter sql string. -func getColumnAddQuery(al *alias, fi *fieldInfo) string { - Q := al.DbBaser.TableQuote() - typ := getColumnTyp(al, fi) - - if !fi.null { - typ += " " + "NOT NULL" - } - - return fmt.Sprintf("ALTER TABLE %s%s%s ADD COLUMN %s%s%s %s %s", - Q, fi.mi.table, Q, - Q, fi.column, Q, - typ, getColumnDefault(fi), - ) -} - -// create database creation string. -func getDbCreateSQL(al *alias) (sqls []string, tableIndexes map[string][]dbIndex) { - if len(modelCache.cache) == 0 { - fmt.Println("no Model found, need register your model") - os.Exit(2) - } - - Q := al.DbBaser.TableQuote() - T := al.DbBaser.DbTypes() - sep := fmt.Sprintf("%s, %s", Q, Q) - - tableIndexes = make(map[string][]dbIndex) - - for _, mi := range modelCache.allOrdered() { - sql := fmt.Sprintf("-- %s\n", strings.Repeat("-", 50)) - sql += fmt.Sprintf("-- Table Structure for `%s`\n", mi.fullName) - sql += fmt.Sprintf("-- %s\n", strings.Repeat("-", 50)) - - sql += fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s%s%s (\n", Q, mi.table, Q) - - columns := make([]string, 0, len(mi.fields.fieldsDB)) - - sqlIndexes := [][]string{} - - for _, fi := range mi.fields.fieldsDB { - - column := fmt.Sprintf(" %s%s%s ", Q, fi.column, Q) - col := getColumnTyp(al, fi) - - if fi.auto { - switch al.Driver { - case DRSqlite, DRPostgres: - column += T["auto"] - default: - column += col + " " + T["auto"] - } - } else if fi.pk { - column += col + " " + T["pk"] - } else { - column += col - - if !fi.null { - column += " " + "NOT NULL" - } - - //if fi.initial.String() != "" { - // column += " DEFAULT " + fi.initial.String() - //} - - // Append attribute DEFAULT - column += getColumnDefault(fi) - - if fi.unique { - column += " " + "UNIQUE" - } - - if fi.index { - sqlIndexes = append(sqlIndexes, []string{fi.column}) - } - } - - if strings.Contains(column, "%COL%") { - column = strings.Replace(column, "%COL%", fi.column, -1) - } - - if fi.description != "" && al.Driver!=DRSqlite { - column += " " + fmt.Sprintf("COMMENT '%s'",fi.description) - } - - columns = append(columns, column) - } - - if mi.model != nil { - allnames := getTableUnique(mi.addrField) - if !mi.manual && len(mi.uniques) > 0 { - allnames = append(allnames, mi.uniques) - } - for _, names := range allnames { - cols := make([]string, 0, len(names)) - for _, name := range names { - if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol { - cols = append(cols, fi.column) - } else { - panic(fmt.Errorf("cannot found column `%s` when parse UNIQUE in `%s.TableUnique`", name, mi.fullName)) - } - } - column := fmt.Sprintf(" UNIQUE (%s%s%s)", Q, strings.Join(cols, sep), Q) - columns = append(columns, column) - } - } - - sql += strings.Join(columns, ",\n") - sql += "\n)" - - if al.Driver == DRMySQL { - var engine string - if mi.model != nil { - engine = getTableEngine(mi.addrField) - } - if engine == "" { - engine = al.Engine - } - sql += " ENGINE=" + engine - } - - sql += ";" - sqls = append(sqls, sql) - - if mi.model != nil { - for _, names := range getTableIndex(mi.addrField) { - cols := make([]string, 0, len(names)) - for _, name := range names { - if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol { - cols = append(cols, fi.column) - } else { - panic(fmt.Errorf("cannot found column `%s` when parse INDEX in `%s.TableIndex`", name, mi.fullName)) - } - } - sqlIndexes = append(sqlIndexes, cols) - } - } - - for _, names := range sqlIndexes { - name := mi.table + "_" + strings.Join(names, "_") - cols := strings.Join(names, sep) - sql := fmt.Sprintf("CREATE INDEX %s%s%s ON %s%s%s (%s%s%s);", Q, name, Q, Q, mi.table, Q, Q, cols, Q) - - index := dbIndex{} - index.Table = mi.table - index.Name = name - index.SQL = sql - - tableIndexes[mi.table] = append(tableIndexes[mi.table], index) - } - - } - - return -} - -// Get string value for the attribute "DEFAULT" for the CREATE, ALTER commands -func getColumnDefault(fi *fieldInfo) string { - var ( - v, t, d string - ) - - // Skip default attribute if field is in relations - if fi.rel || fi.reverse { - return v - } - - t = " DEFAULT '%s' " - - // These defaults will be useful if there no config value orm:"default" and NOT NULL is on - switch fi.fieldType { - case TypeTimeField, TypeDateField, TypeDateTimeField, TypeTextField: - return v - - case TypeBitField, TypeSmallIntegerField, TypeIntegerField, - TypeBigIntegerField, TypePositiveBitField, TypePositiveSmallIntegerField, - TypePositiveIntegerField, TypePositiveBigIntegerField, TypeFloatField, - TypeDecimalField: - t = " DEFAULT %s " - d = "0" - case TypeBooleanField: - t = " DEFAULT %s " - d = "FALSE" - case TypeJSONField, TypeJsonbField: - d = "{}" - } - - if fi.colDefault { - if !fi.initial.Exist() { - v = fmt.Sprintf(t, "") - } else { - v = fmt.Sprintf(t, fi.initial.String()) - } - } else { - if !fi.null { - v = fmt.Sprintf(t, d) - } - } - - return v -} diff --git a/orm/models.go b/orm/models.go deleted file mode 100644 index 4776bcba..00000000 --- a/orm/models.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "sync" -) - -const ( - odCascade = "cascade" - odSetNULL = "set_null" - odSetDefault = "set_default" - odDoNothing = "do_nothing" - defaultStructTagName = "orm" - defaultStructTagDelim = ";" -) - -var ( - modelCache = &_modelCache{ - cache: make(map[string]*modelInfo), - cacheByFullName: make(map[string]*modelInfo), - } -) - -// model info collection -type _modelCache struct { - sync.RWMutex // only used outsite for bootStrap - orders []string - cache map[string]*modelInfo - cacheByFullName map[string]*modelInfo - done bool -} - -// get all model info -func (mc *_modelCache) all() map[string]*modelInfo { - m := make(map[string]*modelInfo, len(mc.cache)) - for k, v := range mc.cache { - m[k] = v - } - return m -} - -// get ordered model info -func (mc *_modelCache) allOrdered() []*modelInfo { - m := make([]*modelInfo, 0, len(mc.orders)) - for _, table := range mc.orders { - m = append(m, mc.cache[table]) - } - return m -} - -// get model info by table name -func (mc *_modelCache) get(table string) (mi *modelInfo, ok bool) { - mi, ok = mc.cache[table] - return -} - -// get model info by full name -func (mc *_modelCache) getByFullName(name string) (mi *modelInfo, ok bool) { - mi, ok = mc.cacheByFullName[name] - return -} - -// set model info to collection -func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo { - mii := mc.cache[table] - mc.cache[table] = mi - mc.cacheByFullName[mi.fullName] = mi - if mii == nil { - mc.orders = append(mc.orders, table) - } - return mii -} - -// clean all model info. -func (mc *_modelCache) clean() { - mc.orders = make([]string, 0) - mc.cache = make(map[string]*modelInfo) - mc.cacheByFullName = make(map[string]*modelInfo) - mc.done = false -} - -// ResetModelCache Clean model cache. Then you can re-RegisterModel. -// Common use this api for test case. -func ResetModelCache() { - modelCache.clean() -} diff --git a/orm/models_boot.go b/orm/models_boot.go deleted file mode 100644 index 8c56b3c4..00000000 --- a/orm/models_boot.go +++ /dev/null @@ -1,347 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed 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 orm - -import ( - "fmt" - "os" - "reflect" - "runtime/debug" - "strings" -) - -// register models. -// PrefixOrSuffix means table name prefix or suffix. -// isPrefix whether the prefix is prefix or suffix -func registerModel(PrefixOrSuffix string, model interface{}, isPrefix bool) { - val := reflect.ValueOf(model) - typ := reflect.Indirect(val).Type() - - if val.Kind() != reflect.Ptr { - panic(fmt.Errorf(" cannot use non-ptr model struct `%s`", getFullName(typ))) - } - // For this case: - // u := &User{} - // registerModel(&u) - if typ.Kind() == reflect.Ptr { - panic(fmt.Errorf(" only allow ptr model struct, it looks you use two reference to the struct `%s`", typ)) - } - - table := getTableName(val) - - if PrefixOrSuffix != "" { - if isPrefix { - table = PrefixOrSuffix + table - } else { - table = table + PrefixOrSuffix - } - } - // models's fullname is pkgpath + struct name - name := getFullName(typ) - if _, ok := modelCache.getByFullName(name); ok { - fmt.Printf(" model `%s` repeat register, must be unique\n", name) - os.Exit(2) - } - - if _, ok := modelCache.get(table); ok { - fmt.Printf(" table name `%s` repeat register, must be unique\n", table) - os.Exit(2) - } - - mi := newModelInfo(val) - if mi.fields.pk == nil { - outFor: - for _, fi := range mi.fields.fieldsDB { - if strings.ToLower(fi.name) == "id" { - switch fi.addrValue.Elem().Kind() { - case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64: - fi.auto = true - fi.pk = true - mi.fields.pk = fi - break outFor - } - } - } - - if mi.fields.pk == nil { - fmt.Printf(" `%s` needs a primary key field, default is to use 'id' if not set\n", name) - os.Exit(2) - } - - } - - mi.table = table - mi.pkg = typ.PkgPath() - mi.model = model - mi.manual = true - - modelCache.set(table, mi) -} - -// bootstrap models -func bootStrap() { - if modelCache.done { - return - } - var ( - err error - models map[string]*modelInfo - ) - if dataBaseCache.getDefault() == nil { - err = fmt.Errorf("must have one register DataBase alias named `default`") - goto end - } - - // set rel and reverse model - // RelManyToMany set the relTable - models = modelCache.all() - for _, mi := range models { - for _, fi := range mi.fields.columns { - if fi.rel || fi.reverse { - elm := fi.addrValue.Type().Elem() - if fi.fieldType == RelReverseMany || fi.fieldType == RelManyToMany { - elm = elm.Elem() - } - // check the rel or reverse model already register - name := getFullName(elm) - mii, ok := modelCache.getByFullName(name) - if !ok || mii.pkg != elm.PkgPath() { - err = fmt.Errorf("can not find rel in field `%s`, `%s` may be miss register", fi.fullName, elm.String()) - goto end - } - fi.relModelInfo = mii - - switch fi.fieldType { - case RelManyToMany: - if fi.relThrough != "" { - if i := strings.LastIndex(fi.relThrough, "."); i != -1 && len(fi.relThrough) > (i+1) { - pn := fi.relThrough[:i] - rmi, ok := modelCache.getByFullName(fi.relThrough) - if !ok || pn != rmi.pkg { - err = fmt.Errorf("field `%s` wrong rel_through value `%s` cannot find table", fi.fullName, fi.relThrough) - goto end - } - fi.relThroughModelInfo = rmi - fi.relTable = rmi.table - } else { - err = fmt.Errorf("field `%s` wrong rel_through value `%s`", fi.fullName, fi.relThrough) - goto end - } - } else { - i := newM2MModelInfo(mi, mii) - if fi.relTable != "" { - i.table = fi.relTable - } - if v := modelCache.set(i.table, i); v != nil { - err = fmt.Errorf("the rel table name `%s` already registered, cannot be use, please change one", fi.relTable) - goto end - } - fi.relTable = i.table - fi.relThroughModelInfo = i - } - - fi.relThroughModelInfo.isThrough = true - } - } - } - } - - // check the rel filed while the relModelInfo also has filed point to current model - // if not exist, add a new field to the relModelInfo - models = modelCache.all() - for _, mi := range models { - for _, fi := range mi.fields.fieldsRel { - switch fi.fieldType { - case RelForeignKey, RelOneToOne, RelManyToMany: - inModel := false - for _, ffi := range fi.relModelInfo.fields.fieldsReverse { - if ffi.relModelInfo == mi { - inModel = true - break - } - } - if !inModel { - rmi := fi.relModelInfo - ffi := new(fieldInfo) - ffi.name = mi.name - ffi.column = ffi.name - ffi.fullName = rmi.fullName + "." + ffi.name - ffi.reverse = true - ffi.relModelInfo = mi - ffi.mi = rmi - if fi.fieldType == RelOneToOne { - ffi.fieldType = RelReverseOne - } else { - ffi.fieldType = RelReverseMany - } - if !rmi.fields.Add(ffi) { - added := false - for cnt := 0; cnt < 5; cnt++ { - ffi.name = fmt.Sprintf("%s%d", mi.name, cnt) - ffi.column = ffi.name - ffi.fullName = rmi.fullName + "." + ffi.name - if added = rmi.fields.Add(ffi); added { - break - } - } - if !added { - panic(fmt.Errorf("cannot generate auto reverse field info `%s` to `%s`", fi.fullName, ffi.fullName)) - } - } - } - } - } - } - - models = modelCache.all() - for _, mi := range models { - for _, fi := range mi.fields.fieldsRel { - switch fi.fieldType { - case RelManyToMany: - for _, ffi := range fi.relThroughModelInfo.fields.fieldsRel { - switch ffi.fieldType { - case RelOneToOne, RelForeignKey: - if ffi.relModelInfo == fi.relModelInfo { - fi.reverseFieldInfoTwo = ffi - } - if ffi.relModelInfo == mi { - fi.reverseField = ffi.name - fi.reverseFieldInfo = ffi - } - } - } - if fi.reverseFieldInfoTwo == nil { - err = fmt.Errorf("can not find m2m field for m2m model `%s`, ensure your m2m model defined correct", - fi.relThroughModelInfo.fullName) - goto end - } - } - } - } - - models = modelCache.all() - for _, mi := range models { - for _, fi := range mi.fields.fieldsReverse { - switch fi.fieldType { - case RelReverseOne: - found := false - mForA: - for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelOneToOne] { - if ffi.relModelInfo == mi { - found = true - fi.reverseField = ffi.name - fi.reverseFieldInfo = ffi - - ffi.reverseField = fi.name - ffi.reverseFieldInfo = fi - break mForA - } - } - if !found { - err = fmt.Errorf("reverse field `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName) - goto end - } - case RelReverseMany: - found := false - mForB: - for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelForeignKey] { - if ffi.relModelInfo == mi { - found = true - fi.reverseField = ffi.name - fi.reverseFieldInfo = ffi - - ffi.reverseField = fi.name - ffi.reverseFieldInfo = fi - - break mForB - } - } - if !found { - mForC: - for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelManyToMany] { - conditions := fi.relThrough != "" && fi.relThrough == ffi.relThrough || - fi.relTable != "" && fi.relTable == ffi.relTable || - fi.relThrough == "" && fi.relTable == "" - if ffi.relModelInfo == mi && conditions { - found = true - - fi.reverseField = ffi.reverseFieldInfoTwo.name - fi.reverseFieldInfo = ffi.reverseFieldInfoTwo - fi.relThroughModelInfo = ffi.relThroughModelInfo - fi.reverseFieldInfoTwo = ffi.reverseFieldInfo - fi.reverseFieldInfoM2M = ffi - ffi.reverseFieldInfoM2M = fi - - break mForC - } - } - } - if !found { - err = fmt.Errorf("reverse field for `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName) - goto end - } - } - } - } - -end: - if err != nil { - fmt.Println(err) - debug.PrintStack() - os.Exit(2) - } -} - -// RegisterModel register models -func RegisterModel(models ...interface{}) { - if modelCache.done { - panic(fmt.Errorf("RegisterModel must be run before BootStrap")) - } - RegisterModelWithPrefix("", models...) -} - -// RegisterModelWithPrefix register models with a prefix -func RegisterModelWithPrefix(prefix string, models ...interface{}) { - if modelCache.done { - panic(fmt.Errorf("RegisterModelWithPrefix must be run before BootStrap")) - } - - for _, model := range models { - registerModel(prefix, model, true) - } -} - -// RegisterModelWithSuffix register models with a suffix -func RegisterModelWithSuffix(suffix string, models ...interface{}) { - if modelCache.done { - panic(fmt.Errorf("RegisterModelWithSuffix must be run before BootStrap")) - } - - for _, model := range models { - registerModel(suffix, model, false) - } -} - -// BootStrap bootstrap models. -// make all model parsed and can not add more models -func BootStrap() { - modelCache.Lock() - defer modelCache.Unlock() - if modelCache.done { - return - } - bootStrap() - modelCache.done = true -} diff --git a/scripts/gobuild.sh b/scripts/gobuild.sh deleted file mode 100755 index 031eafc2..00000000 --- a/scripts/gobuild.sh +++ /dev/null @@ -1,112 +0,0 @@ -#!/bin/bash - -# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY -# -# The original version of this file is located in the https://github.com/istio/common-files repo. -# If you're looking at this file in a different repo and want to make a change, please go to the -# common-files repo, make the change there and check it in. Then come back to this repo and run -# "make update-common". - -# Copyright Istio Authors. All Rights Reserved. -# -# Licensed 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. - -# This script builds and version stamps the output - -# adatp to beego - -VERBOSE=${VERBOSE:-"0"} -V="" -if [[ "${VERBOSE}" == "1" ]];then - V="-x" - set -x -fi - -SCRIPTPATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -OUT=${1:?"output path"} -shift - -set -e - -BUILD_GOOS=${GOOS:-linux} -BUILD_GOARCH=${GOARCH:-amd64} -GOBINARY=${GOBINARY:-go} -GOPKG="$GOPATH/pkg" -BUILDINFO=${BUILDINFO:-""} -STATIC=${STATIC:-1} -LDFLAGS=${LDFLAGS:--extldflags -static} -GOBUILDFLAGS=${GOBUILDFLAGS:-""} -# Split GOBUILDFLAGS by spaces into an array called GOBUILDFLAGS_ARRAY. -IFS=' ' read -r -a GOBUILDFLAGS_ARRAY <<< "$GOBUILDFLAGS" - -GCFLAGS=${GCFLAGS:-} -export CGO_ENABLED=0 - -if [[ "${STATIC}" != "1" ]];then - LDFLAGS="" -fi - -# gather buildinfo if not already provided -# For a release build BUILDINFO should be produced -# at the beginning of the build and used throughout -if [[ -z ${BUILDINFO} ]];then - BUILDINFO=$(mktemp) - "${SCRIPTPATH}/report_build_info.sh" > "${BUILDINFO}" -fi - - -# BUILD LD_EXTRAFLAGS -LD_EXTRAFLAGS="" - -while read -r line; do - LD_EXTRAFLAGS="${LD_EXTRAFLAGS} -X ${line}" -done < "${BUILDINFO}" - -# verify go version before build -# NB. this was copied verbatim from Kubernetes hack -minimum_go_version=go1.13 # supported patterns: go1.x, go1.x.x (x should be a number) -IFS=" " read -ra go_version <<< "$(${GOBINARY} version)" -if [[ "${minimum_go_version}" != $(echo -e "${minimum_go_version}\n${go_version[2]}" | sort -s -t. -k 1,1 -k 2,2n -k 3,3n | head -n1) && "${go_version[2]}" != "devel" ]]; then - echo "Warning: Detected that you are using an older version of the Go compiler. Beego requires ${minimum_go_version} or greater." -fi - -CURRENT_BRANCH=$(git branch | grep '*') -CURRENT_BRANCH=${CURRENT_BRANCH:2} - -BUILD_TIME=$(date +%Y-%m-%d--%T) - -LD_EXTRAFLAGS="${LD_EXTRAFLAGS} -X github.com/astaxie/beego.GoVersion=${go_version[2]:2}" -LD_EXTRAFLAGS="${LD_EXTRAFLAGS} -X github.com/astaxie/beego.GitBranch=${CURRENT_BRANCH}" -LD_EXTRAFLAGS="${LD_EXTRAFLAGS} -X github.com/astaxie/beego.BuildTime=$BUILD_TIME" - -OPTIMIZATION_FLAGS="-trimpath" -if [ "${DEBUG}" == "1" ]; then - OPTIMIZATION_FLAGS="" -fi - - - -echo "BUILD_GOARCH: $BUILD_GOARCH" -echo "GOPKG: $GOPKG" -echo "LD_EXTRAFLAGS: $LD_EXTRAFLAGS" -echo "GO_VERSION: ${go_version[2]}" -echo "BRANCH: $CURRENT_BRANCH" -echo "BUILD_TIME: $BUILD_TIME" - -time GOOS=${BUILD_GOOS} GOARCH=${BUILD_GOARCH} ${GOBINARY} build \ - ${V} "${GOBUILDFLAGS_ARRAY[@]}" ${GCFLAGS:+-gcflags "${GCFLAGS}"} \ - -o "${OUT}" \ - ${OPTIMIZATION_FLAGS} \ - -pkgdir="${GOPKG}/${BUILD_GOOS}_${BUILD_GOARCH}" \ - -ldflags "${LDFLAGS} ${LD_EXTRAFLAGS}" "${@}" \ No newline at end of file diff --git a/scripts/report_build_info.sh b/scripts/report_build_info.sh deleted file mode 100755 index 65ba3748..00000000 --- a/scripts/report_build_info.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash - -# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY -# -# The original version of this file is located in the https://github.com/istio/common-files repo. -# If you're looking at this file in a different repo and want to make a change, please go to the -# common-files repo, make the change there and check it in. Then come back to this repo and run -# "make update-common". - -# Copyright Istio Authors -# -# Licensed 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. - -# adapt to beego - -if BUILD_GIT_REVISION=$(git rev-parse HEAD 2> /dev/null); then - if [[ -n "$(git status --porcelain 2>/dev/null)" ]]; then - BUILD_GIT_REVISION=${BUILD_GIT_REVISION}"-dirty" - fi -else - BUILD_GIT_REVISION=unknown -fi - -# Check for local changes -if git diff-index --quiet HEAD --; then - tree_status="Clean" -else - tree_status="Modified" -fi - -# security wanted VERSION='unknown' -VERSION="${BUILD_GIT_REVISION}" -if [[ -n ${BEEGO_VERSION} ]]; then - VERSION="${BEEGO_VERSION}" -fi - -GIT_DESCRIBE_TAG=$(git describe --tags) - -echo "github.com/astaxie/beego.BuildVersion=${VERSION}" -echo "github.com/astaxie/beego.BuildGitRevision=${BUILD_GIT_REVISION}" -echo "github.com/astaxie/beego.BuildStatus=${tree_status}" -echo "github.com/astaxie/beego.BuildTag=${GIT_DESCRIBE_TAG}" \ No newline at end of file diff --git a/server/web/LICENSE b/server/web/LICENSE new file mode 100644 index 00000000..5dbd4243 --- /dev/null +++ b/server/web/LICENSE @@ -0,0 +1,13 @@ +Copyright 2014 astaxie + +Licensed 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. \ No newline at end of file diff --git a/server/web/admin.go b/server/web/admin.go new file mode 100644 index 00000000..1b06f486 --- /dev/null +++ b/server/web/admin.go @@ -0,0 +1,126 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 web + +import ( + "fmt" + "net/http" + "reflect" + "time" + + "github.com/astaxie/beego/core/logs" +) + +// BeeAdminApp is the default adminApp used by admin module. +var beeAdminApp *adminApp + +// FilterMonitorFunc is default monitor filter when admin module is enable. +// if this func returns, admin module records qps for this request by condition of this function logic. +// usage: +// func MyFilterMonitor(method, requestPath string, t time.Duration, pattern string, statusCode int) bool { +// if method == "POST" { +// return false +// } +// if t.Nanoseconds() < 100 { +// return false +// } +// if strings.HasPrefix(requestPath, "/astaxie") { +// return false +// } +// return true +// } +// beego.FilterMonitorFunc = MyFilterMonitor. +var FilterMonitorFunc func(string, string, time.Duration, string, int) bool + +func init() { + + FilterMonitorFunc = func(string, string, time.Duration, string, int) bool { return true } + +} + +func list(root string, p interface{}, m M) { + pt := reflect.TypeOf(p) + pv := reflect.ValueOf(p) + if pt.Kind() == reflect.Ptr { + pt = pt.Elem() + pv = pv.Elem() + } + for i := 0; i < pv.NumField(); i++ { + var key string + if root == "" { + key = pt.Field(i).Name + } else { + key = root + "." + pt.Field(i).Name + } + if pv.Field(i).Kind() == reflect.Struct { + list(key, pv.Field(i).Interface(), m) + } else { + m[key] = pv.Field(i).Interface() + } + } +} + +func writeJSON(rw http.ResponseWriter, jsonData []byte) { + rw.Header().Set("Content-Type", "application/json") + rw.Write(jsonData) +} + +// adminApp is an http.HandlerFunc map used as beeAdminApp. +type adminApp struct { + *HttpServer +} + +// Route adds http.HandlerFunc to adminApp with url pattern. +func (admin *adminApp) Run() { + + // if len(task.AdminTaskList) > 0 { + // task.StartTask() + // } + logs.Warning("now we don't start tasks here, if you use task module," + + " please invoke task.StartTask, or task will not be executed") + + addr := BConfig.Listen.AdminAddr + + if BConfig.Listen.AdminPort != 0 { + addr = fmt.Sprintf("%s:%d", BConfig.Listen.AdminAddr, BConfig.Listen.AdminPort) + } + + logs.Info("Admin server Running on %s", addr) + + admin.HttpServer.Run(addr) +} + +func registerAdmin() error { + if BConfig.Listen.EnableAdmin { + + c := &adminController{ + servers: make([]*HttpServer, 0, 2), + } + beeAdminApp = &adminApp{ + HttpServer: NewHttpServerWithCfg(BConfig), + } + // keep in mind that all data should be html escaped to avoid XSS attack + beeAdminApp.Router("/", c, "get:AdminIndex") + beeAdminApp.Router("/qps", c, "get:QpsIndex") + beeAdminApp.Router("/prof", c, "get:ProfIndex") + beeAdminApp.Router("/healthcheck", c, "get:Healthcheck") + beeAdminApp.Router("/task", c, "get:TaskStatus") + beeAdminApp.Router("/listconf", c, "get:ListConf") + beeAdminApp.Router("/metrics", c, "get:PrometheusMetrics") + + go beeAdminApp.Run() + } + return nil +} diff --git a/server/web/admin_controller.go b/server/web/admin_controller.go new file mode 100644 index 00000000..2998c8d4 --- /dev/null +++ b/server/web/admin_controller.go @@ -0,0 +1,297 @@ +// Copyright 2020 +// +// Licensed 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 web + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "strconv" + "text/template" + + "github.com/prometheus/client_golang/prometheus/promhttp" + + "github.com/astaxie/beego/core/governor" +) + +type adminController struct { + Controller + servers []*HttpServer +} + +func (a *adminController) registerHttpServer(svr *HttpServer) { + a.servers = append(a.servers, svr) +} + +// ProfIndex is a http.Handler for showing profile command. +// it's in url pattern "/prof" in admin module. +func (a *adminController) ProfIndex() { + rw, r := a.Ctx.ResponseWriter, a.Ctx.Request + r.ParseForm() + command := r.Form.Get("command") + if command == "" { + return + } + + var ( + format = r.Form.Get("format") + data = make(map[interface{}]interface{}) + result bytes.Buffer + ) + governor.ProcessInput(command, &result) + data["Content"] = template.HTMLEscapeString(result.String()) + + if format == "json" && command == "gc summary" { + dataJSON, err := json.Marshal(data) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + writeJSON(rw, dataJSON) + return + } + + data["Title"] = template.HTMLEscapeString(command) + defaultTpl := defaultScriptsTpl + if command == "gc summary" { + defaultTpl = gcAjaxTpl + } + writeTemplate(rw, data, profillingTpl, defaultTpl) +} + +func (a *adminController) PrometheusMetrics() { + promhttp.Handler().ServeHTTP(a.Ctx.ResponseWriter, a.Ctx.Request) +} + +// TaskStatus is a http.Handler with running task status (task name, status and the last execution). +// it's in "/task" pattern in admin module. +func (a *adminController) TaskStatus() { + + rw, req := a.Ctx.ResponseWriter, a.Ctx.Request + + data := make(map[interface{}]interface{}) + + // Run Task + req.ParseForm() + taskname := req.Form.Get("taskname") + if taskname != "" { + cmd := governor.GetCommand("task", "run") + res := cmd.Execute(taskname) + if res.IsSuccess() { + + data["Message"] = []string{"success", + template.HTMLEscapeString(fmt.Sprintf("%s run success,Now the Status is
%s", + taskname, res.Content.(string)))} + + } else { + data["Message"] = []string{"error", template.HTMLEscapeString(fmt.Sprintf("%s", res.Error))} + } + } + + // List Tasks + content := make(M) + resultList := governor.GetCommand("task", "list").Execute().Content.([][]string) + var fields = []string{ + "Task Name", + "Task Spec", + "Task Status", + "Last Time", + "", + } + + content["Fields"] = fields + content["Data"] = resultList + data["Content"] = content + data["Title"] = "Tasks" + writeTemplate(rw, data, tasksTpl, defaultScriptsTpl) +} + +func (a *adminController) AdminIndex() { + // AdminIndex is the default http.Handler for admin module. + // it matches url pattern "/". + writeTemplate(a.Ctx.ResponseWriter, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl) +} + +// Healthcheck is a http.Handler calling health checking and showing the result. +// it's in "/healthcheck" pattern in admin module. +func (a *adminController) Healthcheck() { + heathCheck(a.Ctx.ResponseWriter, a.Ctx.Request) +} + +func heathCheck(rw http.ResponseWriter, r *http.Request) { + var ( + result []string + data = make(map[interface{}]interface{}) + resultList = new([][]string) + content = M{ + "Fields": []string{"Name", "Message", "Status"}, + } + ) + + for name, h := range governor.AdminCheckList { + if err := h.Check(); err != nil { + result = []string{ + "error", + template.HTMLEscapeString(name), + template.HTMLEscapeString(err.Error()), + } + } else { + result = []string{ + "success", + template.HTMLEscapeString(name), + "OK", + } + } + *resultList = append(*resultList, result) + } + + queryParams := r.URL.Query() + jsonFlag := queryParams.Get("json") + shouldReturnJSON, _ := strconv.ParseBool(jsonFlag) + + if shouldReturnJSON { + response := buildHealthCheckResponseList(resultList) + jsonResponse, err := json.Marshal(response) + + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + } else { + writeJSON(rw, jsonResponse) + } + return + } + + content["Data"] = resultList + data["Content"] = content + data["Title"] = "Health Check" + + writeTemplate(rw, data, healthCheckTpl, defaultScriptsTpl) +} + +// QpsIndex is the http.Handler for writing qps statistics map result info in http.ResponseWriter. +// it's registered with url pattern "/qps" in admin module. +func (a *adminController) QpsIndex() { + data := make(map[interface{}]interface{}) + data["Content"] = StatisticsMap.GetMap() + + // do html escape before display path, avoid xss + if content, ok := (data["Content"]).(M); ok { + if resultLists, ok := (content["Data"]).([][]string); ok { + for i := range resultLists { + if len(resultLists[i]) > 0 { + resultLists[i][0] = template.HTMLEscapeString(resultLists[i][0]) + } + } + } + } + writeTemplate(a.Ctx.ResponseWriter, data, qpsTpl, defaultScriptsTpl) +} + +// ListConf is the http.Handler of displaying all beego configuration values as key/value pair. +// it's registered with url pattern "/listconf" in admin module. +func (a *adminController) ListConf() { + rw := a.Ctx.ResponseWriter + r := a.Ctx.Request + r.ParseForm() + command := r.Form.Get("command") + if command == "" { + rw.Write([]byte("command not support")) + return + } + + data := make(map[interface{}]interface{}) + switch command { + case "conf": + m := make(M) + list("BConfig", BConfig, m) + m["appConfigPath"] = template.HTMLEscapeString(appConfigPath) + m["appConfigProvider"] = template.HTMLEscapeString(appConfigProvider) + tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) + tmpl = template.Must(tmpl.Parse(configTpl)) + tmpl = template.Must(tmpl.Parse(defaultScriptsTpl)) + + data["Content"] = m + + tmpl.Execute(rw, data) + + case "router": + content := BeeApp.PrintTree() + content["Fields"] = []string{ + "Router Pattern", + "Methods", + "Controller", + } + data["Content"] = content + data["Title"] = "Routers" + writeTemplate(rw, data, routerAndFilterTpl, defaultScriptsTpl) + case "filter": + var ( + content = M{ + "Fields": []string{ + "Router Pattern", + "Filter Function", + }, + } + ) + + filterTypeData := BeeApp.reportFilter() + + filterTypes := make([]string, 0, len(filterTypeData)) + for k, _ := range filterTypeData { + filterTypes = append(filterTypes, k) + } + + content["Data"] = filterTypeData + content["Methods"] = filterTypes + + data["Content"] = content + data["Title"] = "Filters" + writeTemplate(rw, data, routerAndFilterTpl, defaultScriptsTpl) + default: + rw.Write([]byte("command not support")) + } +} + +func writeTemplate(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) { + tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) + for _, tpl := range tpls { + tmpl = template.Must(tmpl.Parse(tpl)) + } + tmpl.Execute(rw, data) +} + +func buildHealthCheckResponseList(healthCheckResults *[][]string) []map[string]interface{} { + response := make([]map[string]interface{}, len(*healthCheckResults)) + + for i, healthCheckResult := range *healthCheckResults { + currentResultMap := make(map[string]interface{}) + + currentResultMap["name"] = healthCheckResult[0] + currentResultMap["message"] = healthCheckResult[1] + currentResultMap["status"] = healthCheckResult[2] + + response[i] = currentResultMap + } + + return response + +} + +// PrintTree print all routers +// Deprecated using BeeApp directly +func PrintTree() M { + return BeeApp.PrintTree() +} diff --git a/server/web/admin_test.go b/server/web/admin_test.go new file mode 100644 index 00000000..5ef57323 --- /dev/null +++ b/server/web/admin_test.go @@ -0,0 +1,249 @@ +package web + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/core/governor" +) + +type SampleDatabaseCheck struct { +} + +type SampleCacheCheck struct { +} + +func (dc *SampleDatabaseCheck) Check() error { + return nil +} + +func (cc *SampleCacheCheck) Check() error { + return errors.New("no cache detected") +} + +func TestList_01(t *testing.T) { + m := make(M) + list("BConfig", BConfig, m) + t.Log(m) + om := oldMap() + for k, v := range om { + if fmt.Sprint(m[k]) != fmt.Sprint(v) { + t.Log(k, "old-key", v, "new-key", m[k]) + t.FailNow() + } + } +} + +func oldMap() M { + m := make(M) + m["BConfig.AppName"] = BConfig.AppName + m["BConfig.RunMode"] = BConfig.RunMode + m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive + m["BConfig.ServerName"] = BConfig.ServerName + m["BConfig.RecoverPanic"] = BConfig.RecoverPanic + m["BConfig.CopyRequestBody"] = BConfig.CopyRequestBody + m["BConfig.EnableGzip"] = BConfig.EnableGzip + m["BConfig.MaxMemory"] = BConfig.MaxMemory + m["BConfig.EnableErrorsShow"] = BConfig.EnableErrorsShow + m["BConfig.Listen.Graceful"] = BConfig.Listen.Graceful + m["BConfig.Listen.ServerTimeOut"] = BConfig.Listen.ServerTimeOut + m["BConfig.Listen.ListenTCP4"] = BConfig.Listen.ListenTCP4 + m["BConfig.Listen.EnableHTTP"] = BConfig.Listen.EnableHTTP + m["BConfig.Listen.HTTPAddr"] = BConfig.Listen.HTTPAddr + m["BConfig.Listen.HTTPPort"] = BConfig.Listen.HTTPPort + m["BConfig.Listen.EnableHTTPS"] = BConfig.Listen.EnableHTTPS + m["BConfig.Listen.HTTPSAddr"] = BConfig.Listen.HTTPSAddr + m["BConfig.Listen.HTTPSPort"] = BConfig.Listen.HTTPSPort + m["BConfig.Listen.HTTPSCertFile"] = BConfig.Listen.HTTPSCertFile + m["BConfig.Listen.HTTPSKeyFile"] = BConfig.Listen.HTTPSKeyFile + m["BConfig.Listen.EnableAdmin"] = BConfig.Listen.EnableAdmin + m["BConfig.Listen.AdminAddr"] = BConfig.Listen.AdminAddr + m["BConfig.Listen.AdminPort"] = BConfig.Listen.AdminPort + m["BConfig.Listen.EnableFcgi"] = BConfig.Listen.EnableFcgi + m["BConfig.Listen.EnableStdIo"] = BConfig.Listen.EnableStdIo + m["BConfig.WebConfig.AutoRender"] = BConfig.WebConfig.AutoRender + m["BConfig.WebConfig.EnableDocs"] = BConfig.WebConfig.EnableDocs + m["BConfig.WebConfig.FlashName"] = BConfig.WebConfig.FlashName + m["BConfig.WebConfig.FlashSeparator"] = BConfig.WebConfig.FlashSeparator + m["BConfig.WebConfig.DirectoryIndex"] = BConfig.WebConfig.DirectoryIndex + m["BConfig.WebConfig.StaticDir"] = BConfig.WebConfig.StaticDir + m["BConfig.WebConfig.StaticExtensionsToGzip"] = BConfig.WebConfig.StaticExtensionsToGzip + m["BConfig.WebConfig.StaticCacheFileSize"] = BConfig.WebConfig.StaticCacheFileSize + m["BConfig.WebConfig.StaticCacheFileNum"] = BConfig.WebConfig.StaticCacheFileNum + m["BConfig.WebConfig.TemplateLeft"] = BConfig.WebConfig.TemplateLeft + m["BConfig.WebConfig.TemplateRight"] = BConfig.WebConfig.TemplateRight + m["BConfig.WebConfig.ViewsPath"] = BConfig.WebConfig.ViewsPath + m["BConfig.WebConfig.EnableXSRF"] = BConfig.WebConfig.EnableXSRF + m["BConfig.WebConfig.XSRFExpire"] = BConfig.WebConfig.XSRFExpire + m["BConfig.WebConfig.Session.SessionOn"] = BConfig.WebConfig.Session.SessionOn + m["BConfig.WebConfig.Session.SessionProvider"] = BConfig.WebConfig.Session.SessionProvider + m["BConfig.WebConfig.Session.SessionName"] = BConfig.WebConfig.Session.SessionName + m["BConfig.WebConfig.Session.SessionGCMaxLifetime"] = BConfig.WebConfig.Session.SessionGCMaxLifetime + m["BConfig.WebConfig.Session.SessionProviderConfig"] = BConfig.WebConfig.Session.SessionProviderConfig + m["BConfig.WebConfig.Session.SessionCookieLifeTime"] = BConfig.WebConfig.Session.SessionCookieLifeTime + m["BConfig.WebConfig.Session.SessionAutoSetCookie"] = BConfig.WebConfig.Session.SessionAutoSetCookie + m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain + m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly + m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs + m["BConfig.Log.EnableStaticLogs"] = BConfig.Log.EnableStaticLogs + m["BConfig.Log.AccessLogsFormat"] = BConfig.Log.AccessLogsFormat + m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum + m["BConfig.Log.Outputs"] = BConfig.Log.Outputs + return m +} + +func TestWriteJSON(t *testing.T) { + t.Log("Testing the adding of JSON to the response") + + w := httptest.NewRecorder() + originalBody := []int{1, 2, 3} + + res, _ := json.Marshal(originalBody) + + writeJSON(w, res) + + decodedBody := []int{} + err := json.NewDecoder(w.Body).Decode(&decodedBody) + + if err != nil { + t.Fatal("Could not decode response body into slice.") + } + + for i := range decodedBody { + if decodedBody[i] != originalBody[i] { + t.Fatalf("Expected %d but got %d in decoded body slice", originalBody[i], decodedBody[i]) + } + } +} + +func TestHealthCheckHandlerDefault(t *testing.T) { + endpointPath := "/healthcheck" + + governor.AddHealthCheck("database", &SampleDatabaseCheck{}) + governor.AddHealthCheck("cache", &SampleCacheCheck{}) + + req, err := http.NewRequest("GET", endpointPath, nil) + if err != nil { + t.Fatal(err) + } + + w := httptest.NewRecorder() + + handler := http.HandlerFunc(heathCheck) + + handler.ServeHTTP(w, req) + + if status := w.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + if !strings.Contains(w.Body.String(), "database") { + t.Errorf("Expected 'database' in generated template.") + } + +} + +func TestBuildHealthCheckResponseList(t *testing.T) { + healthCheckResults := [][]string{ + []string{ + "error", + "Database", + "Error occured whie starting the db", + }, + []string{ + "success", + "Cache", + "Cache started successfully", + }, + } + + responseList := buildHealthCheckResponseList(&healthCheckResults) + + if len(responseList) != len(healthCheckResults) { + t.Errorf("invalid response map length: got %d want %d", + len(responseList), len(healthCheckResults)) + } + + responseFields := []string{"name", "message", "status"} + + for _, response := range responseList { + for _, field := range responseFields { + _, ok := response[field] + if !ok { + t.Errorf("expected %s to be in the response %v", field, response) + } + } + + } + +} + +func TestHealthCheckHandlerReturnsJSON(t *testing.T) { + + governor.AddHealthCheck("database", &SampleDatabaseCheck{}) + governor.AddHealthCheck("cache", &SampleCacheCheck{}) + + req, err := http.NewRequest("GET", "/healthcheck?json=true", nil) + if err != nil { + t.Fatal(err) + } + + w := httptest.NewRecorder() + + handler := http.HandlerFunc(heathCheck) + + handler.ServeHTTP(w, req) + if status := w.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + decodedResponseBody := []map[string]interface{}{} + expectedResponseBody := []map[string]interface{}{} + + expectedJSONString := []byte(` + [ + { + "message":"database", + "name":"success", + "status":"OK" + }, + { + "message":"cache", + "name":"error", + "status":"no cache detected" + } + ] + `) + + json.Unmarshal(expectedJSONString, &expectedResponseBody) + + json.Unmarshal(w.Body.Bytes(), &decodedResponseBody) + + if len(expectedResponseBody) != len(decodedResponseBody) { + t.Errorf("invalid response map length: got %d want %d", + len(decodedResponseBody), len(expectedResponseBody)) + } + assert.Equal(t, len(expectedResponseBody), len(decodedResponseBody)) + assert.Equal(t, 2, len(decodedResponseBody)) + + var database, cache map[string]interface{} + if decodedResponseBody[0]["message"] == "database" { + database = decodedResponseBody[0] + cache = decodedResponseBody[1] + } else { + database = decodedResponseBody[1] + cache = decodedResponseBody[0] + } + + assert.Equal(t, expectedResponseBody[0], database) + assert.Equal(t, expectedResponseBody[1], cache) + +} diff --git a/adminui.go b/server/web/adminui.go similarity index 99% rename from adminui.go rename to server/web/adminui.go index cdcdef33..de8c9455 100644 --- a/adminui.go +++ b/server/web/adminui.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web var indexTpl = ` {{define "content"}} @@ -21,7 +21,7 @@ var indexTpl = ` For detail usage please check our document:

-Toolbox +Toolbox

Live Monitor diff --git a/beego.go b/server/web/beego.go similarity index 65% rename from beego.go rename to server/web/beego.go index 8ebe0bab..14e51a94 100644 --- a/beego.go +++ b/server/web/beego.go @@ -12,19 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "os" "path/filepath" - "strconv" - "strings" + "sync" ) const ( - // VERSION represent beego web framework version. - VERSION = "1.12.2" - // DEV is for develop DEV = "dev" // PROD is for production @@ -38,7 +34,7 @@ type M map[string]interface{} type hookfunc func() error var ( - hooks = make([]hookfunc, 0) //hook function slice to store the hookfunc + hooks = make([]hookfunc, 0) // hook function slice to store the hookfunc ) // AddAPPStartHook is used to register the hookfunc @@ -55,55 +51,39 @@ func AddAPPStartHook(hf ...hookfunc) { // beego.Run("127.0.0.1:8089") func Run(params ...string) { - initBeforeHTTPRun() - if len(params) > 0 && params[0] != "" { - strs := strings.Split(params[0], ":") - if len(strs) > 0 && strs[0] != "" { - BConfig.Listen.HTTPAddr = strs[0] - } - if len(strs) > 1 && strs[1] != "" { - BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1]) - } - - BConfig.Listen.Domains = params + BeeApp.Run(params[0]) } - - BeeApp.Run() + BeeApp.Run("") } // RunWithMiddleWares Run beego application with middlewares. func RunWithMiddleWares(addr string, mws ...MiddleWare) { - initBeforeHTTPRun() - - strs := strings.Split(addr, ":") - if len(strs) > 0 && strs[0] != "" { - BConfig.Listen.HTTPAddr = strs[0] - BConfig.Listen.Domains = []string{strs[0]} - } - if len(strs) > 1 && strs[1] != "" { - BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1]) - } - - BeeApp.Run(mws...) + BeeApp.Run(addr, mws...) } -func initBeforeHTTPRun() { - //init hooks - AddAPPStartHook( - registerMime, - registerDefaultErrorHandler, - registerSession, - registerTemplate, - registerAdmin, - registerGzip, - ) +var initHttpOnce sync.Once - for _, hk := range hooks { - if err := hk(); err != nil { - panic(err) +// TODO move to module init function +func initBeforeHTTPRun() { + initHttpOnce.Do(func() { + // init hooks + AddAPPStartHook( + registerMime, + registerDefaultErrorHandler, + registerSession, + registerTemplate, + registerAdmin, + registerGzip, + registerCommentRouter, + ) + + for _, hk := range hooks { + if err := hk(); err != nil { + panic(err) + } } - } + }) } // TestBeegoInit is for test package init diff --git a/server/web/captcha/LICENSE b/server/web/captcha/LICENSE new file mode 100644 index 00000000..0ad73ae0 --- /dev/null +++ b/server/web/captcha/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2014 Dmitry Chestnykh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/server/web/captcha/README.md b/server/web/captcha/README.md new file mode 100644 index 00000000..dbc2026b --- /dev/null +++ b/server/web/captcha/README.md @@ -0,0 +1,45 @@ +# Captcha + +an example for use captcha + +``` +package controllers + +import ( + "github.com/astaxie/beego" + "github.com/astaxie/beego/cache" + "github.com/astaxie/beego/utils/captcha" +) + +var cpt *captcha.Captcha + +func init() { + // use beego cache system store the captcha data + store := cache.NewMemoryCache() + cpt = captcha.NewWithFilter("/captcha/", store) +} + +type MainController struct { + beego.Controller +} + +func (this *MainController) Get() { + this.TplName = "index.tpl" +} + +func (this *MainController) Post() { + this.TplName = "index.tpl" + + this.Data["Success"] = cpt.VerifyReq(this.Ctx.Request) +} +``` + +template usage + +``` +{{.Success}} +

+ {{create_captcha}} + +
+``` diff --git a/utils/captcha/captcha.go b/server/web/captcha/captcha.go similarity index 82% rename from utils/captcha/captcha.go rename to server/web/captcha/captcha.go index 42ac70d3..8ce832f7 100644 --- a/utils/captcha/captcha.go +++ b/server/web/captcha/captcha.go @@ -59,6 +59,7 @@ package captcha import ( + context2 "context" "fmt" "html/template" "net/http" @@ -66,11 +67,11 @@ import ( "strings" "time" - "github.com/astaxie/beego" - "github.com/astaxie/beego/cache" - "github.com/astaxie/beego/context" - "github.com/astaxie/beego/logs" - "github.com/astaxie/beego/utils" + "github.com/astaxie/beego/core/logs" + + "github.com/astaxie/beego/core/utils" + "github.com/astaxie/beego/server/web" + "github.com/astaxie/beego/server/web/context" ) var ( @@ -90,7 +91,7 @@ const ( // Captcha struct type Captcha struct { // beego cache store - store cache.Cache + store Storage // url prefix for captcha image URLPrefix string @@ -137,14 +138,15 @@ func (c *Captcha) Handler(ctx *context.Context) { if len(ctx.Input.Query("reload")) > 0 { chars = c.genRandChars() - if err := c.store.Put(key, chars, c.Expiration); err != nil { + if err := c.store.Put(context2.Background(), key, chars, c.Expiration); err != nil { ctx.Output.SetStatus(500) ctx.WriteString("captcha reload error") logs.Error("Reload Create Captcha Error:", err) return } } else { - if v, ok := c.store.Get(key).([]byte); ok { + val, _ := c.store.Get(context2.Background(), key) + if v, ok := val.([]byte); ok { chars = v } else { ctx.Output.SetStatus(404) @@ -183,7 +185,7 @@ func (c *Captcha) CreateCaptcha() (string, error) { chars := c.genRandChars() // save to store - if err := c.store.Put(c.key(id), chars, c.Expiration); err != nil { + if err := c.store.Put(context2.Background(), c.key(id), chars, c.Expiration); err != nil { return "", err } @@ -205,8 +207,8 @@ func (c *Captcha) Verify(id string, challenge string) (success bool) { var chars []byte key := c.key(id) - - if v, ok := c.store.Get(key).([]byte); ok { + val, _ := c.store.Get(context2.Background(), key) + if v, ok := val.([]byte); ok { chars = v } else { return @@ -214,7 +216,7 @@ func (c *Captcha) Verify(id string, challenge string) (success bool) { defer func() { // finally remove it - c.store.Delete(key) + c.store.Delete(context2.Background(), key) }() if len(chars) != len(challenge) { @@ -231,7 +233,7 @@ func (c *Captcha) Verify(id string, challenge string) (success bool) { } // NewCaptcha create a new captcha.Captcha -func NewCaptcha(urlPrefix string, store cache.Cache) *Captcha { +func NewCaptcha(urlPrefix string, store Storage) *Captcha { cpt := &Captcha{} cpt.store = store cpt.FieldIDName = fieldIDName @@ -257,14 +259,23 @@ func NewCaptcha(urlPrefix string, store cache.Cache) *Captcha { // NewWithFilter create a new captcha.Captcha and auto AddFilter for serve captacha image // and add a template func for output html -func NewWithFilter(urlPrefix string, store cache.Cache) *Captcha { +func NewWithFilter(urlPrefix string, store Storage) *Captcha { cpt := NewCaptcha(urlPrefix, store) // create filter for serve captcha image - beego.InsertFilter(cpt.URLPrefix+"*", beego.BeforeRouter, cpt.Handler) + web.InsertFilter(cpt.URLPrefix+"*", web.BeforeRouter, cpt.Handler) // add to template func map - beego.AddFuncMap("create_captcha", cpt.CreateCaptchaHTML) + web.AddFuncMap("create_captcha", cpt.CreateCaptchaHTML) return cpt } + +type Storage interface { + // Get a cached value by key. + Get(ctx context2.Context, key string) (interface{}, error) + // Set a cached value with key and expire time. + Put(ctx context2.Context, key string, val interface{}, timeout time.Duration) error + // Delete cached value by key. + Delete(ctx context2.Context, key string) error +} diff --git a/utils/captcha/image.go b/server/web/captcha/image.go similarity index 100% rename from utils/captcha/image.go rename to server/web/captcha/image.go diff --git a/utils/captcha/image_test.go b/server/web/captcha/image_test.go similarity index 97% rename from utils/captcha/image_test.go rename to server/web/captcha/image_test.go index 5e35b7f7..4b4518a1 100644 --- a/utils/captcha/image_test.go +++ b/server/web/captcha/image_test.go @@ -17,7 +17,7 @@ package captcha import ( "testing" - "github.com/astaxie/beego/utils" + "github.com/astaxie/beego/core/utils" ) type byteCounter struct { diff --git a/utils/captcha/siprng.go b/server/web/captcha/siprng.go similarity index 100% rename from utils/captcha/siprng.go rename to server/web/captcha/siprng.go diff --git a/utils/captcha/siprng_test.go b/server/web/captcha/siprng_test.go similarity index 100% rename from utils/captcha/siprng_test.go rename to server/web/captcha/siprng_test.go diff --git a/config.go b/server/web/config.go similarity index 78% rename from config.go rename to server/web/config.go index b6c9a99c..10138e63 100644 --- a/config.go +++ b/server/web/config.go @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( + "crypto/tls" "fmt" "os" "path/filepath" @@ -22,29 +23,36 @@ import ( "runtime" "strings" - "github.com/astaxie/beego/config" - "github.com/astaxie/beego/context" - "github.com/astaxie/beego/logs" - "github.com/astaxie/beego/session" - "github.com/astaxie/beego/utils" + "github.com/astaxie/beego" + "github.com/astaxie/beego/core/config" + "github.com/astaxie/beego/core/logs" + "github.com/astaxie/beego/server/web/session" + + "github.com/astaxie/beego/core/utils" + "github.com/astaxie/beego/server/web/context" ) // Config is the main struct for BConfig +// TODO after supporting multiple servers, remove common config to somewhere else type Config struct { - AppName string //Application name - RunMode string //Running Mode: dev | prod + AppName string // Application name + RunMode string // Running Mode: dev | prod RouterCaseSensitive bool ServerName string RecoverPanic bool - RecoverFunc func(*context.Context) + RecoverFunc func(*context.Context, *Config) CopyRequestBody bool EnableGzip bool - MaxMemory int64 - EnableErrorsShow bool - EnableErrorsRender bool - Listen Listen - WebConfig WebConfig - Log LogConfig + // MaxMemory and MaxUploadSize are used to limit the request body + // if the request is not uploading file, MaxMemory is the max size of request body + // if the request is uploading file, MaxUploadSize is the max size of request body + MaxMemory int64 + MaxUploadSize int64 + EnableErrorsShow bool + EnableErrorsRender bool + Listen Listen + WebConfig WebConfig + Log LogConfig } // Listen holds for http and https related config @@ -70,6 +78,7 @@ type Listen struct { AdminPort int EnableFcgi bool EnableStdIo bool // EnableStdIo works with EnableFcgi Use FCGI via standard I/O + ClientAuth int } // WebConfig holds web related config @@ -86,6 +95,7 @@ type WebConfig struct { TemplateLeft string TemplateRight string ViewsPath string + CommentRouterPath string EnableXSRF bool XSRFKey string XSRFExpire int @@ -111,8 +121,8 @@ type SessionConfig struct { // LogConfig holds Log related config type LogConfig struct { AccessLogs bool - EnableStaticLogs bool //log static files requests default: false - AccessLogsFormat string //access log format: JSON_FORMAT, APACHE_FORMAT or empty string + EnableStaticLogs bool // log static files requests default: false + AccessLogsFormat string // access log format: JSON_FORMAT, APACHE_FORMAT or empty string FileLineNum bool Outputs map[string]string // Store Adaptor : config } @@ -162,15 +172,15 @@ func init() { } } -func recoverPanic(ctx *context.Context) { +func defaultRecoverPanic(ctx *context.Context, cfg *Config) { if err := recover(); err != nil { if err == ErrAbort { return } - if !BConfig.RecoverPanic { + if !cfg.RecoverPanic { panic(err) } - if BConfig.EnableErrorsShow { + if cfg.EnableErrorsShow { if _, ok := ErrorMaps[fmt.Sprint(err)]; ok { exception(fmt.Sprint(err), ctx) return @@ -187,7 +197,7 @@ func recoverPanic(ctx *context.Context) { logs.Critical(fmt.Sprintf("%s:%d", file, line)) stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line)) } - if BConfig.RunMode == DEV && BConfig.EnableErrorsRender { + if cfg.RunMode == DEV && cfg.EnableErrorsRender { showErr(err, ctx, stack) } if ctx.Output.Status != 0 { @@ -199,18 +209,19 @@ func recoverPanic(ctx *context.Context) { } func newBConfig() *Config { - return &Config{ + res := &Config{ AppName: "beego", RunMode: PROD, RouterCaseSensitive: true, - ServerName: "beegoServer:" + VERSION, + ServerName: "beegoServer:" + beego.VERSION, RecoverPanic: true, - RecoverFunc: recoverPanic, - CopyRequestBody: false, - EnableGzip: false, - MaxMemory: 1 << 26, //64MB - EnableErrorsShow: true, - EnableErrorsRender: true, + + CopyRequestBody: false, + EnableGzip: false, + MaxMemory: 1 << 26, // 64MB + MaxUploadSize: 1 << 30, // 1GB + EnableErrorsShow: true, + EnableErrorsRender: true, Listen: Listen{ Graceful: false, ServerTimeOut: 0, @@ -231,6 +242,7 @@ func newBConfig() *Config { AdminPort: 8088, EnableFcgi: false, EnableStdIo: false, + ClientAuth: int(tls.RequireAndVerifyClientCert), }, WebConfig: WebConfig{ AutoRender: true, @@ -245,6 +257,7 @@ func newBConfig() *Config { TemplateLeft: "{{", TemplateRight: "}}", ViewsPath: "views", + CommentRouterPath: "controllers", EnableXSRF: false, XSRFKey: "beegoxsrf", XSRFExpire: 0, @@ -255,7 +268,7 @@ func newBConfig() *Config { SessionGCMaxLifetime: 3600, SessionProviderConfig: "", SessionDisableHTTPOnly: false, - SessionCookieLifeTime: 0, //set cookie default is the browser life + SessionCookieLifeTime: 0, // set cookie default is the browser life SessionAutoSetCookie: true, SessionDomain: "", SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers @@ -271,6 +284,9 @@ func newBConfig() *Config { Outputs: map[string]string{"console": ""}, }, } + + res.RecoverFunc = defaultRecoverPanic + return res } // now only support ini, next will support json. @@ -282,18 +298,46 @@ func parseConfig(appConfigPath string) (err error) { return assignConfig(AppConfig) } +// assignConfig is tricky. +// For 1.x, it use assignSingleConfig to parse the file +// but for 2.x, we use Unmarshaler method func assignConfig(ac config.Configer) error { + + parseConfigForV1(ac) + + err := ac.Unmarshaler("", BConfig) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, fmt.Sprintf("Unmarshaler config file to BConfig failed. "+ + "And if you are working on v1.x config file, please ignore this, err: %s", err)) + return err + } + + // init log + logs.Reset() + for adaptor, cfg := range BConfig.Log.Outputs { + err := logs.SetLogger(adaptor, cfg) + if err != nil { + fmt.Fprintln(os.Stderr, fmt.Sprintf("%s with the config %q got err:%s", adaptor, cfg, err.Error())) + return err + } + } + logs.SetLogFuncCall(BConfig.Log.FileLineNum) + return nil +} + +func parseConfigForV1(ac config.Configer) { for _, i := range []interface{}{BConfig, &BConfig.Listen, &BConfig.WebConfig, &BConfig.Log, &BConfig.WebConfig.Session} { assignSingleConfig(i, ac) } + // set the run mode first if envRunMode := os.Getenv("BEEGO_RUNMODE"); envRunMode != "" { BConfig.RunMode = envRunMode - } else if runMode := ac.String("RunMode"); runMode != "" { + } else if runMode, err := ac.String("RunMode"); runMode != "" && err == nil { BConfig.RunMode = runMode } - if sd := ac.String("StaticDir"); sd != "" { + if sd, err := ac.String("StaticDir"); sd != "" && err == nil { BConfig.WebConfig.StaticDir = map[string]string{} sds := strings.Fields(sd) for _, v := range sds { @@ -305,7 +349,7 @@ func assignConfig(ac config.Configer) error { } } - if sgz := ac.String("StaticExtensionsToGzip"); sgz != "" { + if sgz, err := ac.String("StaticExtensionsToGzip"); sgz != "" && err == nil { extensions := strings.Split(sgz, ",") fileExts := []string{} for _, ext := range extensions { @@ -331,7 +375,7 @@ func assignConfig(ac config.Configer) error { BConfig.WebConfig.StaticCacheFileNum = sfn } - if lo := ac.String("LogOutputs"); lo != "" { + if lo, err := ac.String("LogOutputs"); lo != "" && err == nil { // if lo is not nil or empty // means user has set his own LogOutputs // clear the default setting to BConfig.Log.Outputs @@ -345,18 +389,6 @@ func assignConfig(ac config.Configer) error { } } } - - //init log - logs.Reset() - for adaptor, config := range BConfig.Log.Outputs { - err := logs.SetLogger(adaptor, config) - if err != nil { - fmt.Fprintln(os.Stderr, fmt.Sprintf("%s with the config %q got err:%s", adaptor, config, err.Error())) - } - } - logs.SetLogFuncCall(BConfig.Log.FileLineNum) - - return nil } func assignSingleConfig(p interface{}, ac config.Configer) { @@ -385,7 +417,7 @@ func assignSingleConfig(p interface{}, ac config.Configer) { pf.SetBool(ac.DefaultBool(name, pf.Bool())) case reflect.Struct: default: - //do nothing here + // do nothing here } } @@ -409,6 +441,7 @@ func LoadAppConfig(adapterName, configPath string) error { } type beegoAppConfig struct { + config.BaseConfiger innerConfig config.Configer } @@ -417,7 +450,11 @@ func newAppConfig(appConfigProvider, appConfigPath string) (*beegoAppConfig, err if err != nil { return nil, err } - return &beegoAppConfig{ac}, nil + return &beegoAppConfig{innerConfig: ac}, nil +} + +func (b *beegoAppConfig) Unmarshaler(prefix string, obj interface{}, opt ...config.DecodeOption) error { + return b.innerConfig.Unmarshaler(prefix, obj, opt...) } func (b *beegoAppConfig) Set(key, val string) error { @@ -427,16 +464,16 @@ func (b *beegoAppConfig) Set(key, val string) error { return nil } -func (b *beegoAppConfig) String(key string) string { - if v := b.innerConfig.String(BConfig.RunMode + "::" + key); v != "" { - return v +func (b *beegoAppConfig) String(key string) (string, error) { + if v, err := b.innerConfig.String(BConfig.RunMode + "::" + key); v != "" && err == nil { + return v, nil } return b.innerConfig.String(key) } -func (b *beegoAppConfig) Strings(key string) []string { - if v := b.innerConfig.Strings(BConfig.RunMode + "::" + key); len(v) > 0 { - return v +func (b *beegoAppConfig) Strings(key string) ([]string, error) { + if v, err := b.innerConfig.Strings(BConfig.RunMode + "::" + key); len(v) > 0 && err == nil { + return v, nil } return b.innerConfig.Strings(key) } @@ -470,14 +507,14 @@ func (b *beegoAppConfig) Float(key string) (float64, error) { } func (b *beegoAppConfig) DefaultString(key string, defaultVal string) string { - if v := b.String(key); v != "" { + if v, err := b.String(key); v != "" && err == nil { return v } return defaultVal } func (b *beegoAppConfig) DefaultStrings(key string, defaultVal []string) []string { - if v := b.Strings(key); len(v) != 0 { + if v, err := b.Strings(key); len(v) != 0 && err == nil { return v } return defaultVal diff --git a/config_test.go b/server/web/config_test.go similarity index 95% rename from config_test.go rename to server/web/config_test.go index 5f71f1c3..0129ebb4 100644 --- a/config_test.go +++ b/server/web/config_test.go @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "encoding/json" "reflect" "testing" - "github.com/astaxie/beego/config" + beeJson "github.com/astaxie/beego/core/config/json" ) func TestDefaults(t *testing.T) { @@ -35,7 +35,7 @@ func TestDefaults(t *testing.T) { func TestAssignConfig_01(t *testing.T) { _BConfig := &Config{} _BConfig.AppName = "beego_test" - jcf := &config.JSONConfig{} + jcf := &beeJson.JSONConfig{} ac, _ := jcf.ParseData([]byte(`{"AppName":"beego_json"}`)) assignSingleConfig(_BConfig, ac) if _BConfig.AppName != "beego_json" { @@ -73,7 +73,7 @@ func TestAssignConfig_02(t *testing.T) { configMap["SessionProviderConfig"] = "file" configMap["FileLineNum"] = true - jcf := &config.JSONConfig{} + jcf := &beeJson.JSONConfig{} bs, _ = json.Marshal(configMap) ac, _ := jcf.ParseData(bs) @@ -109,7 +109,7 @@ func TestAssignConfig_02(t *testing.T) { } func TestAssignConfig_03(t *testing.T) { - jcf := &config.JSONConfig{} + jcf := &beeJson.JSONConfig{} ac, _ := jcf.ParseData([]byte(`{"AppName":"beego"}`)) ac.Set("AppName", "test_app") ac.Set("RunMode", "online") diff --git a/context/acceptencoder.go b/server/web/context/acceptencoder.go similarity index 86% rename from context/acceptencoder.go rename to server/web/context/acceptencoder.go index b4e2492c..8ed6a853 100644 --- a/context/acceptencoder.go +++ b/server/web/context/acceptencoder.go @@ -28,18 +28,18 @@ import ( ) var ( - //Default size==20B same as nginx + // Default size==20B same as nginx defaultGzipMinLength = 20 - //Content will only be compressed if content length is either unknown or greater than gzipMinLength. + // Content will only be compressed if content length is either unknown or greater than gzipMinLength. gzipMinLength = defaultGzipMinLength - //The compression level used for deflate compression. (0-9). + // Compression level used for deflate compression. (0-9). gzipCompressLevel int - //List of HTTP methods to compress. If not set, only GET requests are compressed. + // List of HTTP methods to compress. If not set, only GET requests are compressed. includedMethods map[string]bool getMethodOnly bool ) -// InitGzip init the gzipcompress +// InitGzip initializes the gzipcompress func InitGzip(minLength, compressLevel int, methods []string) { if minLength >= 0 { gzipMinLength = minLength @@ -98,9 +98,9 @@ func (ac acceptEncoder) put(wr resetWriter, level int) { } wr.Reset(nil) - //notice - //compressionLevel==BestCompression DOES NOT MATTER - //sync.Pool will not memory leak + // notice + // compressionLevel==BestCompression DOES NOT MATTER + // sync.Pool will not memory leak switch level { case gzipCompressLevel: @@ -119,10 +119,10 @@ var ( bestCompressionPool: &sync.Pool{New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, flate.BestCompression); return wr }}, } - //according to the sec :http://tools.ietf.org/html/rfc2616#section-3.5 ,the deflate compress in http is zlib indeed - //deflate - //The "zlib" format defined in RFC 1950 [31] in combination with - //the "deflate" compression mechanism described in RFC 1951 [29]. + // According to: http://tools.ietf.org/html/rfc2616#section-3.5 the deflate compress in http is zlib indeed + // deflate + // The "zlib" format defined in RFC 1950 [31] in combination with + // the "deflate" compression mechanism described in RFC 1951 [29]. deflateCompressEncoder = acceptEncoder{ name: "deflate", levelEncode: func(level int) resetWriter { wr, _ := zlib.NewWriterLevel(nil, level); return wr }, @@ -145,7 +145,7 @@ func WriteFile(encoding string, writer io.Writer, file *os.File) (bool, string, return writeLevel(encoding, writer, file, flate.BestCompression) } -// WriteBody reads writes content to writer by the specific encoding(gzip/deflate) +// WriteBody reads writes content to writer by the specific encoding(gzip/deflate) func WriteBody(encoding string, writer io.Writer, content []byte) (bool, string, error) { if encoding == "" || len(content) < gzipMinLength { _, err := writer.Write(content) @@ -154,8 +154,8 @@ func WriteBody(encoding string, writer io.Writer, content []byte) (bool, string, return writeLevel(encoding, writer, bytes.NewReader(content), gzipCompressLevel) } -// writeLevel reads from reader,writes to writer by specific encoding and compress level -// the compress level is defined by deflate package +// writeLevel reads from reader and writes to writer by specific encoding and compress level. +// The compress level is defined by deflate package func writeLevel(encoding string, writer io.Writer, reader io.Reader, level int) (bool, string, error) { var outputWriter resetWriter var err error diff --git a/context/acceptencoder_test.go b/server/web/context/acceptencoder_test.go similarity index 100% rename from context/acceptencoder_test.go rename to server/web/context/acceptencoder_test.go diff --git a/context/context.go b/server/web/context/context.go similarity index 80% rename from context/context.go rename to server/web/context/context.go index de248ed2..53ed3d01 100644 --- a/context/context.go +++ b/server/web/context/context.go @@ -35,10 +35,10 @@ import ( "strings" "time" - "github.com/astaxie/beego/utils" + "github.com/astaxie/beego/core/utils" ) -//commonly used mime-types +// Commonly used mime-types const ( ApplicationJSON = "application/json" ApplicationXML = "application/xml" @@ -55,7 +55,7 @@ func NewContext() *Context { } // Context Http request context struct including BeegoInput, BeegoOutput, http.Request and http.ResponseWriter. -// BeegoInput and BeegoOutput provides some api to operate request and response more easily. +// BeegoInput and BeegoOutput provides an api to operate request and response more easily. type Context struct { Input *BeegoInput Output *BeegoOutput @@ -64,7 +64,7 @@ type Context struct { _xsrfToken string } -// Reset init Context, BeegoInput and BeegoOutput +// Reset initializes Context, BeegoInput and BeegoOutput func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) { ctx.Request = r if ctx.ResponseWriter == nil { @@ -76,37 +76,36 @@ func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) { ctx._xsrfToken = "" } -// Redirect does redirection to localurl with http header status code. +// Redirect redirects to localurl with http header status code. func (ctx *Context) Redirect(status int, localurl string) { http.Redirect(ctx.ResponseWriter, ctx.Request, localurl, status) } -// Abort stops this request. -// if beego.ErrorMaps exists, panic body. +// Abort stops the request. +// If beego.ErrorMaps exists, panic body. func (ctx *Context) Abort(status int, body string) { ctx.Output.SetStatus(status) panic(body) } -// WriteString Write string to response body. -// it sends response body. +// WriteString writes a string to response body. func (ctx *Context) WriteString(content string) { ctx.ResponseWriter.Write([]byte(content)) } -// GetCookie Get cookie from request by a given key. -// It's alias of BeegoInput.Cookie. +// GetCookie gets a cookie from a request for a given key. +// (Alias of BeegoInput.Cookie) func (ctx *Context) GetCookie(key string) string { return ctx.Input.Cookie(key) } -// SetCookie Set cookie for response. -// It's alias of BeegoOutput.Cookie. +// SetCookie sets a cookie for a response. +// (Alias of BeegoOutput.Cookie) func (ctx *Context) SetCookie(name string, value string, others ...interface{}) { ctx.Output.Cookie(name, value, others...) } -// GetSecureCookie Get secure cookie from request by a given key. +// GetSecureCookie gets a secure cookie from a request for a given key. func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) { val := ctx.Input.Cookie(key) if val == "" { @@ -133,7 +132,7 @@ func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) { return string(res), true } -// SetSecureCookie Set Secure cookie for response. +// SetSecureCookie sets a secure cookie for a response. func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) { vs := base64.URLEncoding.EncodeToString([]byte(value)) timestamp := strconv.FormatInt(time.Now().UnixNano(), 10) @@ -144,21 +143,21 @@ func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interf ctx.Output.Cookie(name, cookie, others...) } -// XSRFToken creates a xsrf token string and returns. +// XSRFToken creates and returns an xsrf token string func (ctx *Context) XSRFToken(key string, expire int64) string { if ctx._xsrfToken == "" { token, ok := ctx.GetSecureCookie(key, "_xsrf") if !ok { token = string(utils.RandomCreateBytes(32)) - ctx.SetSecureCookie(key, "_xsrf", token, expire) + ctx.SetSecureCookie(key, "_xsrf", token, expire, "", "", true, true) } ctx._xsrfToken = token } return ctx._xsrfToken } -// CheckXSRFCookie checks xsrf token in this request is valid or not. -// the token can provided in request header "X-Xsrftoken" and "X-CsrfToken" +// CheckXSRFCookie checks if the XSRF token in this request is valid or not. +// The token can be provided in the request header in the form "X-Xsrftoken" or "X-CsrfToken" // or in form field value named as "_xsrf". func (ctx *Context) CheckXSRFCookie() bool { token := ctx.Input.Query("_xsrf") @@ -195,8 +194,8 @@ func (ctx *Context) RenderMethodResult(result interface{}) { } } -//Response is a wrapper for the http.ResponseWriter -//started set to true if response was written to then don't execute other handler +// Response is a wrapper for the http.ResponseWriter +// Started: if true, response was already written to so the other handler will not be executed type Response struct { http.ResponseWriter Started bool @@ -210,16 +209,16 @@ func (r *Response) reset(rw http.ResponseWriter) { r.Started = false } -// Write writes the data to the connection as part of an HTTP reply, -// and sets `started` to true. -// started means the response has sent out. +// Write writes the data to the connection as part of a HTTP reply, +// and sets `Started` to true. +// Started: if true, the response was already sent func (r *Response) Write(p []byte) (int, error) { r.Started = true return r.ResponseWriter.Write(p) } -// WriteHeader sends an HTTP response header with status code, -// and sets `started` to true. +// WriteHeader sends a HTTP response header with status code, +// and sets `Started` to true. func (r *Response) WriteHeader(code int) { if r.Status > 0 { //prevent multiple response.WriteHeader calls diff --git a/context/context_test.go b/server/web/context/context_test.go similarity index 100% rename from context/context_test.go rename to server/web/context/context_test.go diff --git a/context/input.go b/server/web/context/input.go similarity index 92% rename from context/input.go rename to server/web/context/input.go index 7b522c36..504838a3 100644 --- a/context/input.go +++ b/server/web/context/input.go @@ -29,7 +29,7 @@ import ( "strings" "sync" - "github.com/astaxie/beego/session" + "github.com/astaxie/beego/server/web/session" ) // Regexes for checking the accept headers @@ -43,7 +43,7 @@ var ( ) // BeegoInput operates the http request header, data, cookie and body. -// it also contains router params and current session. +// Contains router params and current session. type BeegoInput struct { Context *Context CruSession session.Store @@ -56,7 +56,7 @@ type BeegoInput struct { RunController reflect.Type } -// NewInput return BeegoInput generated by Context. +// NewInput returns the BeegoInput generated by context. func NewInput() *BeegoInput { return &BeegoInput{ pnames: make([]string, 0, maxParam), @@ -65,7 +65,7 @@ func NewInput() *BeegoInput { } } -// Reset init the BeegoInput +// Reset initializes the BeegoInput func (input *BeegoInput) Reset(ctx *Context) { input.Context = ctx input.CruSession = nil @@ -77,27 +77,27 @@ func (input *BeegoInput) Reset(ctx *Context) { input.RequestBody = []byte{} } -// Protocol returns request protocol name, such as HTTP/1.1 . +// Protocol returns the request protocol name, such as HTTP/1.1 . func (input *BeegoInput) Protocol() string { return input.Context.Request.Proto } -// URI returns full request url with query string, fragment. +// URI returns the full request url with query, string and fragment. func (input *BeegoInput) URI() string { return input.Context.Request.RequestURI } -// URL returns request url path (without query string, fragment). +// URL returns the request url path (without query, string and fragment). func (input *BeegoInput) URL() string { - return input.Context.Request.URL.EscapedPath() + return input.Context.Request.URL.Path } -// Site returns base site url as scheme://domain type. +// Site returns the base site url as scheme://domain type. func (input *BeegoInput) Site() string { return input.Scheme() + "://" + input.Domain() } -// Scheme returns request scheme as "http" or "https". +// Scheme returns the request scheme as "http" or "https". func (input *BeegoInput) Scheme() string { if scheme := input.Header("X-Forwarded-Proto"); scheme != "" { return scheme @@ -111,14 +111,13 @@ func (input *BeegoInput) Scheme() string { return "https" } -// Domain returns host name. -// Alias of Host method. +// Domain returns the host name (alias of host method) func (input *BeegoInput) Domain() string { return input.Host() } -// Host returns host name. -// if no host info in request, return localhost. +// Host returns the host name. +// If no host info in request, return localhost. func (input *BeegoInput) Host() string { if input.Context.Request.Host != "" { if hostPart, _, err := net.SplitHostPort(input.Context.Request.Host); err == nil { @@ -134,7 +133,7 @@ func (input *BeegoInput) Method() string { return input.Context.Request.Method } -// Is returns boolean of this request is on given method, such as Is("POST"). +// Is returns the boolean value of this request is on given method, such as Is("POST"). func (input *BeegoInput) Is(method string) bool { return input.Method() == method } @@ -174,7 +173,7 @@ func (input *BeegoInput) IsPatch() bool { return input.Is("PATCH") } -// IsAjax returns boolean of this request is generated by ajax. +// IsAjax returns boolean of is this request generated by ajax. func (input *BeegoInput) IsAjax() bool { return input.Header("X-Requested-With") == "XMLHttpRequest" } @@ -251,7 +250,7 @@ func (input *BeegoInput) Refer() string { } // SubDomains returns sub domain string. -// if aa.bb.domain.com, returns aa.bb . +// if aa.bb.domain.com, returns aa.bb func (input *BeegoInput) SubDomains() string { parts := strings.Split(input.Host(), ".") if len(parts) >= 3 { @@ -306,7 +305,7 @@ func (input *BeegoInput) Params() map[string]string { return m } -// SetParam will set the param with key and value +// SetParam sets the param with key and value func (input *BeegoInput) SetParam(key, val string) { // check if already exists for i, v := range input.pnames { @@ -319,9 +318,8 @@ func (input *BeegoInput) SetParam(key, val string) { input.pnames = append(input.pnames, key) } -// ResetParams clears any of the input's Params -// This function is used to clear parameters so they may be reset between filter -// passes. +// ResetParams clears any of the input's params +// Used to clear parameters so they may be reset between filter passes. func (input *BeegoInput) ResetParams() { input.pnames = input.pnames[:0] input.pvalues = input.pvalues[:0] @@ -333,8 +331,14 @@ func (input *BeegoInput) Query(key string) string { return val } if input.Context.Request.Form == nil { - input.Context.Request.ParseForm() + input.dataLock.Lock() + if input.Context.Request.Form == nil { + input.Context.Request.ParseForm() + } + input.dataLock.Unlock() } + input.dataLock.RLock() + defer input.dataLock.RUnlock() return input.Context.Request.Form.Get(key) } @@ -357,7 +361,7 @@ func (input *BeegoInput) Cookie(key string) string { // Session returns current session item value by a given key. // if non-existed, return nil. func (input *BeegoInput) Session(key interface{}) interface{} { - return input.CruSession.Get(key) + return input.CruSession.Get(nil, key) } // CopyBody returns the raw request body data as bytes. @@ -385,7 +389,7 @@ func (input *BeegoInput) CopyBody(MaxMemory int64) []byte { return requestbody } -// Data return the implicit data in the input +// Data returns the implicit data in the input func (input *BeegoInput) Data() map[interface{}]interface{} { input.dataLock.Lock() defer input.dataLock.Unlock() @@ -406,7 +410,7 @@ func (input *BeegoInput) GetData(key interface{}) interface{} { } // SetData stores data with given key in this context. -// This data are only available in this context. +// This data is only available in this context. func (input *BeegoInput) SetData(key, val interface{}) { input.dataLock.Lock() defer input.dataLock.Unlock() @@ -416,10 +420,10 @@ func (input *BeegoInput) SetData(key, val interface{}) { input.data[key] = val } -// ParseFormOrMulitForm parseForm or parseMultiForm based on Content-type -func (input *BeegoInput) ParseFormOrMulitForm(maxMemory int64) error { +// ParseFormOrMultiForm parseForm or parseMultiForm based on Content-type +func (input *BeegoInput) ParseFormOrMultiForm(maxMemory int64) error { // Parse the body depending on the content type. - if strings.Contains(input.Header("Content-Type"), "multipart/form-data") { + if input.IsUpload() { if err := input.Context.Request.ParseMultipartForm(maxMemory); err != nil { return errors.New("Error parsing request body:" + err.Error()) } diff --git a/context/input_test.go b/server/web/context/input_test.go similarity index 95% rename from context/input_test.go rename to server/web/context/input_test.go index db812a0f..3a6c2e7b 100644 --- a/context/input_test.go +++ b/server/web/context/input_test.go @@ -205,3 +205,13 @@ func TestParams(t *testing.T) { } } +func BenchmarkQuery(b *testing.B) { + beegoInput := NewInput() + beegoInput.Context = NewContext() + beegoInput.Context.Request, _ = http.NewRequest("POST", "http://www.example.com/?q=foo", nil) + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + beegoInput.Query("q") + } + }) +} diff --git a/context/output.go b/server/web/context/output.go similarity index 88% rename from context/output.go rename to server/web/context/output.go index 238dcf45..a6e83681 100644 --- a/context/output.go +++ b/server/web/context/output.go @@ -42,12 +42,12 @@ type BeegoOutput struct { } // NewOutput returns new BeegoOutput. -// it contains nothing now. +// Empty when initialized func NewOutput() *BeegoOutput { return &BeegoOutput{} } -// Reset init BeegoOutput +// Reset initializes BeegoOutput func (output *BeegoOutput) Reset(ctx *Context) { output.Context = ctx output.Status = 0 @@ -58,9 +58,9 @@ func (output *BeegoOutput) Header(key, val string) { output.Context.ResponseWriter.Header().Set(key, val) } -// Body sets response body content. -// if EnableGzip, compress content string. -// it sends out response body directly. +// Body sets the response body content. +// if EnableGzip, content is compressed. +// Sends out response body directly. func (output *BeegoOutput) Body(content []byte) error { var encoding string var buf = &bytes.Buffer{} @@ -85,13 +85,13 @@ func (output *BeegoOutput) Body(content []byte) error { return nil } -// Cookie sets cookie value via given key. -// others are ordered as cookie's max age time, path,domain, secure and httponly. +// Cookie sets a cookie value via given key. +// others: used to set a cookie's max age time, path,domain, secure and httponly. func (output *BeegoOutput) Cookie(name string, value string, others ...interface{}) { var b bytes.Buffer fmt.Fprintf(&b, "%s=%s", sanitizeName(name), sanitizeValue(value)) - //fix cookie not work in IE + // fix cookie not work in IE if len(others) > 0 { var maxAge int64 @@ -183,7 +183,7 @@ func errorRenderer(err error) Renderer { }) } -// JSON writes json to response body. +// JSON writes json to the response body. // if encoding is true, it converts utf-8 to \u0000 type. func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) error { output.Header("Content-Type", "application/json; charset=utf-8") @@ -204,7 +204,7 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) return output.Body(content) } -// YAML writes yaml to response body. +// YAML writes yaml to the response body. func (output *BeegoOutput) YAML(data interface{}) error { output.Header("Content-Type", "application/x-yaml; charset=utf-8") var content []byte @@ -217,7 +217,7 @@ func (output *BeegoOutput) YAML(data interface{}) error { return output.Body(content) } -// JSONP writes jsonp to response body. +// JSONP writes jsonp to the response body. func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error { output.Header("Content-Type", "application/javascript; charset=utf-8") var content []byte @@ -243,7 +243,7 @@ func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error { return output.Body(callbackContent.Bytes()) } -// XML writes xml string to response body. +// XML writes xml string to the response body. func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error { output.Header("Content-Type", "application/xml; charset=utf-8") var content []byte @@ -260,7 +260,7 @@ func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error { return output.Body(content) } -// ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header +// ServeFormatted serves YAML, XML or JSON, depending on the value of the Accept header func (output *BeegoOutput) ServeFormatted(data interface{}, hasIndent bool, hasEncode ...bool) { accept := output.Context.Input.Header("Accept") switch accept { @@ -274,7 +274,7 @@ func (output *BeegoOutput) ServeFormatted(data interface{}, hasIndent bool, hasE } // Download forces response for download file. -// it prepares the download response header automatically. +// Prepares the download response header automatically. func (output *BeegoOutput) Download(file string, filename ...string) { // check get file error, file not found or other error. if _, err := os.Stat(file); err != nil { @@ -323,61 +323,61 @@ func (output *BeegoOutput) ContentType(ext string) { } } -// SetStatus sets response status code. -// It writes response header directly. +// SetStatus sets the response status code. +// Writes response header directly. func (output *BeegoOutput) SetStatus(status int) { output.Status = status } -// IsCachable returns boolean of this request is cached. +// IsCachable returns boolean of if this request is cached. // HTTP 304 means cached. func (output *BeegoOutput) IsCachable() bool { return output.Status >= 200 && output.Status < 300 || output.Status == 304 } -// IsEmpty returns boolean of this request is empty. +// IsEmpty returns boolean of if this request is empty. // HTTP 201,204 and 304 means empty. func (output *BeegoOutput) IsEmpty() bool { return output.Status == 201 || output.Status == 204 || output.Status == 304 } -// IsOk returns boolean of this request runs well. +// IsOk returns boolean of if this request was ok. // HTTP 200 means ok. func (output *BeegoOutput) IsOk() bool { return output.Status == 200 } -// IsSuccessful returns boolean of this request runs successfully. +// IsSuccessful returns boolean of this request was successful. // HTTP 2xx means ok. func (output *BeegoOutput) IsSuccessful() bool { return output.Status >= 200 && output.Status < 300 } -// IsRedirect returns boolean of this request is redirection header. +// IsRedirect returns boolean of if this request is redirected. // HTTP 301,302,307 means redirection. func (output *BeegoOutput) IsRedirect() bool { return output.Status == 301 || output.Status == 302 || output.Status == 303 || output.Status == 307 } -// IsForbidden returns boolean of this request is forbidden. +// IsForbidden returns boolean of if this request is forbidden. // HTTP 403 means forbidden. func (output *BeegoOutput) IsForbidden() bool { return output.Status == 403 } -// IsNotFound returns boolean of this request is not found. +// IsNotFound returns boolean of if this request is not found. // HTTP 404 means not found. func (output *BeegoOutput) IsNotFound() bool { return output.Status == 404 } -// IsClientError returns boolean of this request client sends error data. +// IsClientError returns boolean of if this request client sends error data. // HTTP 4xx means client error. func (output *BeegoOutput) IsClientError() bool { return output.Status >= 400 && output.Status < 500 } -// IsServerError returns boolean of this server handler errors. +// IsServerError returns boolean of if this server handler errors. // HTTP 5xx means server internal error. func (output *BeegoOutput) IsServerError() bool { return output.Status >= 500 && output.Status < 600 @@ -404,5 +404,5 @@ func stringsToJSON(str string) string { // Session sets session item value with given key. func (output *BeegoOutput) Session(name interface{}, value interface{}) { - output.Context.Input.CruSession.Set(name, value) + output.Context.Input.CruSession.Set(nil, name, value) } diff --git a/context/param/conv.go b/server/web/context/param/conv.go similarity index 95% rename from context/param/conv.go rename to server/web/context/param/conv.go index c200e008..fe3388b6 100644 --- a/context/param/conv.go +++ b/server/web/context/param/conv.go @@ -4,8 +4,8 @@ import ( "fmt" "reflect" - beecontext "github.com/astaxie/beego/context" - "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/core/logs" + beecontext "github.com/astaxie/beego/server/web/context" ) // ConvertParams converts http method params to values that will be passed to the method controller as arguments diff --git a/context/param/methodparams.go b/server/web/context/param/methodparams.go similarity index 91% rename from context/param/methodparams.go rename to server/web/context/param/methodparams.go index cd6708a2..b5ccbdd0 100644 --- a/context/param/methodparams.go +++ b/server/web/context/param/methodparams.go @@ -22,7 +22,7 @@ const ( header ) -//New creates a new MethodParam with name and specific options +// New creates a new MethodParam with name and specific options func New(name string, opts ...MethodParamOption) *MethodParam { return newParam(name, nil, opts) } @@ -35,7 +35,7 @@ func newParam(name string, parser paramParser, opts []MethodParamOption) (param return } -//Make creates an array of MethodParmas or an empty array +// Make creates an array of MethodParmas or an empty array func Make(list ...*MethodParam) []*MethodParam { if len(list) > 0 { return list diff --git a/context/param/options.go b/server/web/context/param/options.go similarity index 100% rename from context/param/options.go rename to server/web/context/param/options.go diff --git a/context/param/parsers.go b/server/web/context/param/parsers.go similarity index 100% rename from context/param/parsers.go rename to server/web/context/param/parsers.go diff --git a/context/param/parsers_test.go b/server/web/context/param/parsers_test.go similarity index 98% rename from context/param/parsers_test.go rename to server/web/context/param/parsers_test.go index 7065a28e..81a821f1 100644 --- a/context/param/parsers_test.go +++ b/server/web/context/param/parsers_test.go @@ -1,8 +1,10 @@ package param -import "testing" -import "reflect" -import "time" +import ( + "reflect" + "testing" + "time" +) type testDefinition struct { strValue string diff --git a/context/renderer.go b/server/web/context/renderer.go similarity index 77% rename from context/renderer.go rename to server/web/context/renderer.go index 36a7cb53..5a078332 100644 --- a/context/renderer.go +++ b/server/web/context/renderer.go @@ -1,6 +1,6 @@ package context -// Renderer defines an http response renderer +// Renderer defines a http response renderer type Renderer interface { Render(ctx *Context) } diff --git a/server/web/context/response.go b/server/web/context/response.go new file mode 100644 index 00000000..7bd9a7e8 --- /dev/null +++ b/server/web/context/response.go @@ -0,0 +1,26 @@ +package context + +import ( + "net/http" + "strconv" +) + +const ( + //BadRequest indicates HTTP error 400 + BadRequest StatusCode = http.StatusBadRequest + + //NotFound indicates HTTP error 404 + NotFound StatusCode = http.StatusNotFound +) + +// StatusCode sets the HTTP response status code +type StatusCode int + +func (s StatusCode) Error() string { + return strconv.Itoa(int(s)) +} + +// Render sets the HTTP status code +func (s StatusCode) Render(ctx *Context) { + ctx.Output.SetStatus(int(s)) +} diff --git a/controller.go b/server/web/controller.go similarity index 98% rename from controller.go rename to server/web/controller.go index 0e8853b3..3a1b9837 100644 --- a/controller.go +++ b/server/web/controller.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "bytes" @@ -28,9 +28,10 @@ import ( "strconv" "strings" - "github.com/astaxie/beego/context" - "github.com/astaxie/beego/context/param" - "github.com/astaxie/beego/session" + "github.com/astaxie/beego/server/web/session" + + "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/server/web/context/param" ) var ( @@ -621,7 +622,7 @@ func (c *Controller) SetSession(name interface{}, value interface{}) { if c.CruSession == nil { c.StartSession() } - c.CruSession.Set(name, value) + c.CruSession.Set(nil, name, value) } // GetSession gets value from session. @@ -629,7 +630,7 @@ func (c *Controller) GetSession(name interface{}) interface{} { if c.CruSession == nil { c.StartSession() } - return c.CruSession.Get(name) + return c.CruSession.Get(nil, name) } // DelSession removes value from session. @@ -637,14 +638,14 @@ func (c *Controller) DelSession(name interface{}) { if c.CruSession == nil { c.StartSession() } - c.CruSession.Delete(name) + c.CruSession.Delete(nil, name) } // SessionRegenerateID regenerates session id for this session. // the session data have no changes. func (c *Controller) SessionRegenerateID() { if c.CruSession != nil { - c.CruSession.SessionRelease(c.Ctx.ResponseWriter) + c.CruSession.SessionRelease(nil, c.Ctx.ResponseWriter) } c.CruSession = GlobalSessions.SessionRegenerateID(c.Ctx.ResponseWriter, c.Ctx.Request) c.Ctx.Input.CruSession = c.CruSession @@ -652,7 +653,7 @@ func (c *Controller) SessionRegenerateID() { // DestroySession cleans session data and session cookie. func (c *Controller) DestroySession() { - c.Ctx.Input.CruSession.Flush() + c.Ctx.Input.CruSession.Flush(nil) c.Ctx.Input.CruSession = nil GlobalSessions.SessionDestroy(c.Ctx.ResponseWriter, c.Ctx.Request) } diff --git a/controller_test.go b/server/web/controller_test.go similarity index 94% rename from controller_test.go rename to server/web/controller_test.go index 1e53416d..0b711e0d 100644 --- a/controller_test.go +++ b/server/web/controller_test.go @@ -12,16 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "math" + "os" + "path/filepath" "strconv" "testing" - "github.com/astaxie/beego/context" - "os" - "path/filepath" + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/server/web/context" ) func TestGetInt(t *testing.T) { @@ -125,8 +127,10 @@ func TestGetUint64(t *testing.T) { } func TestAdditionalViewPaths(t *testing.T) { - dir1 := "_beeTmp" - dir2 := "_beeTmp2" + wkdir, err := os.Getwd() + assert.Nil(t, err) + dir1 := filepath.Join(wkdir, "_beeTmp", "TestAdditionalViewPaths") + dir2 := filepath.Join(wkdir, "_beeTmp2", "TestAdditionalViewPaths") defer os.RemoveAll(dir1) defer os.RemoveAll(dir2) diff --git a/server/web/doc.go b/server/web/doc.go new file mode 100644 index 00000000..a32bc576 --- /dev/null +++ b/server/web/doc.go @@ -0,0 +1,17 @@ +/* +Package beego provide a MVC framework +beego: an open-source, high-performance, modular, full-stack web framework + +It is used for rapid development of RESTful APIs, web apps and backend services in Go. +beego is inspired by Tornado, Sinatra and Flask with the added benefit of some Go-specific features such as interfaces and struct embedding. + + package main + import "github.com/astaxie/beego" + + func main() { + beego.Run() + } + +more information: http://beego.me +*/ +package web diff --git a/error.go b/server/web/error.go similarity index 94% rename from error.go rename to server/web/error.go index e5e9fd47..b5ef1d2d 100644 --- a/error.go +++ b/server/web/error.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "fmt" @@ -23,12 +23,14 @@ import ( "strconv" "strings" - "github.com/astaxie/beego/context" - "github.com/astaxie/beego/utils" + "github.com/astaxie/beego" + "github.com/astaxie/beego/core/utils" + + "github.com/astaxie/beego/server/web/context" ) const ( - errorTypeHandler = iota + errorTypeHandler = iota errorTypeController ) @@ -90,7 +92,7 @@ func showErr(err interface{}, ctx *context.Context, stack string) { "RequestURL": ctx.Input.URI(), "RemoteAddr": ctx.Input.IP(), "Stack": stack, - "BeegoVersion": VERSION, + "BeegoVersion": beego.VERSION, "GoVersion": runtime.Version(), } t.Execute(ctx.ResponseWriter, data) @@ -359,11 +361,25 @@ func gatewayTimeout(rw http.ResponseWriter, r *http.Request) { ) } +// show 413 Payload Too Large +func payloadTooLarge(rw http.ResponseWriter, r *http.Request) { + responseError(rw, r, + 413, + `
The page you have requested is unavailable. +
Perhaps you are here because:

+
    +
    The request entity is larger than limits defined by server. +
    Please change the request entity and try again. +
+ `, + ) +} + func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errContent string) { t, _ := template.New("beegoerrortemp").Parse(errtpl) data := M{ "Title": http.StatusText(errCode), - "BeegoVersion": VERSION, + "BeegoVersion": beego.VERSION, "Content": template.HTML(errContent), } t.Execute(rw, data) @@ -373,7 +389,7 @@ func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errCont // usage: // beego.ErrorHandler("404",NotFound) // beego.ErrorHandler("500",InternalServerError) -func ErrorHandler(code string, h http.HandlerFunc) *App { +func ErrorHandler(code string, h http.HandlerFunc) *HttpServer { ErrorMaps[code] = &errorInfo{ errorType: errorTypeHandler, handler: h, @@ -385,7 +401,7 @@ func ErrorHandler(code string, h http.HandlerFunc) *App { // ErrorController registers ControllerInterface to each http err code string. // usage: // beego.ErrorController(&controllers.ErrorController{}) -func ErrorController(c ControllerInterface) *App { +func ErrorController(c ControllerInterface) *HttpServer { reflectVal := reflect.ValueOf(c) rt := reflectVal.Type() ct := reflect.Indirect(reflectVal).Type() diff --git a/error_test.go b/server/web/error_test.go similarity index 99% rename from error_test.go rename to server/web/error_test.go index 378aa953..2685a985 100644 --- a/error_test.go +++ b/server/web/error_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "net/http" diff --git a/server/web/filter.go b/server/web/filter.go new file mode 100644 index 00000000..967de8c9 --- /dev/null +++ b/server/web/filter.go @@ -0,0 +1,134 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 web + +import ( + "strings" + + "github.com/astaxie/beego/server/web/context" +) + +// FilterChain is different from pure FilterFunc +// when you use this, you must invoke next(ctx) inside the FilterFunc which is returned +// And all those FilterChain will be invoked before other FilterFunc +type FilterChain func(next FilterFunc) FilterFunc + +// FilterFunc defines a filter function which is invoked before the controller handler is executed. +type FilterFunc func(ctx *context.Context) + +// FilterRouter defines a filter operation which is invoked before the controller handler is executed. +// It can match the URL against a pattern, and execute a filter function +// when a request with a matching URL arrives. +type FilterRouter struct { + filterFunc FilterFunc + next *FilterRouter + tree *Tree + pattern string + returnOnOutput bool + resetParams bool +} + +// params is for: +// 1. setting the returnOnOutput value (false allows multiple filters to execute) +// 2. determining whether or not params need to be reset. +func newFilterRouter(pattern string, filter FilterFunc, opts ...FilterOpt) *FilterRouter { + mr := &FilterRouter{ + tree: NewTree(), + pattern: pattern, + filterFunc: filter, + } + + fos := &filterOpts{ + returnOnOutput: true, + } + + for _, o := range opts { + o(fos) + } + + if !fos.routerCaseSensitive { + mr.pattern = strings.ToLower(pattern) + } + + mr.returnOnOutput = fos.returnOnOutput + mr.resetParams = fos.resetParams + mr.tree.AddRouter(pattern, true) + return mr +} + +// filter will check whether we need to execute the filter logic +// return (started, done) +func (f *FilterRouter) filter(ctx *context.Context, urlPath string, preFilterParams map[string]string) (bool, bool) { + if f.returnOnOutput && ctx.ResponseWriter.Started { + return true, true + } + if f.resetParams { + preFilterParams = ctx.Input.Params() + } + if ok := f.ValidRouter(urlPath, ctx); ok { + f.filterFunc(ctx) + if f.resetParams { + ctx.Input.ResetParams() + for k, v := range preFilterParams { + ctx.Input.SetParam(k, v) + } + } + } else if f.next != nil { + return f.next.filter(ctx, urlPath, preFilterParams) + } + if f.returnOnOutput && ctx.ResponseWriter.Started { + return true, true + } + return false, false +} + +// ValidRouter checks if the current request is matched by this filter. +// If the request is matched, the values of the URL parameters defined +// by the filter pattern are also returned. +func (f *FilterRouter) ValidRouter(url string, ctx *context.Context) bool { + isOk := f.tree.Match(url, ctx) + if isOk != nil { + if b, ok := isOk.(bool); ok { + return b + } + } + return false +} + +type filterOpts struct { + returnOnOutput bool + resetParams bool + routerCaseSensitive bool +} + +type FilterOpt func(opts *filterOpts) + +func WithReturnOnOutput(ret bool) FilterOpt { + return func(opts *filterOpts) { + opts.returnOnOutput = ret + } +} + +func WithResetParams(reset bool) FilterOpt { + return func(opts *filterOpts) { + opts.resetParams = reset + } +} + +func WithCaseSensitive(sensitive bool) FilterOpt { + return func(opts *filterOpts) { + opts.routerCaseSensitive = sensitive + } +} diff --git a/plugins/apiauth/apiauth.go b/server/web/filter/apiauth/apiauth.go similarity index 77% rename from plugins/apiauth/apiauth.go rename to server/web/filter/apiauth/apiauth.go index 10e25f3f..58153f1d 100644 --- a/plugins/apiauth/apiauth.go +++ b/server/web/filter/apiauth/apiauth.go @@ -65,15 +65,15 @@ import ( "sort" "time" - "github.com/astaxie/beego" - "github.com/astaxie/beego/context" + "github.com/astaxie/beego/server/web" + "github.com/astaxie/beego/server/web/context" ) -// AppIDToAppSecret is used to get appsecret throw appid +// AppIDToAppSecret gets appsecret through appid type AppIDToAppSecret func(string) string -// APIBasicAuth use the basic appid/appkey as the AppIdToAppSecret -func APIBasicAuth(appid, appkey string) beego.FilterFunc { +// APIBasicAuth uses the basic appid/appkey as the AppIdToAppSecret +func APIBasicAuth(appid, appkey string) web.FilterFunc { ft := func(aid string) string { if aid == appid { return appkey @@ -83,56 +83,53 @@ func APIBasicAuth(appid, appkey string) beego.FilterFunc { return APISecretAuth(ft, 300) } -// APIBaiscAuth calls APIBasicAuth for previous callers -func APIBaiscAuth(appid, appkey string) beego.FilterFunc { - return APIBasicAuth(appid, appkey) -} - -// APISecretAuth use AppIdToAppSecret verify and -func APISecretAuth(f AppIDToAppSecret, timeout int) beego.FilterFunc { +// APISecretAuth uses AppIdToAppSecret verify and +func APISecretAuth(f AppIDToAppSecret, timeout int) web.FilterFunc { return func(ctx *context.Context) { if ctx.Input.Query("appid") == "" { ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("miss query param: appid") + ctx.WriteString("missing query parameter: appid") return } appsecret := f(ctx.Input.Query("appid")) if appsecret == "" { ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("not exist this appid") + ctx.WriteString("appid query parameter missing") return } if ctx.Input.Query("signature") == "" { ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("miss query param: signature") + ctx.WriteString("missing query parameter: signature") + return } if ctx.Input.Query("timestamp") == "" { ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("miss query param: timestamp") + ctx.WriteString("missing query parameter: timestamp") return } u, err := time.Parse("2006-01-02 15:04:05", ctx.Input.Query("timestamp")) if err != nil { ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("timestamp format is error, should 2006-01-02 15:04:05") + ctx.WriteString("incorrect timestamp format. Should be in the form 2006-01-02 15:04:05") + return } t := time.Now() if t.Sub(u).Seconds() > float64(timeout) { ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("timeout! the request time is long ago, please try again") + ctx.WriteString("request timer timeout exceeded. Please try again") return } if ctx.Input.Query("signature") != Signature(appsecret, ctx.Input.Method(), ctx.Request.Form, ctx.Input.URL()) { ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("auth failed") + ctx.WriteString("authentication failed") } } } -// Signature used to generate signature with the appsecret/method/params/RequestURI +// Signature generates signature with appsecret/method/params/RequestURI func Signature(appsecret, method string, params url.Values, RequestURL string) (result string) { var b bytes.Buffer keys := make([]string, len(params)) diff --git a/server/web/filter/apiauth/apiauth_test.go b/server/web/filter/apiauth/apiauth_test.go new file mode 100644 index 00000000..1f56cb0f --- /dev/null +++ b/server/web/filter/apiauth/apiauth_test.go @@ -0,0 +1,20 @@ +package apiauth + +import ( + "net/url" + "testing" +) + +func TestSignature(t *testing.T) { + appsecret := "beego secret" + method := "GET" + RequestURL := "http://localhost/test/url" + params := make(url.Values) + params.Add("arg1", "hello") + params.Add("arg2", "beego") + + signature := "mFdpvLh48ca4mDVEItE9++AKKQ/IVca7O/ZyyB8hR58=" + if Signature(appsecret, method, params, RequestURL) != signature { + t.Error("Signature error") + } +} diff --git a/plugins/auth/basic.go b/server/web/filter/auth/basic.go similarity index 94% rename from plugins/auth/basic.go rename to server/web/filter/auth/basic.go index c478044a..ee6af6c3 100644 --- a/plugins/auth/basic.go +++ b/server/web/filter/auth/basic.go @@ -40,14 +40,14 @@ import ( "net/http" "strings" - "github.com/astaxie/beego" - "github.com/astaxie/beego/context" + "github.com/astaxie/beego/server/web" + "github.com/astaxie/beego/server/web/context" ) var defaultRealm = "Authorization Required" // Basic is the http basic auth -func Basic(username string, password string) beego.FilterFunc { +func Basic(username string, password string) web.FilterFunc { secrets := func(user, pass string) bool { return user == username && pass == password } @@ -55,7 +55,7 @@ func Basic(username string, password string) beego.FilterFunc { } // NewBasicAuthenticator return the BasicAuth -func NewBasicAuthenticator(secrets SecretProvider, Realm string) beego.FilterFunc { +func NewBasicAuthenticator(secrets SecretProvider, Realm string) web.FilterFunc { return func(ctx *context.Context) { a := &BasicAuth{Secrets: secrets, Realm: Realm} if username := a.CheckAuth(ctx.Request); username == "" { diff --git a/plugins/authz/authz.go b/server/web/filter/authz/authz.go similarity index 94% rename from plugins/authz/authz.go rename to server/web/filter/authz/authz.go index 9dc0db76..857c52f2 100644 --- a/plugins/authz/authz.go +++ b/server/web/filter/authz/authz.go @@ -40,15 +40,17 @@ package authz import ( - "github.com/astaxie/beego" - "github.com/astaxie/beego/context" - "github.com/casbin/casbin" "net/http" + + "github.com/casbin/casbin" + + "github.com/astaxie/beego/server/web" + "github.com/astaxie/beego/server/web/context" ) // NewAuthorizer returns the authorizer. // Use a casbin enforcer as input -func NewAuthorizer(e *casbin.Enforcer) beego.FilterFunc { +func NewAuthorizer(e *casbin.Enforcer) web.FilterFunc { return func(ctx *context.Context) { a := &BasicAuthorizer{enforcer: e} diff --git a/server/web/filter/authz/authz_model.conf b/server/web/filter/authz/authz_model.conf new file mode 100644 index 00000000..d1b3dbd7 --- /dev/null +++ b/server/web/filter/authz/authz_model.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act + +[role_definition] +g = _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*") \ No newline at end of file diff --git a/server/web/filter/authz/authz_policy.csv b/server/web/filter/authz/authz_policy.csv new file mode 100644 index 00000000..c062dd3e --- /dev/null +++ b/server/web/filter/authz/authz_policy.csv @@ -0,0 +1,7 @@ +p, alice, /dataset1/*, GET +p, alice, /dataset1/resource1, POST +p, bob, /dataset2/resource1, * +p, bob, /dataset2/resource2, GET +p, bob, /dataset2/folder1/*, POST +p, dataset1_admin, /dataset1/*, * +g, cathy, dataset1_admin \ No newline at end of file diff --git a/server/web/filter/authz/authz_test.go b/server/web/filter/authz/authz_test.go new file mode 100644 index 00000000..c0d0dde5 --- /dev/null +++ b/server/web/filter/authz/authz_test.go @@ -0,0 +1,109 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 authz + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/casbin/casbin" + + "github.com/astaxie/beego/server/web" + "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/server/web/filter/auth" +) + +func testRequest(t *testing.T, handler *web.ControllerRegister, user string, path string, method string, code int) { + r, _ := http.NewRequest(method, path, nil) + r.SetBasicAuth(user, "123") + w := httptest.NewRecorder() + handler.ServeHTTP(w, r) + + if w.Code != code { + t.Errorf("%s, %s, %s: %d, supposed to be %d", user, path, method, w.Code, code) + } +} + +func TestBasic(t *testing.T) { + handler := web.NewControllerRegister() + + handler.InsertFilter("*", web.BeforeRouter, auth.Basic("alice", "123")) + handler.InsertFilter("*", web.BeforeRouter, NewAuthorizer(casbin.NewEnforcer("authz_model.conf", "authz_policy.csv"))) + + handler.Any("*", func(ctx *context.Context) { + ctx.Output.SetStatus(200) + }) + + testRequest(t, handler, "alice", "/dataset1/resource1", "GET", 200) + testRequest(t, handler, "alice", "/dataset1/resource1", "POST", 200) + testRequest(t, handler, "alice", "/dataset1/resource2", "GET", 200) + testRequest(t, handler, "alice", "/dataset1/resource2", "POST", 403) +} + +func TestPathWildcard(t *testing.T) { + handler := web.NewControllerRegister() + + handler.InsertFilter("*", web.BeforeRouter, auth.Basic("bob", "123")) + handler.InsertFilter("*", web.BeforeRouter, NewAuthorizer(casbin.NewEnforcer("authz_model.conf", "authz_policy.csv"))) + + handler.Any("*", func(ctx *context.Context) { + ctx.Output.SetStatus(200) + }) + + testRequest(t, handler, "bob", "/dataset2/resource1", "GET", 200) + testRequest(t, handler, "bob", "/dataset2/resource1", "POST", 200) + testRequest(t, handler, "bob", "/dataset2/resource1", "DELETE", 200) + testRequest(t, handler, "bob", "/dataset2/resource2", "GET", 200) + testRequest(t, handler, "bob", "/dataset2/resource2", "POST", 403) + testRequest(t, handler, "bob", "/dataset2/resource2", "DELETE", 403) + + testRequest(t, handler, "bob", "/dataset2/folder1/item1", "GET", 403) + testRequest(t, handler, "bob", "/dataset2/folder1/item1", "POST", 200) + testRequest(t, handler, "bob", "/dataset2/folder1/item1", "DELETE", 403) + testRequest(t, handler, "bob", "/dataset2/folder1/item2", "GET", 403) + testRequest(t, handler, "bob", "/dataset2/folder1/item2", "POST", 200) + testRequest(t, handler, "bob", "/dataset2/folder1/item2", "DELETE", 403) +} + +func TestRBAC(t *testing.T) { + handler := web.NewControllerRegister() + + handler.InsertFilter("*", web.BeforeRouter, auth.Basic("cathy", "123")) + e := casbin.NewEnforcer("authz_model.conf", "authz_policy.csv") + handler.InsertFilter("*", web.BeforeRouter, NewAuthorizer(e)) + + handler.Any("*", func(ctx *context.Context) { + ctx.Output.SetStatus(200) + }) + + // cathy can access all /dataset1/* resources via all methods because it has the dataset1_admin role. + testRequest(t, handler, "cathy", "/dataset1/item", "GET", 200) + testRequest(t, handler, "cathy", "/dataset1/item", "POST", 200) + testRequest(t, handler, "cathy", "/dataset1/item", "DELETE", 200) + testRequest(t, handler, "cathy", "/dataset2/item", "GET", 403) + testRequest(t, handler, "cathy", "/dataset2/item", "POST", 403) + testRequest(t, handler, "cathy", "/dataset2/item", "DELETE", 403) + + // delete all roles on user cathy, so cathy cannot access any resources now. + e.DeleteRolesForUser("cathy") + + testRequest(t, handler, "cathy", "/dataset1/item", "GET", 403) + testRequest(t, handler, "cathy", "/dataset1/item", "POST", 403) + testRequest(t, handler, "cathy", "/dataset1/item", "DELETE", 403) + testRequest(t, handler, "cathy", "/dataset2/item", "GET", 403) + testRequest(t, handler, "cathy", "/dataset2/item", "POST", 403) + testRequest(t, handler, "cathy", "/dataset2/item", "DELETE", 403) +} diff --git a/plugins/cors/cors.go b/server/web/filter/cors/cors.go similarity index 98% rename from plugins/cors/cors.go rename to server/web/filter/cors/cors.go index 45c327ab..3a6905ea 100644 --- a/plugins/cors/cors.go +++ b/server/web/filter/cors/cors.go @@ -42,8 +42,8 @@ import ( "strings" "time" - "github.com/astaxie/beego" - "github.com/astaxie/beego/context" + "github.com/astaxie/beego/server/web" + "github.com/astaxie/beego/server/web/context" ) const ( @@ -187,7 +187,7 @@ func (o *Options) IsOriginAllowed(origin string) (allowed bool) { } // Allow enables CORS for requests those match the provided options. -func Allow(opts *Options) beego.FilterFunc { +func Allow(opts *Options) web.FilterFunc { // Allow default headers if nothing is specified. if len(opts.AllowHeaders) == 0 { opts.AllowHeaders = defaultAllowHeaders diff --git a/plugins/cors/cors_test.go b/server/web/filter/cors/cors_test.go similarity index 88% rename from plugins/cors/cors_test.go rename to server/web/filter/cors/cors_test.go index 34039143..7649de25 100644 --- a/plugins/cors/cors_test.go +++ b/server/web/filter/cors/cors_test.go @@ -21,8 +21,8 @@ import ( "testing" "time" - "github.com/astaxie/beego" - "github.com/astaxie/beego/context" + "github.com/astaxie/beego/server/web" + "github.com/astaxie/beego/server/web/context" ) // HTTPHeaderGuardRecorder is httptest.ResponseRecorder with own http.Header @@ -55,8 +55,8 @@ func (gr *HTTPHeaderGuardRecorder) Header() http.Header { func Test_AllowAll(t *testing.T) { recorder := httptest.NewRecorder() - handler := beego.NewControllerRegister() - handler.InsertFilter("*", beego.BeforeRouter, Allow(&Options{ + handler := web.NewControllerRegister() + handler.InsertFilter("*", web.BeforeRouter, Allow(&Options{ AllowAllOrigins: true, })) handler.Any("/foo", func(ctx *context.Context) { @@ -72,8 +72,8 @@ func Test_AllowAll(t *testing.T) { func Test_AllowRegexMatch(t *testing.T) { recorder := httptest.NewRecorder() - handler := beego.NewControllerRegister() - handler.InsertFilter("*", beego.BeforeRouter, Allow(&Options{ + handler := web.NewControllerRegister() + handler.InsertFilter("*", web.BeforeRouter, Allow(&Options{ AllowOrigins: []string{"https://aaa.com", "https://*.foo.com"}, })) handler.Any("/foo", func(ctx *context.Context) { @@ -92,8 +92,8 @@ func Test_AllowRegexMatch(t *testing.T) { func Test_AllowRegexNoMatch(t *testing.T) { recorder := httptest.NewRecorder() - handler := beego.NewControllerRegister() - handler.InsertFilter("*", beego.BeforeRouter, Allow(&Options{ + handler := web.NewControllerRegister() + handler.InsertFilter("*", web.BeforeRouter, Allow(&Options{ AllowOrigins: []string{"https://*.foo.com"}, })) handler.Any("/foo", func(ctx *context.Context) { @@ -112,8 +112,8 @@ func Test_AllowRegexNoMatch(t *testing.T) { func Test_OtherHeaders(t *testing.T) { recorder := httptest.NewRecorder() - handler := beego.NewControllerRegister() - handler.InsertFilter("*", beego.BeforeRouter, Allow(&Options{ + handler := web.NewControllerRegister() + handler.InsertFilter("*", web.BeforeRouter, Allow(&Options{ AllowAllOrigins: true, AllowCredentials: true, AllowMethods: []string{"PATCH", "GET"}, @@ -156,8 +156,8 @@ func Test_OtherHeaders(t *testing.T) { func Test_DefaultAllowHeaders(t *testing.T) { recorder := httptest.NewRecorder() - handler := beego.NewControllerRegister() - handler.InsertFilter("*", beego.BeforeRouter, Allow(&Options{ + handler := web.NewControllerRegister() + handler.InsertFilter("*", web.BeforeRouter, Allow(&Options{ AllowAllOrigins: true, })) handler.Any("/foo", func(ctx *context.Context) { @@ -175,8 +175,8 @@ func Test_DefaultAllowHeaders(t *testing.T) { func Test_Preflight(t *testing.T) { recorder := NewRecorder() - handler := beego.NewControllerRegister() - handler.InsertFilter("*", beego.BeforeRouter, Allow(&Options{ + handler := web.NewControllerRegister() + handler.InsertFilter("*", web.BeforeRouter, Allow(&Options{ AllowAllOrigins: true, AllowMethods: []string{"PUT", "PATCH"}, AllowHeaders: []string{"Origin", "X-whatever", "X-CaseSensitive"}, @@ -219,8 +219,8 @@ func Test_Preflight(t *testing.T) { func Benchmark_WithoutCORS(b *testing.B) { recorder := httptest.NewRecorder() - handler := beego.NewControllerRegister() - beego.BConfig.RunMode = beego.PROD + handler := web.NewControllerRegister() + web.BConfig.RunMode = web.PROD handler.Any("/foo", func(ctx *context.Context) { ctx.Output.SetStatus(500) }) @@ -233,9 +233,9 @@ func Benchmark_WithoutCORS(b *testing.B) { func Benchmark_WithCORS(b *testing.B) { recorder := httptest.NewRecorder() - handler := beego.NewControllerRegister() - beego.BConfig.RunMode = beego.PROD - handler.InsertFilter("*", beego.BeforeRouter, Allow(&Options{ + handler := web.NewControllerRegister() + web.BConfig.RunMode = web.PROD + handler.InsertFilter("*", web.BeforeRouter, Allow(&Options{ AllowAllOrigins: true, AllowCredentials: true, AllowMethods: []string{"PATCH", "GET"}, diff --git a/server/web/filter/opentracing/filter.go b/server/web/filter/opentracing/filter.go new file mode 100644 index 00000000..c2defa18 --- /dev/null +++ b/server/web/filter/opentracing/filter.go @@ -0,0 +1,86 @@ +// Copyright 2020 beego +// +// Licensed 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 opentracing + +import ( + "context" + + "github.com/astaxie/beego/server/web" + beegoCtx "github.com/astaxie/beego/server/web/context" + logKit "github.com/go-kit/kit/log" + opentracingKit "github.com/go-kit/kit/tracing/opentracing" + "github.com/opentracing/opentracing-go" +) + +// FilterChainBuilder provides an extension point that we can support more configurations if necessary +type FilterChainBuilder struct { + // CustomSpanFunc makes users to custom the span. + CustomSpanFunc func(span opentracing.Span, ctx *beegoCtx.Context) +} + +func (builder *FilterChainBuilder) FilterChain(next web.FilterFunc) web.FilterFunc { + return func(ctx *beegoCtx.Context) { + var ( + spanCtx context.Context + span opentracing.Span + ) + operationName := builder.operationName(ctx) + + if preSpan := opentracing.SpanFromContext(ctx.Request.Context()); preSpan == nil { + inject := opentracingKit.HTTPToContext(opentracing.GlobalTracer(), operationName, logKit.NewNopLogger()) + spanCtx = inject(ctx.Request.Context(), ctx.Request) + span = opentracing.SpanFromContext(spanCtx) + } else { + span, spanCtx = opentracing.StartSpanFromContext(ctx.Request.Context(), operationName) + } + + defer span.Finish() + + newReq := ctx.Request.Clone(spanCtx) + ctx.Reset(ctx.ResponseWriter.ResponseWriter, newReq) + + next(ctx) + // if you think we need to do more things, feel free to create an issue to tell us + span.SetTag("http.status_code", ctx.ResponseWriter.Status) + span.SetTag("http.method", ctx.Input.Method()) + span.SetTag("peer.hostname", ctx.Request.Host) + span.SetTag("http.url", ctx.Request.URL.String()) + span.SetTag("http.scheme", ctx.Request.URL.Scheme) + span.SetTag("span.kind", "server") + span.SetTag("component", "beego") + if ctx.Output.IsServerError() || ctx.Output.IsClientError() { + span.SetTag("error", true) + } + span.SetTag("peer.address", ctx.Request.RemoteAddr) + span.SetTag("http.proto", ctx.Request.Proto) + + span.SetTag("beego.route", ctx.Input.GetData("RouterPattern")) + + if builder.CustomSpanFunc != nil { + builder.CustomSpanFunc(span, ctx) + } + } +} + +func (builder *FilterChainBuilder) operationName(ctx *beegoCtx.Context) string { + operationName := ctx.Input.URL() + // it means that there is not any span, so we create a span as the root span. + // TODO, if we support multiple servers, this need to be changed + route, found := web.BeeApp.Handlers.FindRouter(ctx) + if found { + operationName = ctx.Input.Method() + "#" + route.GetPattern() + } + return operationName +} diff --git a/server/web/filter/opentracing/filter_test.go b/server/web/filter/opentracing/filter_test.go new file mode 100644 index 00000000..d7222c37 --- /dev/null +++ b/server/web/filter/opentracing/filter_test.go @@ -0,0 +1,47 @@ +// Copyright 2020 beego +// +// Licensed 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 opentracing + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/opentracing/opentracing-go" + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/server/web/context" +) + +func TestFilterChainBuilder_FilterChain(t *testing.T) { + builder := &FilterChainBuilder{ + CustomSpanFunc: func(span opentracing.Span, ctx *context.Context) { + span.SetTag("aa", "bbb") + }, + } + + ctx := context.NewContext() + r, _ := http.NewRequest("GET", "/prometheus/user", nil) + w := httptest.NewRecorder() + ctx.Reset(w, r) + ctx.Input.SetData("RouterPattern", "my-route") + + filterFunc := builder.FilterChain(func(ctx *context.Context) { + ctx.Input.SetData("opentracing", true) + }) + + filterFunc(ctx) + assert.True(t, ctx.Input.GetData("opentracing").(bool)) +} diff --git a/server/web/filter/prometheus/filter.go b/server/web/filter/prometheus/filter.go new file mode 100644 index 00000000..7daabd5a --- /dev/null +++ b/server/web/filter/prometheus/filter.go @@ -0,0 +1,87 @@ +// Copyright 2020 beego +// +// Licensed 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 prometheus + +import ( + "strconv" + "strings" + "time" + + "github.com/prometheus/client_golang/prometheus" + + "github.com/astaxie/beego" + "github.com/astaxie/beego/server/web" + "github.com/astaxie/beego/server/web/context" +) + +// FilterChainBuilder is an extension point, +// when we want to support some configuration, +// please use this structure +type FilterChainBuilder struct { +} + +// FilterChain returns a FilterFunc. The filter will records some metrics +func (builder *FilterChainBuilder) FilterChain(next web.FilterFunc) web.FilterFunc { + summaryVec := prometheus.NewSummaryVec(prometheus.SummaryOpts{ + Name: "beego", + Subsystem: "http_request", + ConstLabels: map[string]string{ + "server": web.BConfig.ServerName, + "env": web.BConfig.RunMode, + "appname": web.BConfig.AppName, + }, + Help: "The statics info for http request", + }, []string{"pattern", "method", "status", "duration"}) + + prometheus.MustRegister(summaryVec) + + registerBuildInfo() + + return func(ctx *context.Context) { + startTime := time.Now() + next(ctx) + endTime := time.Now() + go report(endTime.Sub(startTime), ctx, summaryVec) + } +} + +func registerBuildInfo() { + buildInfo := prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "beego", + Subsystem: "build_info", + Help: "The building information", + ConstLabels: map[string]string{ + "appname": web.BConfig.AppName, + "build_version": beego.BuildVersion, + "build_revision": beego.BuildGitRevision, + "build_status": beego.BuildStatus, + "build_tag": beego.BuildTag, + "build_time": strings.Replace(beego.BuildTime, "--", " ", 1), + "go_version": beego.GoVersion, + "git_branch": beego.GitBranch, + "start_time": time.Now().Format("2006-01-02 15:04:05"), + }, + }, []string{}) + + prometheus.MustRegister(buildInfo) + buildInfo.WithLabelValues().Set(1) +} + +func report(dur time.Duration, ctx *context.Context, vec *prometheus.SummaryVec) { + status := ctx.Output.Status + ptn := ctx.Input.GetData("RouterPattern").(string) + ms := dur / time.Millisecond + vec.WithLabelValues(ptn, ctx.Input.Method(), strconv.Itoa(status), strconv.Itoa(int(ms))).Observe(float64(ms)) +} diff --git a/server/web/filter/prometheus/filter_test.go b/server/web/filter/prometheus/filter_test.go new file mode 100644 index 00000000..cb133a64 --- /dev/null +++ b/server/web/filter/prometheus/filter_test.go @@ -0,0 +1,40 @@ +// Copyright 2020 beego +// +// Licensed 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 prometheus + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/server/web/context" +) + +func TestFilterChain(t *testing.T) { + filter := (&FilterChainBuilder{}).FilterChain(func(ctx *context.Context) { + // do nothing + ctx.Input.SetData("invocation", true) + }) + + ctx := context.NewContext() + r, _ := http.NewRequest("GET", "/prometheus/user", nil) + w := httptest.NewRecorder() + ctx.Reset(w, r) + ctx.Input.SetData("RouterPattern", "my-route") + filter(ctx) + assert.True(t, ctx.Input.GetData("invocation").(bool)) +} diff --git a/server/web/filter_chain_test.go b/server/web/filter_chain_test.go new file mode 100644 index 00000000..e175ab29 --- /dev/null +++ b/server/web/filter_chain_test.go @@ -0,0 +1,48 @@ +// Copyright 2020 +// +// Licensed 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 web + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/server/web/context" +) + +func TestControllerRegister_InsertFilterChain(t *testing.T) { + + InsertFilterChain("/*", func(next FilterFunc) FilterFunc { + return func(ctx *context.Context) { + ctx.Output.Header("filter", "filter-chain") + next(ctx) + } + }) + + ns := NewNamespace("/chain") + + ns.Get("/*", func(ctx *context.Context) { + ctx.Output.Body([]byte("hello")) + }) + + r, _ := http.NewRequest("GET", "/chain/user", nil) + w := httptest.NewRecorder() + + BeeApp.Handlers.ServeHTTP(w, r) + + assert.Equal(t, "filter-chain", w.Header().Get("filter")) +} diff --git a/filter_test.go b/server/web/filter_test.go similarity index 97% rename from filter_test.go rename to server/web/filter_test.go index 4ca4d2b8..11f575d6 100644 --- a/filter_test.go +++ b/server/web/filter_test.go @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "net/http" "net/http/httptest" "testing" - "github.com/astaxie/beego/context" + "github.com/astaxie/beego/server/web/context" ) var FilterUser = func(ctx *context.Context) { diff --git a/flash.go b/server/web/flash.go similarity index 99% rename from flash.go rename to server/web/flash.go index a6485a17..55f6435d 100644 --- a/flash.go +++ b/server/web/flash.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "fmt" diff --git a/flash_test.go b/server/web/flash_test.go similarity index 99% rename from flash_test.go rename to server/web/flash_test.go index d5e9608d..2deef54e 100644 --- a/flash_test.go +++ b/server/web/flash_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "net/http" diff --git a/fs.go b/server/web/fs.go similarity index 99% rename from fs.go rename to server/web/fs.go index 41cc6f6e..5457457a 100644 --- a/fs.go +++ b/server/web/fs.go @@ -1,4 +1,4 @@ -package beego +package web import ( "net/http" diff --git a/grace/grace.go b/server/web/grace/grace.go similarity index 100% rename from grace/grace.go rename to server/web/grace/grace.go diff --git a/grace/server.go b/server/web/grace/server.go similarity index 98% rename from grace/server.go rename to server/web/grace/server.go index 008a6171..13fa6e34 100644 --- a/grace/server.go +++ b/server/web/grace/server.go @@ -29,8 +29,8 @@ type Server struct { terminalChan chan error } -// Serve accepts incoming connections on the Listener l, -// creating a new service goroutine for each. +// Serve accepts incoming connections on the Listener l +// and creates a new service goroutine for each. // The service goroutines read requests and then call srv.Handler to reply to them. func (srv *Server) Serve() (err error) { srv.state = StateRunning diff --git a/hooks.go b/server/web/hooks.go similarity index 84% rename from hooks.go rename to server/web/hooks.go index b8671d35..58e2c0f3 100644 --- a/hooks.go +++ b/server/web/hooks.go @@ -1,4 +1,4 @@ -package beego +package web import ( "encoding/json" @@ -6,9 +6,9 @@ import ( "net/http" "path/filepath" - "github.com/astaxie/beego/context" - "github.com/astaxie/beego/logs" - "github.com/astaxie/beego/session" + "github.com/astaxie/beego/core/logs" + "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/server/web/session" ) // register MIME type with content type @@ -34,6 +34,7 @@ func registerDefaultErrorHandler() error { "504": gatewayTimeout, "417": invalidxsrf, "422": missingxsrf, + "413": payloadTooLarge, } for e, h := range m { if _, ok := ErrorMaps[e]; !ok { @@ -46,9 +47,9 @@ func registerDefaultErrorHandler() error { func registerSession() error { if BConfig.WebConfig.Session.SessionOn { var err error - sessionConfig := AppConfig.String("sessionConfig") + sessionConfig, err := AppConfig.String("sessionConfig") conf := new(session.ManagerConfig) - if sessionConfig == "" { + if sessionConfig == "" || err != nil { conf.CookieName = BConfig.WebConfig.Session.SessionName conf.EnableSetCookie = BConfig.WebConfig.Session.SessionAutoSetCookie conf.Gclifetime = BConfig.WebConfig.Session.SessionGCMaxLifetime @@ -84,13 +85,6 @@ func registerTemplate() error { return nil } -func registerAdmin() error { - if BConfig.Listen.EnableAdmin { - go beeAdminApp.Run() - } - return nil -} - func registerGzip() error { if BConfig.EnableGzip { context.InitGzip( @@ -101,3 +95,13 @@ func registerGzip() error { } return nil } + +func registerCommentRouter() error { + if BConfig.RunMode == DEV { + if err := parserPkg(filepath.Join(WorkPath, BConfig.WebConfig.CommentRouterPath)); err != nil { + return err + } + } + + return nil +} diff --git a/mime.go b/server/web/mime.go similarity index 99% rename from mime.go rename to server/web/mime.go index ca2878ab..9393e9c7 100644 --- a/mime.go +++ b/server/web/mime.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web var mimemaps = map[string]string{ ".3dm": "x-world/x-3dmf", diff --git a/namespace.go b/server/web/namespace.go similarity index 98% rename from namespace.go rename to server/web/namespace.go index 4952c9d5..58afb6c7 100644 --- a/namespace.go +++ b/server/web/namespace.go @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "net/http" "strings" - beecontext "github.com/astaxie/beego/context" + beecontext "github.com/astaxie/beego/server/web/context" ) type namespaceCond func(*beecontext.Context) bool @@ -91,7 +91,7 @@ func (n *Namespace) Filter(action string, filter ...FilterFunc) *Namespace { a = FinishRouter } for _, f := range filter { - n.handlers.InsertFilter("*", a, f) + n.handlers.InsertFilter("*", a, f, WithReturnOnOutput(true)) } return n } diff --git a/namespace_test.go b/server/web/namespace_test.go similarity index 98% rename from namespace_test.go rename to server/web/namespace_test.go index b3f20dff..a6f87bba 100644 --- a/namespace_test.go +++ b/server/web/namespace_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "net/http" @@ -20,7 +20,7 @@ import ( "strconv" "testing" - "github.com/astaxie/beego/context" + "github.com/astaxie/beego/server/web/context" ) func TestNamespaceGet(t *testing.T) { diff --git a/utils/pagination/controller.go b/server/web/pagination/controller.go similarity index 81% rename from utils/pagination/controller.go rename to server/web/pagination/controller.go index 2f022d0c..f6b2f73d 100644 --- a/utils/pagination/controller.go +++ b/server/web/pagination/controller.go @@ -15,12 +15,13 @@ package pagination import ( - "github.com/astaxie/beego/context" + "github.com/astaxie/beego/core/utils/pagination" + "github.com/astaxie/beego/server/web/context" ) // SetPaginator Instantiates a Paginator and assigns it to context.Input.Data("paginator"). -func SetPaginator(context *context.Context, per int, nums int64) (paginator *Paginator) { - paginator = NewPaginator(context.Request, per, nums) +func SetPaginator(context *context.Context, per int, nums int64) (paginator *pagination.Paginator) { + paginator = pagination.NewPaginator(context.Request, per, nums) context.Input.SetData("paginator", &paginator) return } diff --git a/parser.go b/server/web/parser.go similarity index 93% rename from parser.go rename to server/web/parser.go index 3a311894..c3434501 100644 --- a/parser.go +++ b/server/web/parser.go @@ -12,15 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "encoding/json" "errors" "fmt" "go/ast" - "go/parser" - "go/token" "io/ioutil" "os" "path/filepath" @@ -30,16 +28,19 @@ import ( "strings" "unicode" - "github.com/astaxie/beego/context/param" - "github.com/astaxie/beego/logs" - "github.com/astaxie/beego/utils" + "golang.org/x/tools/go/packages" + + "github.com/astaxie/beego/core/logs" + + "github.com/astaxie/beego/core/utils" + "github.com/astaxie/beego/server/web/context/param" ) var globalRouterTemplate = `package {{.routersDir}} import ( - "github.com/astaxie/beego" - "github.com/astaxie/beego/context/param"{{.globalimport}} + beego "github.com/astaxie/beego/server/web" + "github.com/astaxie/beego/server/web/context/param"{{.globalimport}} ) func init() { @@ -76,7 +77,7 @@ func init() { pkgLastupdate = make(map[string]int64) } -func parserPkg(pkgRealpath, pkgpath string) error { +func parserPkg(pkgRealpath string) error { rep := strings.NewReplacer("\\", "_", "/", "_", ".", "_") commentFilename, _ = filepath.Rel(AppPath, pkgRealpath) commentFilename = commentPrefix + rep.Replace(commentFilename) + ".go" @@ -85,24 +86,23 @@ func parserPkg(pkgRealpath, pkgpath string) error { return nil } genInfoList = make(map[string][]ControllerComments) - fileSet := token.NewFileSet() - astPkgs, err := parser.ParseDir(fileSet, pkgRealpath, func(info os.FileInfo) bool { - name := info.Name() - return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") - }, parser.ParseComments) + pkgs, err := packages.Load(&packages.Config{ + Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedSyntax, + Dir: pkgRealpath, + }, "./...") if err != nil { return err } - for _, pkg := range astPkgs { - for _, fl := range pkg.Files { + for _, pkg := range pkgs { + for _, fl := range pkg.Syntax { for _, d := range fl.Decls { switch specDecl := d.(type) { case *ast.FuncDecl: if specDecl.Recv != nil { exp, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr) // Check that the type is correct first beforing throwing to parser if ok { - parserComments(specDecl, fmt.Sprint(exp.X), pkgpath) + parserComments(specDecl, fmt.Sprint(exp.X), pkg.PkgPath) } } } @@ -221,7 +221,7 @@ func buildMethodParams(funcParams []*ast.Field, pc *parsedComment) []*param.Meth func buildMethodParam(fparam *ast.Field, name string, pc *parsedComment) *param.MethodParam { options := []param.MethodParamOption{} if cparam, ok := pc.params[name]; ok { - //Build param from comment info + // Build param from comment info name = cparam.name if cparam.required { options = append(options, param.IsRequired) @@ -358,10 +358,10 @@ filterLoop: methods := matches[2] if methods == "" { pc.methods = []string{"get"} - //pc.hasGet = true + // pc.hasGet = true } else { pc.methods = strings.Split(methods, ",") - //pc.hasGet = strings.Contains(methods, "get") + // pc.hasGet = strings.Contains(methods, "get") } pcs = append(pcs, pc) } else { @@ -566,8 +566,17 @@ func getpathTime(pkgRealpath string) (lastupdate int64, err error) { return lastupdate, err } for _, f := range fl { - if lastupdate < f.ModTime().UnixNano() { - lastupdate = f.ModTime().UnixNano() + var t int64 + if f.IsDir() { + t, err = getpathTime(filepath.Join(pkgRealpath, f.Name())) + if err != nil { + return lastupdate, err + } + } else { + t = f.ModTime().UnixNano() + } + if lastupdate < t { + lastupdate = t } } return lastupdate, nil diff --git a/policy.go b/server/web/policy.go similarity index 97% rename from policy.go rename to server/web/policy.go index ab23f927..14673422 100644 --- a/policy.go +++ b/server/web/policy.go @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "strings" - "github.com/astaxie/beego/context" + "github.com/astaxie/beego/server/web/context" ) // PolicyFunc defines a policy function which is invoked before the controller handler is executed. diff --git a/router.go b/server/web/router.go similarity index 79% rename from router.go rename to server/web/router.go index b19a199d..7bb89d82 100644 --- a/router.go +++ b/server/web/router.go @@ -12,26 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "errors" "fmt" "net/http" - "os" "path" - "path/filepath" "reflect" "strconv" "strings" "sync" "time" - beecontext "github.com/astaxie/beego/context" - "github.com/astaxie/beego/context/param" - "github.com/astaxie/beego/logs" - "github.com/astaxie/beego/toolbox" - "github.com/astaxie/beego/utils" + "github.com/astaxie/beego/core/logs" + + "github.com/astaxie/beego/core/utils" + beecontext "github.com/astaxie/beego/server/web/context" + "github.com/astaxie/beego/server/web/context/param" ) // default filter execution points @@ -134,11 +132,22 @@ type ControllerRegister struct { enableFilter bool filters [FinishRouter + 1][]*FilterRouter pool sync.Pool + + // the filter created by FilterChain + chainRoot *FilterRouter + + cfg *Config } // NewControllerRegister returns a new ControllerRegister. +// Usually you should not use this method +// please use NewControllerRegisterWithCfg func NewControllerRegister() *ControllerRegister { - return &ControllerRegister{ + return NewControllerRegisterWithCfg(BeeApp.Cfg) +} + +func NewControllerRegisterWithCfg(cfg *Config) *ControllerRegister { + res := &ControllerRegister{ routers: make(map[string]*Tree), policies: make(map[string]*Tree), pool: sync.Pool{ @@ -146,7 +155,10 @@ func NewControllerRegister() *ControllerRegister { return beecontext.NewContext() }, }, + cfg: cfg, } + res.chainRoot = newFilterRouter("/*", res.serveHttp, WithCaseSensitive(false)) + return res } // Add controller handler and pattern rules to ControllerRegister. @@ -237,7 +249,7 @@ func (p *ControllerRegister) addWithMethodParams(pattern string, c ControllerInt } func (p *ControllerRegister) addToRouter(method, pattern string, r *ControllerInfo) { - if !BConfig.RouterCaseSensitive { + if !p.cfg.RouterCaseSensitive { pattern = strings.ToLower(pattern) } if t, ok := p.routers[method]; ok { @@ -252,45 +264,6 @@ func (p *ControllerRegister) addToRouter(method, pattern string, r *ControllerIn // Include only when the Runmode is dev will generate router file in the router/auto.go from the controller // Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) func (p *ControllerRegister) Include(cList ...ControllerInterface) { - if BConfig.RunMode == DEV { - skip := make(map[string]bool, 10) - wgopath := utils.GetGOPATHs() - go111module := os.Getenv(`GO111MODULE`) - for _, c := range cList { - reflectVal := reflect.ValueOf(c) - t := reflect.Indirect(reflectVal).Type() - // for go modules - if go111module == `on` { - pkgpath := filepath.Join(WorkPath, "..", t.PkgPath()) - if utils.FileExists(pkgpath) { - if pkgpath != "" { - if _, ok := skip[pkgpath]; !ok { - skip[pkgpath] = true - parserPkg(pkgpath, t.PkgPath()) - } - } - } - } else { - if len(wgopath) == 0 { - panic("you are in dev mode. So please set gopath") - } - pkgpath := "" - for _, wg := range wgopath { - wg, _ = filepath.EvalSymlinks(filepath.Join(wg, "src", t.PkgPath())) - if utils.FileExists(wg) { - pkgpath = wg - break - } - } - if pkgpath != "" { - if _, ok := skip[pkgpath]; !ok { - skip[pkgpath] = true - parserPkg(pkgpath, t.PkgPath()) - } - } - } - } - } for _, c := range cList { reflectVal := reflect.ValueOf(c) t := reflect.Indirect(reflectVal).Type() @@ -298,7 +271,7 @@ func (p *ControllerRegister) Include(cList ...ControllerInterface) { if comm, ok := GlobalControllerRouter[key]; ok { for _, a := range comm { for _, f := range a.Filters { - p.InsertFilter(f.Pattern, f.Pos, f.Filter, f.ReturnOnOutput, f.ResetParams) + p.InsertFilter(f.Pattern, f.Pos, f.Filter, WithReturnOnOutput(f.ReturnOnOutput), WithResetParams(f.ResetParams)) } p.addWithMethodParams(a.Router, c, a.MethodParams, strings.Join(a.AllowHTTPMethods, ",")+":"+a.Method) @@ -488,28 +461,32 @@ func (p *ControllerRegister) AddAutoPrefix(prefix string, c ControllerInterface) // params is for: // 1. setting the returnOnOutput value (false allows multiple filters to execute) // 2. determining whether or not params need to be reset. -func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) error { - mr := &FilterRouter{ - tree: NewTree(), - pattern: pattern, - filterFunc: filter, - returnOnOutput: true, - } - if !BConfig.RouterCaseSensitive { - mr.pattern = strings.ToLower(pattern) - } - - paramsLen := len(params) - if paramsLen > 0 { - mr.returnOnOutput = params[0] - } - if paramsLen > 1 { - mr.resetParams = params[1] - } - mr.tree.AddRouter(pattern, true) +func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter FilterFunc, opts ...FilterOpt) error { + opts = append(opts, WithCaseSensitive(p.cfg.RouterCaseSensitive)) + mr := newFilterRouter(pattern, filter, opts...) return p.insertFilterRouter(pos, mr) } +// InsertFilterChain is similar to InsertFilter, +// but it will using chainRoot.filterFunc as input to build a new filterFunc +// for example, assume that chainRoot is funcA +// and we add new FilterChain +// fc := func(next) { +// return func(ctx) { +// // do something +// next(ctx) +// // do something +// } +// } +func (p *ControllerRegister) InsertFilterChain(pattern string, chain FilterChain, opts ...FilterOpt) { + root := p.chainRoot + filterFunc := chain(root.filterFunc) + opts = append(opts, WithCaseSensitive(p.cfg.RouterCaseSensitive)) + p.chainRoot = newFilterRouter(pattern, filterFunc, opts...) + p.chainRoot.next = root + +} + // add Filter into func (p *ControllerRegister) insertFilterRouter(pos int, mr *FilterRouter) (err error) { if pos < BeforeStatic || pos > FinishRouter { @@ -668,23 +645,9 @@ func (p *ControllerRegister) getURL(t *Tree, url, controllerName, methodName str func (p *ControllerRegister) execFilter(context *beecontext.Context, urlPath string, pos int) (started bool) { var preFilterParams map[string]string for _, filterR := range p.filters[pos] { - if filterR.returnOnOutput && context.ResponseWriter.Started { - return true - } - if filterR.resetParams { - preFilterParams = context.Input.Params() - } - if ok := filterR.ValidRouter(urlPath, context); ok { - filterR.filterFunc(context) - if filterR.resetParams { - context.Input.ResetParams() - for k, v := range preFilterParams { - context.Input.SetParam(k, v) - } - } - } - if filterR.returnOnOutput && context.ResponseWriter.Started { - return true + b, done := filterR.filter(context, urlPath, preFilterParams) + if done { + return b } } return false @@ -692,7 +655,21 @@ func (p *ControllerRegister) execFilter(context *beecontext.Context, urlPath str // Implement http.Handler interface. func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + + ctx := p.GetContext() + + ctx.Reset(rw, r) + defer p.GiveBackContext(ctx) + + var preFilterParams map[string]string + p.chainRoot.filter(ctx, p.getUrlPath(ctx), preFilterParams) +} + +func (p *ControllerRegister) serveHttp(ctx *beecontext.Context) { + var err error startTime := time.Now() + r := ctx.Request + rw := ctx.ResponseWriter.ResponseWriter var ( runRouter reflect.Type findRouter bool @@ -701,102 +678,118 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) routerInfo *ControllerInfo isRunnable bool ) - context := p.GetContext() - context.Reset(rw, r) - - defer p.GiveBackContext(context) - if BConfig.RecoverFunc != nil { - defer BConfig.RecoverFunc(context) + if p.cfg.RecoverFunc != nil { + defer p.cfg.RecoverFunc(ctx, p.cfg) } - context.Output.EnableGzip = BConfig.EnableGzip + ctx.Output.EnableGzip = p.cfg.EnableGzip - if BConfig.RunMode == DEV { - context.Output.Header("Server", BConfig.ServerName) + if p.cfg.RunMode == DEV { + ctx.Output.Header("Server", p.cfg.ServerName) } - var urlPath = r.URL.Path - - if !BConfig.RouterCaseSensitive { - urlPath = strings.ToLower(urlPath) - } + urlPath := p.getUrlPath(ctx) // filter wrong http method if !HTTPMETHOD[r.Method] { - exception("405", context) + exception("405", ctx) goto Admin } // filter for static file - if len(p.filters[BeforeStatic]) > 0 && p.execFilter(context, urlPath, BeforeStatic) { + if len(p.filters[BeforeStatic]) > 0 && p.execFilter(ctx, urlPath, BeforeStatic) { goto Admin } - serverStaticRouter(context) + serverStaticRouter(ctx) - if context.ResponseWriter.Started { + if ctx.ResponseWriter.Started { findRouter = true goto Admin } if r.Method != http.MethodGet && r.Method != http.MethodHead { - if BConfig.CopyRequestBody && !context.Input.IsUpload() { - context.Input.CopyBody(BConfig.MaxMemory) + + if ctx.Input.IsUpload() { + ctx.Input.Context.Request.Body = http.MaxBytesReader(ctx.Input.Context.ResponseWriter, + ctx.Input.Context.Request.Body, + p.cfg.MaxUploadSize) + } else if p.cfg.CopyRequestBody { + // connection will close if the incoming data are larger (RFC 7231, 6.5.11) + if r.ContentLength > p.cfg.MaxMemory { + logs.Error(errors.New("payload too large")) + exception("413", ctx) + goto Admin + } + ctx.Input.CopyBody(p.cfg.MaxMemory) + } else { + ctx.Input.Context.Request.Body = http.MaxBytesReader(ctx.Input.Context.ResponseWriter, + ctx.Input.Context.Request.Body, + p.cfg.MaxMemory) + } + + err = ctx.Input.ParseFormOrMultiForm(p.cfg.MaxMemory) + if err != nil { + logs.Error(err) + if strings.Contains(err.Error(), `http: request body too large`) { + exception("413", ctx) + } else { + exception("500", ctx) + } + goto Admin } - context.Input.ParseFormOrMulitForm(BConfig.MaxMemory) } // session init - if BConfig.WebConfig.Session.SessionOn { - var err error - context.Input.CruSession, err = GlobalSessions.SessionStart(rw, r) + if p.cfg.WebConfig.Session.SessionOn { + ctx.Input.CruSession, err = GlobalSessions.SessionStart(rw, r) if err != nil { logs.Error(err) - exception("503", context) + exception("503", ctx) goto Admin } defer func() { - if context.Input.CruSession != nil { - context.Input.CruSession.SessionRelease(rw) + if ctx.Input.CruSession != nil { + ctx.Input.CruSession.SessionRelease(nil, rw) } }() } - if len(p.filters[BeforeRouter]) > 0 && p.execFilter(context, urlPath, BeforeRouter) { + if len(p.filters[BeforeRouter]) > 0 && p.execFilter(ctx, urlPath, BeforeRouter) { goto Admin } // User can define RunController and RunMethod in filter - if context.Input.RunController != nil && context.Input.RunMethod != "" { + if ctx.Input.RunController != nil && ctx.Input.RunMethod != "" { findRouter = true - runMethod = context.Input.RunMethod - runRouter = context.Input.RunController + runMethod = ctx.Input.RunMethod + runRouter = ctx.Input.RunController } else { - routerInfo, findRouter = p.FindRouter(context) + routerInfo, findRouter = p.FindRouter(ctx) } // if no matches to url, throw a not found exception if !findRouter { - exception("404", context) + exception("404", ctx) goto Admin } - if splat := context.Input.Param(":splat"); splat != "" { + if splat := ctx.Input.Param(":splat"); splat != "" { for k, v := range strings.Split(splat, "/") { - context.Input.SetParam(strconv.Itoa(k), v) + ctx.Input.SetParam(strconv.Itoa(k), v) } } if routerInfo != nil { // store router pattern into context - context.Input.SetData("RouterPattern", routerInfo.pattern) + ctx.Input.SetData("RouterPattern", routerInfo.pattern) } // execute middleware filters - if len(p.filters[BeforeExec]) > 0 && p.execFilter(context, urlPath, BeforeExec) { + if len(p.filters[BeforeExec]) > 0 && p.execFilter(ctx, urlPath, BeforeExec) { goto Admin } // check policies - if p.execPolicy(context, urlPath) { + if p.execPolicy(ctx, urlPath) { goto Admin } @@ -804,22 +797,22 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) if routerInfo.routerType == routerTypeRESTFul { if _, ok := routerInfo.methods[r.Method]; ok { isRunnable = true - routerInfo.runFunction(context) + routerInfo.runFunction(ctx) } else { - exception("405", context) + exception("405", ctx) goto Admin } } else if routerInfo.routerType == routerTypeHandler { isRunnable = true - routerInfo.handler.ServeHTTP(context.ResponseWriter, context.Request) + routerInfo.handler.ServeHTTP(ctx.ResponseWriter, ctx.Request) } else { runRouter = routerInfo.controllerType methodParams = routerInfo.methodParams method := r.Method - if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodPut { + if r.Method == http.MethodPost && ctx.Input.Query("_method") == http.MethodPut { method = http.MethodPut } - if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodDelete { + if r.Method == http.MethodPost && ctx.Input.Query("_method") == http.MethodDelete { method = http.MethodDelete } if m, ok := routerInfo.methods[method]; ok { @@ -848,23 +841,23 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) } // call the controller init function - execController.Init(context, runRouter.Name(), runMethod, execController) + execController.Init(ctx, runRouter.Name(), runMethod, execController) // call prepare function execController.Prepare() // if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf - if BConfig.WebConfig.EnableXSRF { + if p.cfg.WebConfig.EnableXSRF { execController.XSRFToken() if r.Method == http.MethodPost || r.Method == http.MethodDelete || r.Method == http.MethodPut || - (r.Method == http.MethodPost && (context.Input.Query("_method") == http.MethodDelete || context.Input.Query("_method") == http.MethodPut)) { + (r.Method == http.MethodPost && (ctx.Input.Query("_method") == http.MethodDelete || ctx.Input.Query("_method") == http.MethodPut)) { execController.CheckXSRFCookie() } } execController.URLMapping() - if !context.ResponseWriter.Started { + if !ctx.ResponseWriter.Started { // exec main logic switch runMethod { case http.MethodGet: @@ -887,19 +880,19 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) if !execController.HandlerFunc(runMethod) { vc := reflect.ValueOf(execController) method := vc.MethodByName(runMethod) - in := param.ConvertParams(methodParams, method.Type(), context) + in := param.ConvertParams(methodParams, method.Type(), ctx) out := method.Call(in) // For backward compatibility we only handle response if we had incoming methodParams if methodParams != nil { - p.handleParamResponse(context, execController, out) + p.handleParamResponse(ctx, execController, out) } } } // render template - if !context.ResponseWriter.Started && context.Output.Status == 0 { - if BConfig.WebConfig.AutoRender { + if !ctx.ResponseWriter.Started && ctx.Output.Status == 0 { + if p.cfg.WebConfig.AutoRender { if err := execController.Render(); err != nil { logs.Error(err) } @@ -912,27 +905,27 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) } // execute middleware filters - if len(p.filters[AfterExec]) > 0 && p.execFilter(context, urlPath, AfterExec) { + if len(p.filters[AfterExec]) > 0 && p.execFilter(ctx, urlPath, AfterExec) { goto Admin } - if len(p.filters[FinishRouter]) > 0 && p.execFilter(context, urlPath, FinishRouter) { + if len(p.filters[FinishRouter]) > 0 && p.execFilter(ctx, urlPath, FinishRouter) { goto Admin } Admin: // admin module record QPS - statusCode := context.ResponseWriter.Status + statusCode := ctx.ResponseWriter.Status if statusCode == 0 { statusCode = 200 } - LogAccess(context, &startTime, statusCode) + LogAccess(ctx, &startTime, statusCode) timeDur := time.Since(startTime) - context.ResponseWriter.Elapsed = timeDur - if BConfig.Listen.EnableAdmin { + ctx.ResponseWriter.Elapsed = timeDur + if p.cfg.Listen.EnableAdmin { pattern := "" if routerInfo != nil { pattern = routerInfo.pattern @@ -943,14 +936,14 @@ Admin: if runRouter != nil { routerName = runRouter.Name() } - go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, routerName, timeDur) + go StatisticsMap.AddStatistics(r.Method, r.URL.Path, routerName, timeDur) } } - if BConfig.RunMode == DEV && !BConfig.Log.AccessLogs { + if p.cfg.RunMode == DEV && !p.cfg.Log.AccessLogs { match := map[bool]string{true: "match", false: "nomatch"} devInfo := fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", - context.Input.IP(), + ctx.Input.IP(), logs.ColorByStatus(statusCode), statusCode, logs.ResetColor(), timeDur.String(), match[findRouter], @@ -963,11 +956,19 @@ Admin: logs.Debug(devInfo) } // Call WriteHeader if status code has been set changed - if context.Output.Status != 0 { - context.ResponseWriter.WriteHeader(context.Output.Status) + if ctx.Output.Status != 0 { + ctx.ResponseWriter.WriteHeader(ctx.Output.Status) } } +func (p *ControllerRegister) getUrlPath(ctx *beecontext.Context) string { + urlPath := ctx.Request.URL.Path + if !p.cfg.RouterCaseSensitive { + urlPath = strings.ToLower(urlPath) + } + return urlPath +} + func (p *ControllerRegister) handleParamResponse(context *beecontext.Context, execController ControllerInterface, results []reflect.Value) { // looping in reverse order for the case when both error and value are returned and error sets the response status code for i := len(results) - 1; i >= 0; i-- { @@ -985,7 +986,7 @@ func (p *ControllerRegister) handleParamResponse(context *beecontext.Context, ex // FindRouter Find Router info for URL func (p *ControllerRegister) FindRouter(context *beecontext.Context) (routerInfo *ControllerInfo, isFind bool) { var urlPath = context.Input.URL() - if !BConfig.RouterCaseSensitive { + if !p.cfg.RouterCaseSensitive { urlPath = strings.ToLower(urlPath) } httpMethod := context.Input.Method() @@ -1011,36 +1012,5 @@ func toURL(params map[string]string) string { // LogAccess logging info HTTP Access func LogAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) { - // Skip logging if AccessLogs config is false - if !BConfig.Log.AccessLogs { - return - } - // Skip logging static requests unless EnableStaticLogs config is true - if !BConfig.Log.EnableStaticLogs && DefaultAccessLogFilter.Filter(ctx) { - return - } - var ( - requestTime time.Time - elapsedTime time.Duration - r = ctx.Request - ) - if startTime != nil { - requestTime = *startTime - elapsedTime = time.Since(*startTime) - } - record := &logs.AccessLogRecord{ - RemoteAddr: ctx.Input.IP(), - RequestTime: requestTime, - RequestMethod: r.Method, - Request: fmt.Sprintf("%s %s %s", r.Method, r.RequestURI, r.Proto), - ServerProtocol: r.Proto, - Host: r.Host, - Status: statusCode, - ElapsedTime: elapsedTime, - HTTPReferrer: r.Header.Get("Referer"), - HTTPUserAgent: r.Header.Get("User-Agent"), - RemoteUser: r.Header.Get("Remote-User"), - BodyBytesSent: 0, // @todo this one is missing! - } - logs.AccessLog(record, BConfig.Log.AccessLogsFormat) + BeeApp.LogAccess(ctx, startTime, statusCode) } diff --git a/router_test.go b/server/web/router_test.go similarity index 88% rename from router_test.go rename to server/web/router_test.go index 2797b33a..59ccd1fc 100644 --- a/router_test.go +++ b/server/web/router_test.go @@ -12,16 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( + "bytes" "net/http" "net/http/httptest" "strings" "testing" - "github.com/astaxie/beego/context" - "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/core/logs" + + "github.com/astaxie/beego/server/web/context" ) type TestController struct { @@ -71,7 +73,6 @@ func (tc *TestController) GetEmptyBody() { tc.Ctx.Output.Body(res) } - type JSONController struct { Controller } @@ -211,6 +212,23 @@ func TestAutoExtFunc(t *testing.T) { } } +func TestEscape(t *testing.T) { + + r, _ := http.NewRequest("GET", "/search/%E4%BD%A0%E5%A5%BD", nil) + w := httptest.NewRecorder() + + handler := NewControllerRegister() + handler.Get("/search/:keyword(.+)", func(ctx *context.Context) { + value := ctx.Input.Param(":keyword") + ctx.Output.Body([]byte(value)) + }) + handler.ServeHTTP(w, r) + str := w.Body.String() + if str != "你好" { + t.Errorf("incorrect, %s", str) + } +} + func TestRouteOk(t *testing.T) { r, _ := http.NewRequest("GET", "/person/anderson/thomas?learn=kungfu", nil) @@ -363,7 +381,7 @@ func TestRouterHandlerAll(t *testing.T) { } // -// Benchmarks NewApp: +// Benchmarks NewHttpSever: // func beegoFilterFunc(ctx *context.Context) { @@ -422,7 +440,7 @@ func TestInsertFilter(t *testing.T) { testName := "TestInsertFilter" mux := NewControllerRegister() - mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}) + mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, WithReturnOnOutput(true)) if !mux.filters[BeforeRouter][0].returnOnOutput { t.Errorf( "%s: passing no variadic params should set returnOnOutput to true", @@ -435,7 +453,7 @@ func TestInsertFilter(t *testing.T) { } mux = NewControllerRegister() - mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, false) + mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, WithReturnOnOutput(false)) if mux.filters[BeforeRouter][0].returnOnOutput { t.Errorf( "%s: passing false as 1st variadic param should set returnOnOutput to false", @@ -443,7 +461,7 @@ func TestInsertFilter(t *testing.T) { } mux = NewControllerRegister() - mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, true, true) + mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, WithReturnOnOutput(true), WithResetParams(true)) if !mux.filters[BeforeRouter][0].resetParams { t.Errorf( "%s: passing true as 2nd variadic param should set resetParams to true", @@ -460,7 +478,7 @@ func TestParamResetFilter(t *testing.T) { mux := NewControllerRegister() - mux.InsertFilter("*", BeforeExec, beegoResetParams, true, true) + mux.InsertFilter("*", BeforeExec, beegoResetParams, WithReturnOnOutput(true), WithResetParams(true)) mux.Get(route, beegoHandleResetParams) @@ -513,8 +531,8 @@ func TestFilterBeforeExec(t *testing.T) { url := "/beforeExec" mux := NewControllerRegister() - mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput) - mux.InsertFilter(url, BeforeExec, beegoBeforeExec1) + mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput, WithReturnOnOutput(true)) + mux.InsertFilter(url, BeforeExec, beegoBeforeExec1, WithReturnOnOutput(true)) mux.Get(url, beegoFilterFunc) @@ -541,7 +559,7 @@ func TestFilterAfterExec(t *testing.T) { mux := NewControllerRegister() mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput) mux.InsertFilter(url, BeforeExec, beegoFilterNoOutput) - mux.InsertFilter(url, AfterExec, beegoAfterExec1, false) + mux.InsertFilter(url, AfterExec, beegoAfterExec1, WithReturnOnOutput(false)) mux.Get(url, beegoFilterFunc) @@ -569,10 +587,10 @@ func TestFilterFinishRouter(t *testing.T) { url := "/finishRouter" mux := NewControllerRegister() - mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput) - mux.InsertFilter(url, BeforeExec, beegoFilterNoOutput) - mux.InsertFilter(url, AfterExec, beegoFilterNoOutput) - mux.InsertFilter(url, FinishRouter, beegoFinishRouter1) + mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput, WithReturnOnOutput(true)) + mux.InsertFilter(url, BeforeExec, beegoFilterNoOutput, WithReturnOnOutput(true)) + mux.InsertFilter(url, AfterExec, beegoFilterNoOutput, WithReturnOnOutput(true)) + mux.InsertFilter(url, FinishRouter, beegoFinishRouter1, WithReturnOnOutput(true)) mux.Get(url, beegoFilterFunc) @@ -603,7 +621,7 @@ func TestFilterFinishRouterMultiFirstOnly(t *testing.T) { url := "/finishRouterMultiFirstOnly" mux := NewControllerRegister() - mux.InsertFilter(url, FinishRouter, beegoFinishRouter1, false) + mux.InsertFilter(url, FinishRouter, beegoFinishRouter1, WithReturnOnOutput(false)) mux.InsertFilter(url, FinishRouter, beegoFinishRouter2) mux.Get(url, beegoFilterFunc) @@ -630,8 +648,8 @@ func TestFilterFinishRouterMulti(t *testing.T) { url := "/finishRouterMulti" mux := NewControllerRegister() - mux.InsertFilter(url, FinishRouter, beegoFinishRouter1, false) - mux.InsertFilter(url, FinishRouter, beegoFinishRouter2, false) + mux.InsertFilter(url, FinishRouter, beegoFinishRouter1, WithReturnOnOutput(false)) + mux.InsertFilter(url, FinishRouter, beegoFinishRouter2, WithReturnOnOutput(false)) mux.Get(url, beegoFilterFunc) @@ -656,17 +674,14 @@ func beegoBeforeRouter1(ctx *context.Context) { ctx.WriteString("|BeforeRouter1") } - func beegoBeforeExec1(ctx *context.Context) { ctx.WriteString("|BeforeExec1") } - func beegoAfterExec1(ctx *context.Context) { ctx.WriteString("|AfterExec1") } - func beegoFinishRouter1(ctx *context.Context) { ctx.WriteString("|FinishRouter1") } @@ -709,3 +724,29 @@ func TestYAMLPrepare(t *testing.T) { t.Errorf(w.Body.String()) } } + +func TestRouterEntityTooLargeCopyBody(t *testing.T) { + _MaxMemory := BConfig.MaxMemory + _CopyRequestBody := BConfig.CopyRequestBody + BConfig.CopyRequestBody = true + BConfig.MaxMemory = 20 + + BeeApp.Cfg.CopyRequestBody = true + BeeApp.Cfg.MaxMemory = 20 + b := bytes.NewBuffer([]byte("barbarbarbarbarbarbarbarbarbar")) + r, _ := http.NewRequest("POST", "/user/123", b) + w := httptest.NewRecorder() + + handler := NewControllerRegister() + handler.Post("/user/:id", func(ctx *context.Context) { + ctx.Output.Body([]byte(ctx.Input.Param(":id"))) + }) + handler.ServeHTTP(w, r) + + BConfig.CopyRequestBody = _CopyRequestBody + BConfig.MaxMemory = _MaxMemory + + if w.Code != http.StatusRequestEntityTooLarge { + t.Errorf("TestRouterRequestEntityTooLarge can't run") + } +} diff --git a/server/web/server.go b/server/web/server.go new file mode 100644 index 00000000..25841563 --- /dev/null +++ b/server/web/server.go @@ -0,0 +1,751 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 web + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/http/fcgi" + "os" + "path" + "strconv" + "strings" + "text/template" + "time" + + "golang.org/x/crypto/acme/autocert" + + "github.com/astaxie/beego/core/logs" + beecontext "github.com/astaxie/beego/server/web/context" + + "github.com/astaxie/beego/core/utils" + "github.com/astaxie/beego/server/web/grace" +) + +var ( + // BeeApp is an application instance + // If you are using single server, you could use this + // But if you need multiple servers, do not use this + BeeApp *HttpServer +) + +func init() { + // create beego application + BeeApp = NewHttpSever() +} + +// HttpServer defines beego application with a new PatternServeMux. +type HttpServer struct { + Handlers *ControllerRegister + Server *http.Server + Cfg *Config +} + +// NewHttpSever returns a new beego application. +// this method will use the BConfig as the configure to create HttpServer +// Be careful that when you update BConfig, the server's Cfg will be updated too +func NewHttpSever() *HttpServer { + return NewHttpServerWithCfg(BConfig) +} + +// NewHttpServerWithCfg will create an sever with specific cfg +func NewHttpServerWithCfg(cfg *Config) *HttpServer { + cr := NewControllerRegisterWithCfg(cfg) + app := &HttpServer{ + Handlers: cr, + Server: &http.Server{}, + Cfg: cfg, + } + + return app +} + +// MiddleWare function for http.Handler +type MiddleWare func(http.Handler) http.Handler + +// Run beego application. +func (app *HttpServer) Run(addr string, mws ...MiddleWare) { + + initBeforeHTTPRun() + + app.initAddr(addr) + + addr = app.Cfg.Listen.HTTPAddr + + if app.Cfg.Listen.HTTPPort != 0 { + addr = fmt.Sprintf("%s:%d", app.Cfg.Listen.HTTPAddr, app.Cfg.Listen.HTTPPort) + } + + var ( + err error + l net.Listener + endRunning = make(chan bool, 1) + ) + + // run cgi server + if app.Cfg.Listen.EnableFcgi { + if app.Cfg.Listen.EnableStdIo { + if err = fcgi.Serve(nil, app.Handlers); err == nil { // standard I/O + logs.Info("Use FCGI via standard I/O") + } else { + logs.Critical("Cannot use FCGI via standard I/O", err) + } + return + } + if app.Cfg.Listen.HTTPPort == 0 { + // remove the Socket file before start + if utils.FileExists(addr) { + os.Remove(addr) + } + l, err = net.Listen("unix", addr) + } else { + l, err = net.Listen("tcp", addr) + } + if err != nil { + logs.Critical("Listen: ", err) + } + if err = fcgi.Serve(l, app.Handlers); err != nil { + logs.Critical("fcgi.Serve: ", err) + } + return + } + + app.Server.Handler = app.Handlers + for i := len(mws) - 1; i >= 0; i-- { + if mws[i] == nil { + continue + } + app.Server.Handler = mws[i](app.Server.Handler) + } + app.Server.ReadTimeout = time.Duration(app.Cfg.Listen.ServerTimeOut) * time.Second + app.Server.WriteTimeout = time.Duration(app.Cfg.Listen.ServerTimeOut) * time.Second + app.Server.ErrorLog = logs.GetLogger("HTTP") + + // run graceful mode + if app.Cfg.Listen.Graceful { + httpsAddr := app.Cfg.Listen.HTTPSAddr + app.Server.Addr = httpsAddr + if app.Cfg.Listen.EnableHTTPS || app.Cfg.Listen.EnableMutualHTTPS { + go func() { + time.Sleep(1000 * time.Microsecond) + if app.Cfg.Listen.HTTPSPort != 0 { + httpsAddr = fmt.Sprintf("%s:%d", app.Cfg.Listen.HTTPSAddr, app.Cfg.Listen.HTTPSPort) + app.Server.Addr = httpsAddr + } + server := grace.NewServer(httpsAddr, app.Server.Handler) + server.Server.ReadTimeout = app.Server.ReadTimeout + server.Server.WriteTimeout = app.Server.WriteTimeout + if app.Cfg.Listen.EnableMutualHTTPS { + if err := server.ListenAndServeMutualTLS(app.Cfg.Listen.HTTPSCertFile, + app.Cfg.Listen.HTTPSKeyFile, + app.Cfg.Listen.TrustCaFile); err != nil { + logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid())) + time.Sleep(100 * time.Microsecond) + } + } else { + if app.Cfg.Listen.AutoTLS { + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist(app.Cfg.Listen.Domains...), + Cache: autocert.DirCache(app.Cfg.Listen.TLSCacheDir), + } + app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate} + app.Cfg.Listen.HTTPSCertFile, app.Cfg.Listen.HTTPSKeyFile = "", "" + } + if err := server.ListenAndServeTLS(app.Cfg.Listen.HTTPSCertFile, app.Cfg.Listen.HTTPSKeyFile); err != nil { + logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid())) + time.Sleep(100 * time.Microsecond) + } + } + endRunning <- true + }() + } + if app.Cfg.Listen.EnableHTTP { + go func() { + server := grace.NewServer(addr, app.Server.Handler) + server.Server.ReadTimeout = app.Server.ReadTimeout + server.Server.WriteTimeout = app.Server.WriteTimeout + if app.Cfg.Listen.ListenTCP4 { + server.Network = "tcp4" + } + if err := server.ListenAndServe(); err != nil { + logs.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid())) + time.Sleep(100 * time.Microsecond) + } + endRunning <- true + }() + } + <-endRunning + return + } + + // run normal mode + if app.Cfg.Listen.EnableHTTPS || app.Cfg.Listen.EnableMutualHTTPS { + go func() { + time.Sleep(1000 * time.Microsecond) + if app.Cfg.Listen.HTTPSPort != 0 { + app.Server.Addr = fmt.Sprintf("%s:%d", app.Cfg.Listen.HTTPSAddr, app.Cfg.Listen.HTTPSPort) + } else if app.Cfg.Listen.EnableHTTP { + logs.Info("Start https server error, conflict with http. Please reset https port") + return + } + logs.Info("https server Running on https://%s", app.Server.Addr) + if app.Cfg.Listen.AutoTLS { + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist(app.Cfg.Listen.Domains...), + Cache: autocert.DirCache(app.Cfg.Listen.TLSCacheDir), + } + app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate} + app.Cfg.Listen.HTTPSCertFile, app.Cfg.Listen.HTTPSKeyFile = "", "" + } else if app.Cfg.Listen.EnableMutualHTTPS { + pool := x509.NewCertPool() + data, err := ioutil.ReadFile(app.Cfg.Listen.TrustCaFile) + if err != nil { + logs.Info("MutualHTTPS should provide TrustCaFile") + return + } + pool.AppendCertsFromPEM(data) + app.Server.TLSConfig = &tls.Config{ + ClientCAs: pool, + ClientAuth: tls.ClientAuthType(app.Cfg.Listen.ClientAuth), + } + } + if err := app.Server.ListenAndServeTLS(app.Cfg.Listen.HTTPSCertFile, app.Cfg.Listen.HTTPSKeyFile); err != nil { + logs.Critical("ListenAndServeTLS: ", err) + time.Sleep(100 * time.Microsecond) + endRunning <- true + } + }() + + } + if app.Cfg.Listen.EnableHTTP { + go func() { + app.Server.Addr = addr + logs.Info("http server Running on http://%s", app.Server.Addr) + if app.Cfg.Listen.ListenTCP4 { + ln, err := net.Listen("tcp4", app.Server.Addr) + if err != nil { + logs.Critical("ListenAndServe: ", err) + time.Sleep(100 * time.Microsecond) + endRunning <- true + return + } + if err = app.Server.Serve(ln); err != nil { + logs.Critical("ListenAndServe: ", err) + time.Sleep(100 * time.Microsecond) + endRunning <- true + return + } + } else { + if err := app.Server.ListenAndServe(); err != nil { + logs.Critical("ListenAndServe: ", err) + time.Sleep(100 * time.Microsecond) + endRunning <- true + } + } + }() + } + <-endRunning +} + +// Router see HttpServer.Router +func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *HttpServer { + return BeeApp.Router(rootpath, c, mappingMethods...) +} + +// Router adds a patterned controller handler to BeeApp. +// it's an alias method of HttpServer.Router. +// usage: +// simple router +// beego.Router("/admin", &admin.UserController{}) +// beego.Router("/admin/index", &admin.ArticleController{}) +// +// regex router +// +// beego.Router("/api/:id([0-9]+)", &controllers.RController{}) +// +// custom rules +// beego.Router("/api/list",&RestController{},"*:ListFood") +// beego.Router("/api/create",&RestController{},"post:CreateFood") +// beego.Router("/api/update",&RestController{},"put:UpdateFood") +// beego.Router("/api/delete",&RestController{},"delete:DeleteFood") +func (app *HttpServer) Router(rootPath string, c ControllerInterface, mappingMethods ...string) *HttpServer { + app.Handlers.Add(rootPath, c, mappingMethods...) + return app +} + +// UnregisterFixedRoute see HttpServer.UnregisterFixedRoute +func UnregisterFixedRoute(fixedRoute string, method string) *HttpServer { + return BeeApp.UnregisterFixedRoute(fixedRoute, method) +} + +// UnregisterFixedRoute unregisters the route with the specified fixedRoute. It is particularly useful +// in web applications that inherit most routes from a base webapp via the underscore +// import, and aim to overwrite only certain paths. +// The method parameter can be empty or "*" for all HTTP methods, or a particular +// method type (e.g. "GET" or "POST") for selective removal. +// +// Usage (replace "GET" with "*" for all methods): +// beego.UnregisterFixedRoute("/yourpreviouspath", "GET") +// beego.Router("/yourpreviouspath", yourControllerAddress, "get:GetNewPage") +func (app *HttpServer) UnregisterFixedRoute(fixedRoute string, method string) *HttpServer { + subPaths := splitPath(fixedRoute) + if method == "" || method == "*" { + for m := range HTTPMETHOD { + if _, ok := app.Handlers.routers[m]; !ok { + continue + } + if app.Handlers.routers[m].prefix == strings.Trim(fixedRoute, "/ ") { + findAndRemoveSingleTree(app.Handlers.routers[m]) + continue + } + findAndRemoveTree(subPaths, app.Handlers.routers[m], m) + } + return app + } + // Single HTTP method + um := strings.ToUpper(method) + if _, ok := app.Handlers.routers[um]; ok { + if app.Handlers.routers[um].prefix == strings.Trim(fixedRoute, "/ ") { + findAndRemoveSingleTree(app.Handlers.routers[um]) + return app + } + findAndRemoveTree(subPaths, app.Handlers.routers[um], um) + } + return app +} + +func findAndRemoveTree(paths []string, entryPointTree *Tree, method string) { + for i := range entryPointTree.fixrouters { + if entryPointTree.fixrouters[i].prefix == paths[0] { + if len(paths) == 1 { + if len(entryPointTree.fixrouters[i].fixrouters) > 0 { + // If the route had children subtrees, remove just the functional leaf, + // to allow children to function as before + if len(entryPointTree.fixrouters[i].leaves) > 0 { + entryPointTree.fixrouters[i].leaves[0] = nil + entryPointTree.fixrouters[i].leaves = entryPointTree.fixrouters[i].leaves[1:] + } + } else { + // Remove the *Tree from the fixrouters slice + entryPointTree.fixrouters[i] = nil + + if i == len(entryPointTree.fixrouters)-1 { + entryPointTree.fixrouters = entryPointTree.fixrouters[:i] + } else { + entryPointTree.fixrouters = append(entryPointTree.fixrouters[:i], entryPointTree.fixrouters[i+1:len(entryPointTree.fixrouters)]...) + } + } + return + } + findAndRemoveTree(paths[1:], entryPointTree.fixrouters[i], method) + } + } +} + +func findAndRemoveSingleTree(entryPointTree *Tree) { + if entryPointTree == nil { + return + } + if len(entryPointTree.fixrouters) > 0 { + // If the route had children subtrees, remove just the functional leaf, + // to allow children to function as before + if len(entryPointTree.leaves) > 0 { + entryPointTree.leaves[0] = nil + entryPointTree.leaves = entryPointTree.leaves[1:] + } + } +} + +// Include see HttpServer.Include +func Include(cList ...ControllerInterface) *HttpServer { + return BeeApp.Include(cList...) +} + +// Include will generate router file in the router/xxx.go from the controller's comments +// usage: +// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) +// type BankAccount struct{ +// beego.Controller +// } +// +// register the function +// func (b *BankAccount)Mapping(){ +// b.Mapping("ShowAccount" , b.ShowAccount) +// b.Mapping("ModifyAccount", b.ModifyAccount) +// } +// +// //@router /account/:id [get] +// func (b *BankAccount) ShowAccount(){ +// //logic +// } +// +// +// //@router /account/:id [post] +// func (b *BankAccount) ModifyAccount(){ +// //logic +// } +// +// the comments @router url methodlist +// url support all the function Router's pattern +// methodlist [get post head put delete options *] +func (app *HttpServer) Include(cList ...ControllerInterface) *HttpServer { + app.Handlers.Include(cList...) + return app +} + +// RESTRouter see HttpServer.RESTRouter +func RESTRouter(rootpath string, c ControllerInterface) *HttpServer { + return BeeApp.RESTRouter(rootpath, c) +} + +// RESTRouter adds a restful controller handler to BeeApp. +// its' controller implements beego.ControllerInterface and +// defines a param "pattern/:objectId" to visit each resource. +func (app *HttpServer) RESTRouter(rootpath string, c ControllerInterface) *HttpServer { + app.Router(rootpath, c) + app.Router(path.Join(rootpath, ":objectId"), c) + return app +} + +// AutoRouter see HttpServer.AutoRouter +func AutoRouter(c ControllerInterface) *HttpServer { + return BeeApp.AutoRouter(c) +} + +// AutoRouter adds defined controller handler to BeeApp. +// it's same to HttpServer.AutoRouter. +// if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page, +// visit the url /main/list to exec List function or /main/page to exec Page function. +func (app *HttpServer) AutoRouter(c ControllerInterface) *HttpServer { + app.Handlers.AddAuto(c) + return app +} + +// AutoPrefix see HttpServer.AutoPrefix +func AutoPrefix(prefix string, c ControllerInterface) *HttpServer { + return BeeApp.AutoPrefix(prefix, c) +} + +// AutoPrefix adds controller handler to BeeApp with prefix. +// it's same to HttpServer.AutoRouterWithPrefix. +// if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page, +// visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function. +func (app *HttpServer) AutoPrefix(prefix string, c ControllerInterface) *HttpServer { + app.Handlers.AddAutoPrefix(prefix, c) + return app +} + +// Get see HttpServer.Get +func Get(rootpath string, f FilterFunc) *HttpServer { + return BeeApp.Get(rootpath, f) +} + +// Get used to register router for Get method +// usage: +// beego.Get("/", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (app *HttpServer) Get(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Get(rootpath, f) + return app +} + +// Post see HttpServer.Post +func Post(rootpath string, f FilterFunc) *HttpServer { + return BeeApp.Post(rootpath, f) +} + +// Post used to register router for Post method +// usage: +// beego.Post("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (app *HttpServer) Post(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Post(rootpath, f) + return app +} + +// Delete see HttpServer.Delete +func Delete(rootpath string, f FilterFunc) *HttpServer { + return BeeApp.Delete(rootpath, f) +} + +// Delete used to register router for Delete method +// usage: +// beego.Delete("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (app *HttpServer) Delete(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Delete(rootpath, f) + return app +} + +// Put see HttpServer.Put +func Put(rootpath string, f FilterFunc) *HttpServer { + return BeeApp.Put(rootpath, f) +} + +// Put used to register router for Put method +// usage: +// beego.Put("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (app *HttpServer) Put(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Put(rootpath, f) + return app +} + +// Head see HttpServer.Head +func Head(rootpath string, f FilterFunc) *HttpServer { + return BeeApp.Head(rootpath, f) +} + +// Head used to register router for Head method +// usage: +// beego.Head("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (app *HttpServer) Head(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Head(rootpath, f) + return app +} + +// Options see HttpServer.Options +func Options(rootpath string, f FilterFunc) *HttpServer { + BeeApp.Handlers.Options(rootpath, f) + return BeeApp +} + +// Options used to register router for Options method +// usage: +// beego.Options("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (app *HttpServer) Options(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Options(rootpath, f) + return app +} + +// Patch see HttpServer.Patch +func Patch(rootpath string, f FilterFunc) *HttpServer { + return BeeApp.Patch(rootpath, f) +} + +// Patch used to register router for Patch method +// usage: +// beego.Patch("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (app *HttpServer) Patch(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Patch(rootpath, f) + return app +} + +// Any see HttpServer.Any +func Any(rootpath string, f FilterFunc) *HttpServer { + return BeeApp.Any(rootpath, f) +} + +// Any used to register router for all methods +// usage: +// beego.Any("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (app *HttpServer) Any(rootpath string, f FilterFunc) *HttpServer { + app.Handlers.Any(rootpath, f) + return app +} + +// Handler see HttpServer.Handler +func Handler(rootpath string, h http.Handler, options ...interface{}) *HttpServer { + return BeeApp.Handler(rootpath, h, options...) +} + +// Handler used to register a Handler router +// usage: +// beego.Handler("/api", http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { +// fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) +// })) +func (app *HttpServer) Handler(rootpath string, h http.Handler, options ...interface{}) *HttpServer { + app.Handlers.Handler(rootpath, h, options...) + return app +} + +// InserFilter see HttpServer.InsertFilter +func InsertFilter(pattern string, pos int, filter FilterFunc, opts ...FilterOpt) *HttpServer { + return BeeApp.InsertFilter(pattern, pos, filter, opts...) +} + +// InsertFilter adds a FilterFunc with pattern condition and action constant. +// The pos means action constant including +// beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter. +// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute) +func (app *HttpServer) InsertFilter(pattern string, pos int, filter FilterFunc, opts ...FilterOpt) *HttpServer { + app.Handlers.InsertFilter(pattern, pos, filter, opts...) + return app +} + +// InsertFilterChain see HttpServer.InsertFilterChain +func InsertFilterChain(pattern string, filterChain FilterChain, opts ...FilterOpt) *HttpServer { + return BeeApp.InsertFilterChain(pattern, filterChain, opts...) +} + +// InsertFilterChain adds a FilterFunc built by filterChain. +// This filter will be executed before all filters. +// the filter's behavior like stack's behavior +// and the last filter is serving the http request +func (app *HttpServer) InsertFilterChain(pattern string, filterChain FilterChain, opts ...FilterOpt) *HttpServer { + app.Handlers.InsertFilterChain(pattern, filterChain, opts...) + return app +} + +func (app *HttpServer) initAddr(addr string) { + strs := strings.Split(addr, ":") + if len(strs) > 0 && strs[0] != "" { + app.Cfg.Listen.HTTPAddr = strs[0] + app.Cfg.Listen.Domains = []string{strs[0]} + } + if len(strs) > 1 && strs[1] != "" { + app.Cfg.Listen.HTTPPort, _ = strconv.Atoi(strs[1]) + } +} + +func (app *HttpServer) LogAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) { + // Skip logging if AccessLogs config is false + if !app.Cfg.Log.AccessLogs { + return + } + // Skip logging static requests unless EnableStaticLogs config is true + if !app.Cfg.Log.EnableStaticLogs && DefaultAccessLogFilter.Filter(ctx) { + return + } + var ( + requestTime time.Time + elapsedTime time.Duration + r = ctx.Request + ) + if startTime != nil { + requestTime = *startTime + elapsedTime = time.Since(*startTime) + } + record := &logs.AccessLogRecord{ + RemoteAddr: ctx.Input.IP(), + RequestTime: requestTime, + RequestMethod: r.Method, + Request: fmt.Sprintf("%s %s %s", r.Method, r.RequestURI, r.Proto), + ServerProtocol: r.Proto, + Host: r.Host, + Status: statusCode, + ElapsedTime: elapsedTime, + HTTPReferrer: r.Header.Get("Referer"), + HTTPUserAgent: r.Header.Get("User-Agent"), + RemoteUser: r.Header.Get("Remote-User"), + BodyBytesSent: r.ContentLength, + } + logs.AccessLog(record, app.Cfg.Log.AccessLogsFormat) +} + +// PrintTree prints all registered routers. +func (app *HttpServer) PrintTree() M { + var ( + content = M{} + methods = []string{} + methodsData = make(M) + ) + for method, t := range app.Handlers.routers { + + resultList := new([][]string) + + printTree(resultList, t) + + methods = append(methods, template.HTMLEscapeString(method)) + methodsData[template.HTMLEscapeString(method)] = resultList + } + + content["Data"] = methodsData + content["Methods"] = methods + return content +} + +func printTree(resultList *[][]string, t *Tree) { + for _, tr := range t.fixrouters { + printTree(resultList, tr) + } + if t.wildcard != nil { + printTree(resultList, t.wildcard) + } + for _, l := range t.leaves { + if v, ok := l.runObject.(*ControllerInfo); ok { + if v.routerType == routerTypeBeego { + var result = []string{ + template.HTMLEscapeString(v.pattern), + template.HTMLEscapeString(fmt.Sprintf("%s", v.methods)), + template.HTMLEscapeString(v.controllerType.String()), + } + *resultList = append(*resultList, result) + } else if v.routerType == routerTypeRESTFul { + var result = []string{ + template.HTMLEscapeString(v.pattern), + template.HTMLEscapeString(fmt.Sprintf("%s", v.methods)), + "", + } + *resultList = append(*resultList, result) + } else if v.routerType == routerTypeHandler { + var result = []string{ + template.HTMLEscapeString(v.pattern), + "", + "", + } + *resultList = append(*resultList, result) + } + } + } +} + +func (app *HttpServer) reportFilter() M { + filterTypeData := make(M) + // filterTypes := []string{} + if app.Handlers.enableFilter { + // var filterType string + for k, fr := range map[int]string{ + BeforeStatic: "Before Static", + BeforeRouter: "Before Router", + BeforeExec: "Before Exec", + AfterExec: "After Exec", + FinishRouter: "Finish Router", + } { + if bf := app.Handlers.filters[k]; len(bf) > 0 { + resultList := new([][]string) + for _, f := range bf { + var result = []string{ + // void xss + template.HTMLEscapeString(f.pattern), + template.HTMLEscapeString(utils.GetFuncName(f.filterFunc)), + } + *resultList = append(*resultList, result) + } + filterTypeData[fr] = resultList + } + } + } + + return filterTypeData +} diff --git a/server/web/server_test.go b/server/web/server_test.go new file mode 100644 index 00000000..0b0c601c --- /dev/null +++ b/server/web/server_test.go @@ -0,0 +1,30 @@ +// Copyright 2020 +// +// Licensed 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 web + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewHttpServerWithCfg(t *testing.T) { + + BConfig.AppName = "Before" + svr := NewHttpServerWithCfg(BConfig) + svr.Cfg.AppName = "hello" + assert.Equal(t, "hello", BConfig.AppName) + +} diff --git a/session/README.md b/server/web/session/README.md similarity index 98% rename from session/README.md rename to server/web/session/README.md index 6d0a297e..a5c3bd6d 100644 --- a/session/README.md +++ b/server/web/session/README.md @@ -101,7 +101,7 @@ Maybe you will find the **memory** provider is a good example. type Provider interface { SessionInit(gclifetime int64, config string) error SessionRead(sid string) (SessionStore, error) - SessionExist(sid string) bool + SessionExist(sid string) (bool, error) SessionRegenerate(oldsid, sid string) (SessionStore, error) SessionDestroy(sid string) error SessionAll() int //get all active session diff --git a/session/couchbase/sess_couchbase.go b/server/web/session/couchbase/sess_couchbase.go similarity index 69% rename from session/couchbase/sess_couchbase.go rename to server/web/session/couchbase/sess_couchbase.go index 707d042c..7f15956a 100644 --- a/session/couchbase/sess_couchbase.go +++ b/server/web/session/couchbase/sess_couchbase.go @@ -33,13 +33,15 @@ package couchbase import ( + "context" + "encoding/json" "net/http" "strings" "sync" couchbase "github.com/couchbase/go-couchbase" - "github.com/astaxie/beego/session" + "github.com/astaxie/beego/server/web/session" ) var couchbpder = &Provider{} @@ -56,14 +58,14 @@ type SessionStore struct { // Provider couchabse provided type Provider struct { maxlifetime int64 - savePath string - pool string - bucket string + SavePath string `json:"save_path"` + Pool string `json:"pool"` + Bucket string `json:"bucket"` b *couchbase.Bucket } // Set value to couchabse session -func (cs *SessionStore) Set(key, value interface{}) error { +func (cs *SessionStore) Set(ctx context.Context, key, value interface{}) error { cs.lock.Lock() defer cs.lock.Unlock() cs.values[key] = value @@ -71,7 +73,7 @@ func (cs *SessionStore) Set(key, value interface{}) error { } // Get value from couchabse session -func (cs *SessionStore) Get(key interface{}) interface{} { +func (cs *SessionStore) Get(ctx context.Context, key interface{}) interface{} { cs.lock.RLock() defer cs.lock.RUnlock() if v, ok := cs.values[key]; ok { @@ -81,7 +83,7 @@ func (cs *SessionStore) Get(key interface{}) interface{} { } // Delete value in couchbase session by given key -func (cs *SessionStore) Delete(key interface{}) error { +func (cs *SessionStore) Delete(ctx context.Context, key interface{}) error { cs.lock.Lock() defer cs.lock.Unlock() delete(cs.values, key) @@ -89,7 +91,7 @@ func (cs *SessionStore) Delete(key interface{}) error { } // Flush Clean all values in couchbase session -func (cs *SessionStore) Flush() error { +func (cs *SessionStore) Flush(context.Context) error { cs.lock.Lock() defer cs.lock.Unlock() cs.values = make(map[interface{}]interface{}) @@ -97,12 +99,12 @@ func (cs *SessionStore) Flush() error { } // SessionID Get couchbase session store id -func (cs *SessionStore) SessionID() string { +func (cs *SessionStore) SessionID(context.Context) string { return cs.sid } // SessionRelease Write couchbase session with Gob string -func (cs *SessionStore) SessionRelease(w http.ResponseWriter) { +func (cs *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { defer cs.b.Close() bo, err := session.EncodeGob(cs.values) @@ -114,17 +116,17 @@ func (cs *SessionStore) SessionRelease(w http.ResponseWriter) { } func (cp *Provider) getBucket() *couchbase.Bucket { - c, err := couchbase.Connect(cp.savePath) + c, err := couchbase.Connect(cp.SavePath) if err != nil { return nil } - pool, err := c.GetPool(cp.pool) + pool, err := c.GetPool(cp.Pool) if err != nil { return nil } - bucket, err := pool.GetBucket(cp.bucket) + bucket, err := pool.GetBucket(cp.Bucket) if err != nil { return nil } @@ -134,25 +136,38 @@ func (cp *Provider) getBucket() *couchbase.Bucket { // SessionInit init couchbase session // savepath like couchbase server REST/JSON URL -// e.g. http://host:port/, Pool, Bucket -func (cp *Provider) SessionInit(maxlifetime int64, savePath string) error { +// For v1.x e.g. http://host:port/, Pool, Bucket +// For v2.x, you should pass json string. +// e.g. { "save_path": "http://host:port/", "pool": "mypool", "bucket": "mybucket"} +func (cp *Provider) SessionInit(ctx context.Context, maxlifetime int64, cfg string) error { cp.maxlifetime = maxlifetime + cfg = strings.TrimSpace(cfg) + // we think this is v2.0, using json to init the session + if strings.HasPrefix(cfg, "{") { + return json.Unmarshal([]byte(cfg), cp) + } else { + return cp.initOldStyle(cfg) + } +} + +// initOldStyle keep compatible with v1.x +func (cp *Provider) initOldStyle(savePath string) error { configs := strings.Split(savePath, ",") if len(configs) > 0 { - cp.savePath = configs[0] + cp.SavePath = configs[0] } if len(configs) > 1 { - cp.pool = configs[1] + cp.Pool = configs[1] } if len(configs) > 2 { - cp.bucket = configs[2] + cp.Bucket = configs[2] } return nil } // SessionRead read couchbase session by sid -func (cp *Provider) SessionRead(sid string) (session.Store, error) { +func (cp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { cp.b = cp.getBucket() var ( @@ -179,20 +194,20 @@ func (cp *Provider) SessionRead(sid string) (session.Store, error) { // SessionExist Check couchbase session exist. // it checkes sid exist or not. -func (cp *Provider) SessionExist(sid string) bool { +func (cp *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { cp.b = cp.getBucket() defer cp.b.Close() var doc []byte if err := cp.b.Get(sid, &doc); err != nil || doc == nil { - return false + return false, err } - return true + return true, nil } // SessionRegenerate remove oldsid and use sid to generate new session -func (cp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { +func (cp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { cp.b = cp.getBucket() var doc []byte @@ -224,8 +239,8 @@ func (cp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) return cs, nil } -// SessionDestroy Remove bucket in this couchbase -func (cp *Provider) SessionDestroy(sid string) error { +// SessionDestroy Remove Bucket in this couchbase +func (cp *Provider) SessionDestroy(ctx context.Context, sid string) error { cp.b = cp.getBucket() defer cp.b.Close() @@ -234,11 +249,11 @@ func (cp *Provider) SessionDestroy(sid string) error { } // SessionGC Recycle -func (cp *Provider) SessionGC() { +func (cp *Provider) SessionGC(context.Context) { } // SessionAll return all active session -func (cp *Provider) SessionAll() int { +func (cp *Provider) SessionAll(context.Context) int { return 0 } diff --git a/server/web/session/couchbase/sess_couchbase_test.go b/server/web/session/couchbase/sess_couchbase_test.go new file mode 100644 index 00000000..5959f9c3 --- /dev/null +++ b/server/web/session/couchbase/sess_couchbase_test.go @@ -0,0 +1,43 @@ +// Copyright 2020 +// +// Licensed 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 couchbase + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestProvider_SessionInit(t *testing.T) { + // using old style + savePath := `http://host:port/,Pool,Bucket` + cp := &Provider{} + cp.SessionInit(context.Background(), 12, savePath) + assert.Equal(t, "http://host:port/", cp.SavePath) + assert.Equal(t, "Pool", cp.Pool) + assert.Equal(t, "Bucket", cp.Bucket) + assert.Equal(t, int64(12), cp.maxlifetime) + + savePath = ` +{ "save_path": "my save path", "pool": "mypool", "bucket": "mybucket"} +` + cp = &Provider{} + cp.SessionInit(context.Background(), 12, savePath) + assert.Equal(t, "my save path", cp.SavePath) + assert.Equal(t, "mypool", cp.Pool) + assert.Equal(t, "mybucket", cp.Bucket) + assert.Equal(t, int64(12), cp.maxlifetime) +} diff --git a/session/ledis/ledis_session.go b/server/web/session/ledis/ledis_session.go similarity index 59% rename from session/ledis/ledis_session.go rename to server/web/session/ledis/ledis_session.go index ee81df67..5b930fcd 100644 --- a/session/ledis/ledis_session.go +++ b/server/web/session/ledis/ledis_session.go @@ -2,6 +2,8 @@ package ledis import ( + "context" + "encoding/json" "net/http" "strconv" "strings" @@ -10,7 +12,7 @@ import ( "github.com/ledisdb/ledisdb/config" "github.com/ledisdb/ledisdb/ledis" - "github.com/astaxie/beego/session" + "github.com/astaxie/beego/server/web/session" ) var ( @@ -27,7 +29,7 @@ type SessionStore struct { } // Set value in ledis session -func (ls *SessionStore) Set(key, value interface{}) error { +func (ls *SessionStore) Set(ctx context.Context, key, value interface{}) error { ls.lock.Lock() defer ls.lock.Unlock() ls.values[key] = value @@ -35,7 +37,7 @@ func (ls *SessionStore) Set(key, value interface{}) error { } // Get value in ledis session -func (ls *SessionStore) Get(key interface{}) interface{} { +func (ls *SessionStore) Get(ctx context.Context, key interface{}) interface{} { ls.lock.RLock() defer ls.lock.RUnlock() if v, ok := ls.values[key]; ok { @@ -45,7 +47,7 @@ func (ls *SessionStore) Get(key interface{}) interface{} { } // Delete value in ledis session -func (ls *SessionStore) Delete(key interface{}) error { +func (ls *SessionStore) Delete(ctx context.Context, key interface{}) error { ls.lock.Lock() defer ls.lock.Unlock() delete(ls.values, key) @@ -53,7 +55,7 @@ func (ls *SessionStore) Delete(key interface{}) error { } // Flush clear all values in ledis session -func (ls *SessionStore) Flush() error { +func (ls *SessionStore) Flush(context.Context) error { ls.lock.Lock() defer ls.lock.Unlock() ls.values = make(map[interface{}]interface{}) @@ -61,12 +63,12 @@ func (ls *SessionStore) Flush() error { } // SessionID get ledis session id -func (ls *SessionStore) SessionID() string { +func (ls *SessionStore) SessionID(context.Context) string { return ls.sid } // SessionRelease save session values to ledis -func (ls *SessionStore) SessionRelease(w http.ResponseWriter) { +func (ls *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { b, err := session.EncodeGob(ls.values) if err != nil { return @@ -78,40 +80,56 @@ func (ls *SessionStore) SessionRelease(w http.ResponseWriter) { // Provider ledis session provider type Provider struct { maxlifetime int64 - savePath string - db int + SavePath string `json:"save_path"` + Db int `json:"db"` } // SessionInit init ledis session // savepath like ledis server saveDataPath,pool size -// e.g. 127.0.0.1:6379,100,astaxie -func (lp *Provider) SessionInit(maxlifetime int64, savePath string) error { +// v1.x e.g. 127.0.0.1:6379,100 +// v2.x you should pass a json string +// e.g. { "save_path": "my save path", "db": 100} +func (lp *Provider) SessionInit(ctx context.Context, maxlifetime int64, cfgStr string) error { var err error lp.maxlifetime = maxlifetime - configs := strings.Split(savePath, ",") - if len(configs) == 1 { - lp.savePath = configs[0] - } else if len(configs) == 2 { - lp.savePath = configs[0] - lp.db, err = strconv.Atoi(configs[1]) - if err != nil { - return err - } + cfgStr = strings.TrimSpace(cfgStr) + // we think cfgStr is v2.0, using json to init the session + if strings.HasPrefix(cfgStr, "{") { + err = json.Unmarshal([]byte(cfgStr), lp) + } else { + err = lp.initOldStyle(cfgStr) } + + if err != nil { + return err + } + cfg := new(config.Config) - cfg.DataDir = lp.savePath + cfg.DataDir = lp.SavePath var ledisInstance *ledis.Ledis ledisInstance, err = ledis.Open(cfg) if err != nil { return err } - c, err = ledisInstance.Select(lp.db) + c, err = ledisInstance.Select(lp.Db) + return err +} + +func (lp *Provider) initOldStyle(cfgStr string) error { + var err error + configs := strings.Split(cfgStr, ",") + if len(configs) == 1 { + lp.SavePath = configs[0] + } else if len(configs) == 2 { + lp.SavePath = configs[0] + lp.Db, err = strconv.Atoi(configs[1]) + } return err } // SessionRead read ledis session by sid -func (lp *Provider) SessionRead(sid string) (session.Store, error) { +func (lp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { var ( kv map[interface{}]interface{} err error @@ -132,13 +150,13 @@ func (lp *Provider) SessionRead(sid string) (session.Store, error) { } // SessionExist check ledis session exist by sid -func (lp *Provider) SessionExist(sid string) bool { +func (lp *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { count, _ := c.Exists([]byte(sid)) - return count != 0 + return count != 0, nil } // SessionRegenerate generate new sid for ledis session -func (lp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { +func (lp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { count, _ := c.Exists([]byte(sid)) if count == 0 { // oldsid doesn't exists, set the new sid directly @@ -151,21 +169,21 @@ func (lp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) c.Set([]byte(sid), data) c.Expire([]byte(sid), lp.maxlifetime) } - return lp.SessionRead(sid) + return lp.SessionRead(context.Background(), sid) } // SessionDestroy delete ledis session by id -func (lp *Provider) SessionDestroy(sid string) error { +func (lp *Provider) SessionDestroy(ctx context.Context, sid string) error { c.Del([]byte(sid)) return nil } // SessionGC Impelment method, no used. -func (lp *Provider) SessionGC() { +func (lp *Provider) SessionGC(context.Context) { } // SessionAll return all active session -func (lp *Provider) SessionAll() int { +func (lp *Provider) SessionAll(context.Context) int { return 0 } func init() { diff --git a/server/web/session/ledis/ledis_session_test.go b/server/web/session/ledis/ledis_session_test.go new file mode 100644 index 00000000..1cfb3ed1 --- /dev/null +++ b/server/web/session/ledis/ledis_session_test.go @@ -0,0 +1,41 @@ +// Copyright 2020 +// +// Licensed 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 ledis + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestProvider_SessionInit(t *testing.T) { + // using old style + savePath := `http://host:port/,100` + cp := &Provider{} + cp.SessionInit(context.Background(), 12, savePath) + assert.Equal(t, "http://host:port/", cp.SavePath) + assert.Equal(t, 100, cp.Db) + assert.Equal(t, int64(12), cp.maxlifetime) + + savePath = ` +{ "save_path": "my save path", "db": 100} +` + cp = &Provider{} + cp.SessionInit(context.Background(), 12, savePath) + assert.Equal(t, "my save path", cp.SavePath) + assert.Equal(t, 100, cp.Db) + assert.Equal(t, int64(12), cp.maxlifetime) +} diff --git a/session/memcache/sess_memcache.go b/server/web/session/memcache/sess_memcache.go similarity index 81% rename from session/memcache/sess_memcache.go rename to server/web/session/memcache/sess_memcache.go index 85a2d815..168116ef 100644 --- a/session/memcache/sess_memcache.go +++ b/server/web/session/memcache/sess_memcache.go @@ -33,11 +33,12 @@ package memcache import ( + "context" "net/http" "strings" "sync" - "github.com/astaxie/beego/session" + "github.com/astaxie/beego/server/web/session" "github.com/bradfitz/gomemcache/memcache" ) @@ -54,7 +55,7 @@ type SessionStore struct { } // Set value in memcache session -func (rs *SessionStore) Set(key, value interface{}) error { +func (rs *SessionStore) Set(ctx context.Context, key, value interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() rs.values[key] = value @@ -62,7 +63,7 @@ func (rs *SessionStore) Set(key, value interface{}) error { } // Get value in memcache session -func (rs *SessionStore) Get(key interface{}) interface{} { +func (rs *SessionStore) Get(ctx context.Context, key interface{}) interface{} { rs.lock.RLock() defer rs.lock.RUnlock() if v, ok := rs.values[key]; ok { @@ -72,7 +73,7 @@ func (rs *SessionStore) Get(key interface{}) interface{} { } // Delete value in memcache session -func (rs *SessionStore) Delete(key interface{}) error { +func (rs *SessionStore) Delete(ctx context.Context, key interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() delete(rs.values, key) @@ -80,7 +81,7 @@ func (rs *SessionStore) Delete(key interface{}) error { } // Flush clear all values in memcache session -func (rs *SessionStore) Flush() error { +func (rs *SessionStore) Flush(context.Context) error { rs.lock.Lock() defer rs.lock.Unlock() rs.values = make(map[interface{}]interface{}) @@ -88,12 +89,12 @@ func (rs *SessionStore) Flush() error { } // SessionID get memcache session id -func (rs *SessionStore) SessionID() string { +func (rs *SessionStore) SessionID(context.Context) string { return rs.sid } // SessionRelease save session values to memcache -func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { +func (rs *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { b, err := session.EncodeGob(rs.values) if err != nil { return @@ -113,7 +114,7 @@ type MemProvider struct { // SessionInit init memcache session // savepath like // e.g. 127.0.0.1:9090 -func (rp *MemProvider) SessionInit(maxlifetime int64, savePath string) error { +func (rp *MemProvider) SessionInit(ctx context.Context, maxlifetime int64, savePath string) error { rp.maxlifetime = maxlifetime rp.conninfo = strings.Split(savePath, ";") client = memcache.New(rp.conninfo...) @@ -121,7 +122,7 @@ func (rp *MemProvider) SessionInit(maxlifetime int64, savePath string) error { } // SessionRead read memcache session by sid -func (rp *MemProvider) SessionRead(sid string) (session.Store, error) { +func (rp *MemProvider) SessionRead(ctx context.Context, sid string) (session.Store, error) { if client == nil { if err := rp.connectInit(); err != nil { return nil, err @@ -149,20 +150,20 @@ func (rp *MemProvider) SessionRead(sid string) (session.Store, error) { } // SessionExist check memcache session exist by sid -func (rp *MemProvider) SessionExist(sid string) bool { +func (rp *MemProvider) SessionExist(ctx context.Context, sid string) (bool, error) { if client == nil { if err := rp.connectInit(); err != nil { - return false + return false, err } } if item, err := client.Get(sid); err != nil || len(item.Value) == 0 { - return false + return false, err } - return true + return true, nil } // SessionRegenerate generate new sid for memcache session -func (rp *MemProvider) SessionRegenerate(oldsid, sid string) (session.Store, error) { +func (rp *MemProvider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { if client == nil { if err := rp.connectInit(); err != nil { return nil, err @@ -201,7 +202,7 @@ func (rp *MemProvider) SessionRegenerate(oldsid, sid string) (session.Store, err } // SessionDestroy delete memcache session by id -func (rp *MemProvider) SessionDestroy(sid string) error { +func (rp *MemProvider) SessionDestroy(ctx context.Context, sid string) error { if client == nil { if err := rp.connectInit(); err != nil { return err @@ -217,11 +218,11 @@ func (rp *MemProvider) connectInit() error { } // SessionGC Impelment method, no used. -func (rp *MemProvider) SessionGC() { +func (rp *MemProvider) SessionGC(context.Context) { } // SessionAll return all activeSession -func (rp *MemProvider) SessionAll() int { +func (rp *MemProvider) SessionAll(context.Context) int { return 0 } diff --git a/session/mysql/sess_mysql.go b/server/web/session/mysql/sess_mysql.go similarity index 82% rename from session/mysql/sess_mysql.go rename to server/web/session/mysql/sess_mysql.go index 301353ab..89da361d 100644 --- a/session/mysql/sess_mysql.go +++ b/server/web/session/mysql/sess_mysql.go @@ -41,12 +41,13 @@ package mysql import ( + "context" "database/sql" "net/http" "sync" "time" - "github.com/astaxie/beego/session" + "github.com/astaxie/beego/server/web/session" // import mysql driver _ "github.com/go-sql-driver/mysql" ) @@ -67,7 +68,7 @@ type SessionStore struct { // Set value in mysql session. // it is temp value in map. -func (st *SessionStore) Set(key, value interface{}) error { +func (st *SessionStore) Set(ctx context.Context, key, value interface{}) error { st.lock.Lock() defer st.lock.Unlock() st.values[key] = value @@ -75,7 +76,7 @@ func (st *SessionStore) Set(key, value interface{}) error { } // Get value from mysql session -func (st *SessionStore) Get(key interface{}) interface{} { +func (st *SessionStore) Get(ctx context.Context, key interface{}) interface{} { st.lock.RLock() defer st.lock.RUnlock() if v, ok := st.values[key]; ok { @@ -85,7 +86,7 @@ func (st *SessionStore) Get(key interface{}) interface{} { } // Delete value in mysql session -func (st *SessionStore) Delete(key interface{}) error { +func (st *SessionStore) Delete(ctx context.Context, key interface{}) error { st.lock.Lock() defer st.lock.Unlock() delete(st.values, key) @@ -93,7 +94,7 @@ func (st *SessionStore) Delete(key interface{}) error { } // Flush clear all values in mysql session -func (st *SessionStore) Flush() error { +func (st *SessionStore) Flush(context.Context) error { st.lock.Lock() defer st.lock.Unlock() st.values = make(map[interface{}]interface{}) @@ -101,13 +102,13 @@ func (st *SessionStore) Flush() error { } // SessionID get session id of this mysql session store -func (st *SessionStore) SessionID() string { +func (st *SessionStore) SessionID(context.Context) string { return st.sid } // SessionRelease save mysql session values to database. // must call this method to save values to database. -func (st *SessionStore) SessionRelease(w http.ResponseWriter) { +func (st *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { defer st.c.Close() b, err := session.EncodeGob(st.values) if err != nil { @@ -134,14 +135,14 @@ func (mp *Provider) connectInit() *sql.DB { // SessionInit init mysql session. // savepath is the connection string of mysql. -func (mp *Provider) SessionInit(maxlifetime int64, savePath string) error { +func (mp *Provider) SessionInit(ctx context.Context, maxlifetime int64, savePath string) error { mp.maxlifetime = maxlifetime mp.savePath = savePath return nil } // SessionRead get mysql session by sid -func (mp *Provider) SessionRead(sid string) (session.Store, error) { +func (mp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { c := mp.connectInit() row := c.QueryRow("select session_data from "+TableName+" where session_key=?", sid) var sessiondata []byte @@ -164,17 +165,23 @@ func (mp *Provider) SessionRead(sid string) (session.Store, error) { } // SessionExist check mysql session exist -func (mp *Provider) SessionExist(sid string) bool { +func (mp *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { c := mp.connectInit() defer c.Close() row := c.QueryRow("select session_data from "+TableName+" where session_key=?", sid) var sessiondata []byte err := row.Scan(&sessiondata) - return err != sql.ErrNoRows + if err != nil { + if err == sql.ErrNoRows { + return false, nil + } + return false, err + } + return true, nil } // SessionRegenerate generate new sid for mysql session -func (mp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { +func (mp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { c := mp.connectInit() row := c.QueryRow("select session_data from "+TableName+" where session_key=?", oldsid) var sessiondata []byte @@ -197,7 +204,7 @@ func (mp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) } // SessionDestroy delete mysql session by sid -func (mp *Provider) SessionDestroy(sid string) error { +func (mp *Provider) SessionDestroy(ctx context.Context, sid string) error { c := mp.connectInit() c.Exec("DELETE FROM "+TableName+" where session_key=?", sid) c.Close() @@ -205,14 +212,14 @@ func (mp *Provider) SessionDestroy(sid string) error { } // SessionGC delete expired values in mysql session -func (mp *Provider) SessionGC() { +func (mp *Provider) SessionGC(context.Context) { c := mp.connectInit() c.Exec("DELETE from "+TableName+" where session_expiry < ?", time.Now().Unix()-mp.maxlifetime) c.Close() } // SessionAll count values in mysql session -func (mp *Provider) SessionAll() int { +func (mp *Provider) SessionAll(context.Context) int { c := mp.connectInit() defer c.Close() var total int diff --git a/session/postgres/sess_postgresql.go b/server/web/session/postgres/sess_postgresql.go similarity index 83% rename from session/postgres/sess_postgresql.go rename to server/web/session/postgres/sess_postgresql.go index 0b8b9645..a83ac083 100644 --- a/session/postgres/sess_postgresql.go +++ b/server/web/session/postgres/sess_postgresql.go @@ -51,12 +51,13 @@ package postgres import ( + "context" "database/sql" "net/http" "sync" "time" - "github.com/astaxie/beego/session" + "github.com/astaxie/beego/server/web/session" // import postgresql Driver _ "github.com/lib/pq" ) @@ -73,7 +74,7 @@ type SessionStore struct { // Set value in postgresql session. // it is temp value in map. -func (st *SessionStore) Set(key, value interface{}) error { +func (st *SessionStore) Set(ctx context.Context, key, value interface{}) error { st.lock.Lock() defer st.lock.Unlock() st.values[key] = value @@ -81,7 +82,7 @@ func (st *SessionStore) Set(key, value interface{}) error { } // Get value from postgresql session -func (st *SessionStore) Get(key interface{}) interface{} { +func (st *SessionStore) Get(ctx context.Context, key interface{}) interface{} { st.lock.RLock() defer st.lock.RUnlock() if v, ok := st.values[key]; ok { @@ -91,7 +92,7 @@ func (st *SessionStore) Get(key interface{}) interface{} { } // Delete value in postgresql session -func (st *SessionStore) Delete(key interface{}) error { +func (st *SessionStore) Delete(ctx context.Context, key interface{}) error { st.lock.Lock() defer st.lock.Unlock() delete(st.values, key) @@ -99,7 +100,7 @@ func (st *SessionStore) Delete(key interface{}) error { } // Flush clear all values in postgresql session -func (st *SessionStore) Flush() error { +func (st *SessionStore) Flush(context.Context) error { st.lock.Lock() defer st.lock.Unlock() st.values = make(map[interface{}]interface{}) @@ -107,13 +108,13 @@ func (st *SessionStore) Flush() error { } // SessionID get session id of this postgresql session store -func (st *SessionStore) SessionID() string { +func (st *SessionStore) SessionID(context.Context) string { return st.sid } // SessionRelease save postgresql session values to database. // must call this method to save values to database. -func (st *SessionStore) SessionRelease(w http.ResponseWriter) { +func (st *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { defer st.c.Close() b, err := session.EncodeGob(st.values) if err != nil { @@ -141,14 +142,14 @@ func (mp *Provider) connectInit() *sql.DB { // SessionInit init postgresql session. // savepath is the connection string of postgresql. -func (mp *Provider) SessionInit(maxlifetime int64, savePath string) error { +func (mp *Provider) SessionInit(ctx context.Context, maxlifetime int64, savePath string) error { mp.maxlifetime = maxlifetime mp.savePath = savePath return nil } // SessionRead get postgresql session by sid -func (mp *Provider) SessionRead(sid string) (session.Store, error) { +func (mp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { c := mp.connectInit() row := c.QueryRow("select session_data from session where session_key=$1", sid) var sessiondata []byte @@ -178,17 +179,23 @@ func (mp *Provider) SessionRead(sid string) (session.Store, error) { } // SessionExist check postgresql session exist -func (mp *Provider) SessionExist(sid string) bool { +func (mp *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { c := mp.connectInit() defer c.Close() row := c.QueryRow("select session_data from session where session_key=$1", sid) var sessiondata []byte err := row.Scan(&sessiondata) - return err != sql.ErrNoRows + if err != nil { + if err == sql.ErrNoRows { + return false, nil + } + return false, err + } + return true, nil } // SessionRegenerate generate new sid for postgresql session -func (mp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { +func (mp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { c := mp.connectInit() row := c.QueryRow("select session_data from session where session_key=$1", oldsid) var sessiondata []byte @@ -212,7 +219,7 @@ func (mp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) } // SessionDestroy delete postgresql session by sid -func (mp *Provider) SessionDestroy(sid string) error { +func (mp *Provider) SessionDestroy(ctx context.Context, sid string) error { c := mp.connectInit() c.Exec("DELETE FROM session where session_key=$1", sid) c.Close() @@ -220,14 +227,14 @@ func (mp *Provider) SessionDestroy(sid string) error { } // SessionGC delete expired values in postgresql session -func (mp *Provider) SessionGC() { +func (mp *Provider) SessionGC(context.Context) { c := mp.connectInit() c.Exec("DELETE from session where EXTRACT(EPOCH FROM (current_timestamp - session_expiry)) > $1", mp.maxlifetime) c.Close() } // SessionAll count values in postgresql session -func (mp *Provider) SessionAll() int { +func (mp *Provider) SessionAll(context.Context) int { c := mp.connectInit() defer c.Close() var total int diff --git a/session/redis/sess_redis.go b/server/web/session/redis/sess_redis.go similarity index 50% rename from session/redis/sess_redis.go rename to server/web/session/redis/sess_redis.go index 5c382d61..c6e3bcbb 100644 --- a/session/redis/sess_redis.go +++ b/server/web/session/redis/sess_redis.go @@ -33,15 +33,17 @@ package redis import ( + "context" + "encoding/json" "net/http" "strconv" "strings" "sync" "time" - "github.com/astaxie/beego/session" + "github.com/go-redis/redis/v7" - "github.com/gomodule/redigo/redis" + "github.com/astaxie/beego/server/web/session" ) var redispder = &Provider{} @@ -51,7 +53,7 @@ var MaxPoolSize = 100 // SessionStore redis session store type SessionStore struct { - p *redis.Pool + p *redis.Client sid string lock sync.RWMutex values map[interface{}]interface{} @@ -59,7 +61,7 @@ type SessionStore struct { } // Set value in redis session -func (rs *SessionStore) Set(key, value interface{}) error { +func (rs *SessionStore) Set(ctx context.Context, key, value interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() rs.values[key] = value @@ -67,7 +69,7 @@ func (rs *SessionStore) Set(key, value interface{}) error { } // Get value in redis session -func (rs *SessionStore) Get(key interface{}) interface{} { +func (rs *SessionStore) Get(ctx context.Context, key interface{}) interface{} { rs.lock.RLock() defer rs.lock.RUnlock() if v, ok := rs.values[key]; ok { @@ -77,7 +79,7 @@ func (rs *SessionStore) Get(key interface{}) interface{} { } // Delete value in redis session -func (rs *SessionStore) Delete(key interface{}) error { +func (rs *SessionStore) Delete(ctx context.Context, key interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() delete(rs.values, key) @@ -85,7 +87,7 @@ func (rs *SessionStore) Delete(key interface{}) error { } // Flush clear all values in redis session -func (rs *SessionStore) Flush() error { +func (rs *SessionStore) Flush(context.Context) error { rs.lock.Lock() defer rs.lock.Unlock() rs.values = make(map[interface{}]interface{}) @@ -93,109 +95,132 @@ func (rs *SessionStore) Flush() error { } // SessionID get redis session id -func (rs *SessionStore) SessionID() string { +func (rs *SessionStore) SessionID(context.Context) string { return rs.sid } // SessionRelease save session values to redis -func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { +func (rs *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { b, err := session.EncodeGob(rs.values) if err != nil { return } - c := rs.p.Get() - defer c.Close() - c.Do("SETEX", rs.sid, rs.maxlifetime, string(b)) + c := rs.p + c.Set(rs.sid, string(b), time.Duration(rs.maxlifetime)*time.Second) } // Provider redis session provider type Provider struct { maxlifetime int64 - savePath string - poolsize int - password string - dbNum int - poollist *redis.Pool + SavePath string `json:"save_path"` + Poolsize int `json:"poolsize"` + Password string `json:"password"` + DbNum int `json:"db_num"` + + idleTimeout time.Duration + IdleTimeoutStr string `json:"idle_timeout"` + + idleCheckFrequency time.Duration + IdleCheckFrequencyStr string `json:"idle_check_frequency"` + MaxRetries int `json:"max_retries"` + poollist *redis.Client } // SessionInit init redis session // savepath like redis server addr,pool size,password,dbnum,IdleTimeout second -// e.g. 127.0.0.1:6379,100,astaxie,0,30 -func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error { +// v1.x e.g. 127.0.0.1:6379,100,astaxie,0,30 +// v2.0 you should pass json string +func (rp *Provider) SessionInit(ctx context.Context, maxlifetime int64, cfgStr string) error { rp.maxlifetime = maxlifetime + + cfgStr = strings.TrimSpace(cfgStr) + // we think cfgStr is v2.0, using json to init the session + if strings.HasPrefix(cfgStr, "{") { + err := json.Unmarshal([]byte(cfgStr), rp) + if err != nil { + return err + } + rp.idleTimeout, err = time.ParseDuration(rp.IdleTimeoutStr) + if err != nil { + return err + } + + rp.idleCheckFrequency, err = time.ParseDuration(rp.IdleCheckFrequencyStr) + if err != nil { + return err + } + + } else { + rp.initOldStyle(cfgStr) + } + + rp.poollist = redis.NewClient(&redis.Options{ + Addr: rp.SavePath, + Password: rp.Password, + PoolSize: rp.Poolsize, + DB: rp.DbNum, + IdleTimeout: rp.idleTimeout, + IdleCheckFrequency: rp.idleCheckFrequency, + MaxRetries: rp.MaxRetries, + }) + + return rp.poollist.Ping().Err() +} + +func (rp *Provider) initOldStyle(savePath string) { configs := strings.Split(savePath, ",") if len(configs) > 0 { - rp.savePath = configs[0] + rp.SavePath = configs[0] } if len(configs) > 1 { poolsize, err := strconv.Atoi(configs[1]) if err != nil || poolsize < 0 { - rp.poolsize = MaxPoolSize + rp.Poolsize = MaxPoolSize } else { - rp.poolsize = poolsize + rp.Poolsize = poolsize } } else { - rp.poolsize = MaxPoolSize + rp.Poolsize = MaxPoolSize } if len(configs) > 2 { - rp.password = configs[2] + rp.Password = configs[2] } if len(configs) > 3 { dbnum, err := strconv.Atoi(configs[3]) if err != nil || dbnum < 0 { - rp.dbNum = 0 + rp.DbNum = 0 } else { - rp.dbNum = dbnum + rp.DbNum = dbnum } } else { - rp.dbNum = 0 + rp.DbNum = 0 } - var idleTimeout time.Duration = 0 if len(configs) > 4 { timeout, err := strconv.Atoi(configs[4]) if err == nil && timeout > 0 { - idleTimeout = time.Duration(timeout) * time.Second + rp.idleTimeout = time.Duration(timeout) * time.Second } } - rp.poollist = &redis.Pool{ - Dial: func() (redis.Conn, error) { - c, err := redis.Dial("tcp", rp.savePath) - if err != nil { - return nil, err - } - if rp.password != "" { - if _, err = c.Do("AUTH", rp.password); err != nil { - c.Close() - return nil, err - } - } - // some redis proxy such as twemproxy is not support select command - if rp.dbNum > 0 { - _, err = c.Do("SELECT", rp.dbNum) - if err != nil { - c.Close() - return nil, err - } - } - return c, err - }, - MaxIdle: rp.poolsize, + if len(configs) > 5 { + checkFrequency, err := strconv.Atoi(configs[5]) + if err == nil && checkFrequency > 0 { + rp.idleCheckFrequency = time.Duration(checkFrequency) * time.Second + } + } + if len(configs) > 6 { + retries, err := strconv.Atoi(configs[6]) + if err == nil && retries > 0 { + rp.MaxRetries = retries + } } - - rp.poollist.IdleTimeout = idleTimeout - - return rp.poollist.Get().Err() } // SessionRead read redis session by sid -func (rp *Provider) SessionRead(sid string) (session.Store, error) { - c := rp.poollist.Get() - defer c.Close() - +func (rp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { var kv map[interface{}]interface{} - kvs, err := redis.String(c.Do("GET", sid)) - if err != nil && err != redis.ErrNil { + kvs, err := rp.poollist.Get(sid).Result() + if err != nil && err != redis.Nil { return nil, err } if len(kvs) == 0 { @@ -211,48 +236,44 @@ func (rp *Provider) SessionRead(sid string) (session.Store, error) { } // SessionExist check redis session exist by sid -func (rp *Provider) SessionExist(sid string) bool { - c := rp.poollist.Get() - defer c.Close() +func (rp *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { + c := rp.poollist - if existed, err := redis.Int(c.Do("EXISTS", sid)); err != nil || existed == 0 { - return false + if existed, err := c.Exists(sid).Result(); err != nil || existed == 0 { + return false, err } - return true + return true, nil } // SessionRegenerate generate new sid for redis session -func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - c := rp.poollist.Get() - defer c.Close() - - if existed, _ := redis.Int(c.Do("EXISTS", oldsid)); existed == 0 { +func (rp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { + c := rp.poollist + if existed, _ := c.Exists(oldsid).Result(); existed == 0 { // oldsid doesn't exists, set the new sid directly // ignore error here, since if it return error // the existed value will be 0 - c.Do("SET", sid, "", "EX", rp.maxlifetime) + c.Do(c.Context(), "SET", sid, "", "EX", rp.maxlifetime) } else { - c.Do("RENAME", oldsid, sid) - c.Do("EXPIRE", sid, rp.maxlifetime) + c.Rename(oldsid, sid) + c.Expire(sid, time.Duration(rp.maxlifetime)*time.Second) } - return rp.SessionRead(sid) + return rp.SessionRead(context.Background(), sid) } // SessionDestroy delete redis session by id -func (rp *Provider) SessionDestroy(sid string) error { - c := rp.poollist.Get() - defer c.Close() +func (rp *Provider) SessionDestroy(ctx context.Context, sid string) error { + c := rp.poollist - c.Do("DEL", sid) + c.Del(sid) return nil } // SessionGC Impelment method, no used. -func (rp *Provider) SessionGC() { +func (rp *Provider) SessionGC(context.Context) { } // SessionAll return all activeSession -func (rp *Provider) SessionAll() int { +func (rp *Provider) SessionAll(context.Context) int { return 0 } diff --git a/server/web/session/redis/sess_redis_test.go b/server/web/session/redis/sess_redis_test.go new file mode 100644 index 00000000..64dbc9f9 --- /dev/null +++ b/server/web/session/redis/sess_redis_test.go @@ -0,0 +1,112 @@ +package redis + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/server/web/session" +) + +func TestRedis(t *testing.T) { + sessionConfig := &session.ManagerConfig{ + CookieName: "gosessionid", + EnableSetCookie: true, + Gclifetime: 3600, + Maxlifetime: 3600, + Secure: false, + CookieLifeTime: 3600, + } + + redisAddr := os.Getenv("REDIS_ADDR") + if redisAddr == "" { + redisAddr = "127.0.0.1:6379" + } + + sessionConfig.ProviderConfig = fmt.Sprintf("%s,100,,0,30", redisAddr) + globalSession, err := session.NewManager("redis", sessionConfig) + if err != nil { + t.Fatal("could not create manager:", err) + } + + go globalSession.GC() + + r, _ := http.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + + sess, err := globalSession.SessionStart(w, r) + if err != nil { + t.Fatal("session start failed:", err) + } + defer sess.SessionRelease(nil, w) + + // SET AND GET + err = sess.Set(nil, "username", "astaxie") + if err != nil { + t.Fatal("set username failed:", err) + } + username := sess.Get(nil, "username") + if username != "astaxie" { + t.Fatal("get username failed") + } + + // DELETE + err = sess.Delete(nil, "username") + if err != nil { + t.Fatal("delete username failed:", err) + } + username = sess.Get(nil, "username") + if username != nil { + t.Fatal("delete username failed") + } + + // FLUSH + err = sess.Set(nil, "username", "astaxie") + if err != nil { + t.Fatal("set failed:", err) + } + err = sess.Set(nil, "password", "1qaz2wsx") + if err != nil { + t.Fatal("set failed:", err) + } + username = sess.Get(nil, "username") + if username != "astaxie" { + t.Fatal("get username failed") + } + password := sess.Get(nil, "password") + if password != "1qaz2wsx" { + t.Fatal("get password failed") + } + err = sess.Flush(nil) + if err != nil { + t.Fatal("flush failed:", err) + } + username = sess.Get(nil, "username") + if username != nil { + t.Fatal("flush failed") + } + password = sess.Get(nil, "password") + if password != nil { + t.Fatal("flush failed") + } + + sess.SessionRelease(nil, w) +} + +func TestProvider_SessionInit(t *testing.T) { + + savePath := ` +{ "save_path": "my save path", "idle_timeout": "3s"} +` + cp := &Provider{} + cp.SessionInit(context.Background(), 12, savePath) + assert.Equal(t, "my save path", cp.SavePath) + assert.Equal(t, 3*time.Second, cp.idleTimeout) + assert.Equal(t, int64(12), cp.maxlifetime) +} diff --git a/session/redis_cluster/redis_cluster.go b/server/web/session/redis_cluster/redis_cluster.go similarity index 55% rename from session/redis_cluster/redis_cluster.go rename to server/web/session/redis_cluster/redis_cluster.go index 2fe300df..d2971e71 100644 --- a/session/redis_cluster/redis_cluster.go +++ b/server/web/session/redis_cluster/redis_cluster.go @@ -31,14 +31,19 @@ // // more docs: http://beego.me/docs/module/session.md package redis_cluster + import ( + "context" + "encoding/json" "net/http" "strconv" "strings" "sync" - "github.com/astaxie/beego/session" - rediss "github.com/go-redis/redis" "time" + + rediss "github.com/go-redis/redis/v7" + + "github.com/astaxie/beego/server/web/session" ) var redispder = &Provider{} @@ -56,7 +61,7 @@ type SessionStore struct { } // Set value in redis_cluster session -func (rs *SessionStore) Set(key, value interface{}) error { +func (rs *SessionStore) Set(ctx context.Context, key, value interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() rs.values[key] = value @@ -64,7 +69,7 @@ func (rs *SessionStore) Set(key, value interface{}) error { } // Get value in redis_cluster session -func (rs *SessionStore) Get(key interface{}) interface{} { +func (rs *SessionStore) Get(ctx context.Context, key interface{}) interface{} { rs.lock.RLock() defer rs.lock.RUnlock() if v, ok := rs.values[key]; ok { @@ -74,7 +79,7 @@ func (rs *SessionStore) Get(key interface{}) interface{} { } // Delete value in redis_cluster session -func (rs *SessionStore) Delete(key interface{}) error { +func (rs *SessionStore) Delete(ctx context.Context, key interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() delete(rs.values, key) @@ -82,7 +87,7 @@ func (rs *SessionStore) Delete(key interface{}) error { } // Flush clear all values in redis_cluster session -func (rs *SessionStore) Flush() error { +func (rs *SessionStore) Flush(context.Context) error { rs.lock.Lock() defer rs.lock.Unlock() rs.values = make(map[interface{}]interface{}) @@ -90,73 +95,125 @@ func (rs *SessionStore) Flush() error { } // SessionID get redis_cluster session id -func (rs *SessionStore) SessionID() string { +func (rs *SessionStore) SessionID(context.Context) string { return rs.sid } // SessionRelease save session values to redis_cluster -func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { +func (rs *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { b, err := session.EncodeGob(rs.values) if err != nil { return } c := rs.p - c.Set(rs.sid, string(b), time.Duration(rs.maxlifetime) * time.Second) + c.Set(rs.sid, string(b), time.Duration(rs.maxlifetime)*time.Second) } // Provider redis_cluster session provider type Provider struct { maxlifetime int64 - savePath string - poolsize int - password string - dbNum int - poollist *rediss.ClusterClient + SavePath string `json:"save_path"` + Poolsize int `json:"poolsize"` + Password string `json:"password"` + DbNum int `json:"db_num"` + + idleTimeout time.Duration + IdleTimeoutStr string `json:"idle_timeout"` + + idleCheckFrequency time.Duration + IdleCheckFrequencyStr string `json:"idle_check_frequency"` + MaxRetries int `json:"max_retries"` + poollist *rediss.ClusterClient } // SessionInit init redis_cluster session -// savepath like redis server addr,pool size,password,dbnum +// cfgStr like redis server addr,pool size,password,dbnum // e.g. 127.0.0.1:6379;127.0.0.1:6380,100,test,0 -func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error { +func (rp *Provider) SessionInit(ctx context.Context, maxlifetime int64, cfgStr string) error { rp.maxlifetime = maxlifetime - configs := strings.Split(savePath, ",") - if len(configs) > 0 { - rp.savePath = configs[0] - } - if len(configs) > 1 { - poolsize, err := strconv.Atoi(configs[1]) - if err != nil || poolsize < 0 { - rp.poolsize = MaxPoolSize - } else { - rp.poolsize = poolsize + cfgStr = strings.TrimSpace(cfgStr) + // we think cfgStr is v2.0, using json to init the session + if strings.HasPrefix(cfgStr, "{") { + err := json.Unmarshal([]byte(cfgStr), rp) + if err != nil { + return err } - } else { - rp.poolsize = MaxPoolSize - } - if len(configs) > 2 { - rp.password = configs[2] - } - if len(configs) > 3 { - dbnum, err := strconv.Atoi(configs[3]) - if err != nil || dbnum < 0 { - rp.dbNum = 0 - } else { - rp.dbNum = dbnum + rp.idleTimeout, err = time.ParseDuration(rp.IdleTimeoutStr) + if err != nil { + return err } + + rp.idleCheckFrequency, err = time.ParseDuration(rp.IdleCheckFrequencyStr) + if err != nil { + return err + } + } else { - rp.dbNum = 0 + rp.initOldStyle(cfgStr) } - + rp.poollist = rediss.NewClusterClient(&rediss.ClusterOptions{ - Addrs: strings.Split(rp.savePath, ";"), - Password: rp.password, - PoolSize: rp.poolsize, + Addrs: strings.Split(rp.SavePath, ";"), + Password: rp.Password, + PoolSize: rp.Poolsize, + IdleTimeout: rp.idleTimeout, + IdleCheckFrequency: rp.idleCheckFrequency, + MaxRetries: rp.MaxRetries, }) return rp.poollist.Ping().Err() } +// for v1.x +func (rp *Provider) initOldStyle(savePath string) { + configs := strings.Split(savePath, ",") + if len(configs) > 0 { + rp.SavePath = configs[0] + } + if len(configs) > 1 { + poolsize, err := strconv.Atoi(configs[1]) + if err != nil || poolsize < 0 { + rp.Poolsize = MaxPoolSize + } else { + rp.Poolsize = poolsize + } + } else { + rp.Poolsize = MaxPoolSize + } + if len(configs) > 2 { + rp.Password = configs[2] + } + if len(configs) > 3 { + dbnum, err := strconv.Atoi(configs[3]) + if err != nil || dbnum < 0 { + rp.DbNum = 0 + } else { + rp.DbNum = dbnum + } + } else { + rp.DbNum = 0 + } + if len(configs) > 4 { + timeout, err := strconv.Atoi(configs[4]) + if err == nil && timeout > 0 { + rp.idleTimeout = time.Duration(timeout) * time.Second + } + } + if len(configs) > 5 { + checkFrequency, err := strconv.Atoi(configs[5]) + if err == nil && checkFrequency > 0 { + rp.idleCheckFrequency = time.Duration(checkFrequency) * time.Second + } + } + if len(configs) > 6 { + retries, err := strconv.Atoi(configs[6]) + if err == nil && retries > 0 { + rp.MaxRetries = retries + } + } +} + // SessionRead read redis_cluster session by sid -func (rp *Provider) SessionRead(sid string) (session.Store, error) { +func (rp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { var kv map[interface{}]interface{} kvs, err := rp.poollist.Get(sid).Result() if err != nil && err != rediss.Nil { @@ -175,43 +232,43 @@ func (rp *Provider) SessionRead(sid string) (session.Store, error) { } // SessionExist check redis_cluster session exist by sid -func (rp *Provider) SessionExist(sid string) bool { +func (rp *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { c := rp.poollist if existed, err := c.Exists(sid).Result(); err != nil || existed == 0 { - return false + return false, err } - return true + return true, nil } // SessionRegenerate generate new sid for redis_cluster session -func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { +func (rp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { c := rp.poollist - + if existed, err := c.Exists(oldsid).Result(); err != nil || existed == 0 { // oldsid doesn't exists, set the new sid directly // ignore error here, since if it return error // the existed value will be 0 - c.Set(sid, "", time.Duration(rp.maxlifetime) * time.Second) + c.Set(sid, "", time.Duration(rp.maxlifetime)*time.Second) } else { c.Rename(oldsid, sid) - c.Expire(sid, time.Duration(rp.maxlifetime) * time.Second) + c.Expire(sid, time.Duration(rp.maxlifetime)*time.Second) } - return rp.SessionRead(sid) + return rp.SessionRead(context.Background(), sid) } // SessionDestroy delete redis session by id -func (rp *Provider) SessionDestroy(sid string) error { +func (rp *Provider) SessionDestroy(ctx context.Context, sid string) error { c := rp.poollist c.Del(sid) return nil } // SessionGC Impelment method, no used. -func (rp *Provider) SessionGC() { +func (rp *Provider) SessionGC(context.Context) { } // SessionAll return all activeSession -func (rp *Provider) SessionAll() int { +func (rp *Provider) SessionAll(context.Context) int { return 0 } diff --git a/server/web/session/redis_cluster/redis_cluster_test.go b/server/web/session/redis_cluster/redis_cluster_test.go new file mode 100644 index 00000000..0192cd87 --- /dev/null +++ b/server/web/session/redis_cluster/redis_cluster_test.go @@ -0,0 +1,35 @@ +// Copyright 2020 +// +// Licensed 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 redis_cluster + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestProvider_SessionInit(t *testing.T) { + + savePath := ` +{ "save_path": "my save path", "idle_timeout": "3s"} +` + cp := &Provider{} + cp.SessionInit(context.Background(), 12, savePath) + assert.Equal(t, "my save path", cp.SavePath) + assert.Equal(t, 3*time.Second, cp.idleTimeout) + assert.Equal(t, int64(12), cp.maxlifetime) +} diff --git a/session/redis_sentinel/sess_redis_sentinel.go b/server/web/session/redis_sentinel/sess_redis_sentinel.go similarity index 57% rename from session/redis_sentinel/sess_redis_sentinel.go rename to server/web/session/redis_sentinel/sess_redis_sentinel.go index 6ecb2977..89d73b86 100644 --- a/session/redis_sentinel/sess_redis_sentinel.go +++ b/server/web/session/redis_sentinel/sess_redis_sentinel.go @@ -33,13 +33,17 @@ package redis_sentinel import ( - "github.com/astaxie/beego/session" - "github.com/go-redis/redis" + "context" + "encoding/json" "net/http" "strconv" "strings" "sync" "time" + + "github.com/go-redis/redis/v7" + + "github.com/astaxie/beego/server/web/session" ) var redispder = &Provider{} @@ -57,7 +61,7 @@ type SessionStore struct { } // Set value in redis_sentinel session -func (rs *SessionStore) Set(key, value interface{}) error { +func (rs *SessionStore) Set(ctx context.Context, key, value interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() rs.values[key] = value @@ -65,7 +69,7 @@ func (rs *SessionStore) Set(key, value interface{}) error { } // Get value in redis_sentinel session -func (rs *SessionStore) Get(key interface{}) interface{} { +func (rs *SessionStore) Get(ctx context.Context, key interface{}) interface{} { rs.lock.RLock() defer rs.lock.RUnlock() if v, ok := rs.values[key]; ok { @@ -75,7 +79,7 @@ func (rs *SessionStore) Get(key interface{}) interface{} { } // Delete value in redis_sentinel session -func (rs *SessionStore) Delete(key interface{}) error { +func (rs *SessionStore) Delete(ctx context.Context, key interface{}) error { rs.lock.Lock() defer rs.lock.Unlock() delete(rs.values, key) @@ -83,7 +87,7 @@ func (rs *SessionStore) Delete(key interface{}) error { } // Flush clear all values in redis_sentinel session -func (rs *SessionStore) Flush() error { +func (rs *SessionStore) Flush(context.Context) error { rs.lock.Lock() defer rs.lock.Unlock() rs.values = make(map[interface{}]interface{}) @@ -91,12 +95,12 @@ func (rs *SessionStore) Flush() error { } // SessionID get redis_sentinel session id -func (rs *SessionStore) SessionID() string { +func (rs *SessionStore) SessionID(context.Context) string { return rs.sid } // SessionRelease save session values to redis_sentinel -func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { +func (rs *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { b, err := session.EncodeGob(rs.values) if err != nil { return @@ -108,69 +112,121 @@ func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { // Provider redis_sentinel session provider type Provider struct { maxlifetime int64 - savePath string - poolsize int - password string - dbNum int - poollist *redis.Client - masterName string + SavePath string `json:"save_path"` + Poolsize int `json:"poolsize"` + Password string `json:"password"` + DbNum int `json:"db_num"` + + idleTimeout time.Duration + IdleTimeoutStr string `json:"idle_timeout"` + + idleCheckFrequency time.Duration + IdleCheckFrequencyStr string `json:"idle_check_frequency"` + MaxRetries int `json:"max_retries"` + poollist *redis.Client + MasterName string `json:"master_name"` } // SessionInit init redis_sentinel session -// savepath like redis sentinel addr,pool size,password,dbnum,masterName +// cfgStr like redis sentinel addr,pool size,password,dbnum,masterName // e.g. 127.0.0.1:26379;127.0.0.2:26379,100,1qaz2wsx,0,mymaster -func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error { +func (rp *Provider) SessionInit(ctx context.Context, maxlifetime int64, cfgStr string) error { rp.maxlifetime = maxlifetime - configs := strings.Split(savePath, ",") - if len(configs) > 0 { - rp.savePath = configs[0] - } - if len(configs) > 1 { - poolsize, err := strconv.Atoi(configs[1]) - if err != nil || poolsize < 0 { - rp.poolsize = DefaultPoolSize - } else { - rp.poolsize = poolsize + cfgStr = strings.TrimSpace(cfgStr) + // we think cfgStr is v2.0, using json to init the session + if strings.HasPrefix(cfgStr, "{") { + err := json.Unmarshal([]byte(cfgStr), rp) + if err != nil { + return err } - } else { - rp.poolsize = DefaultPoolSize - } - if len(configs) > 2 { - rp.password = configs[2] - } - if len(configs) > 3 { - dbnum, err := strconv.Atoi(configs[3]) - if err != nil || dbnum < 0 { - rp.dbNum = 0 - } else { - rp.dbNum = dbnum + rp.idleTimeout, err = time.ParseDuration(rp.IdleTimeoutStr) + if err != nil { + return err } - } else { - rp.dbNum = 0 - } - if len(configs) > 4 { - if configs[4] != "" { - rp.masterName = configs[4] - } else { - rp.masterName = "mymaster" + + rp.idleCheckFrequency, err = time.ParseDuration(rp.IdleCheckFrequencyStr) + if err != nil { + return err } + } else { - rp.masterName = "mymaster" + rp.initOldStyle(cfgStr) } rp.poollist = redis.NewFailoverClient(&redis.FailoverOptions{ - SentinelAddrs: strings.Split(rp.savePath, ";"), - Password: rp.password, - PoolSize: rp.poolsize, - DB: rp.dbNum, - MasterName: rp.masterName, + SentinelAddrs: strings.Split(rp.SavePath, ";"), + Password: rp.Password, + PoolSize: rp.Poolsize, + DB: rp.DbNum, + MasterName: rp.MasterName, + IdleTimeout: rp.idleTimeout, + IdleCheckFrequency: rp.idleCheckFrequency, + MaxRetries: rp.MaxRetries, }) return rp.poollist.Ping().Err() } +// for v1.x +func (rp *Provider) initOldStyle(savePath string) { + configs := strings.Split(savePath, ",") + if len(configs) > 0 { + rp.SavePath = configs[0] + } + if len(configs) > 1 { + poolsize, err := strconv.Atoi(configs[1]) + if err != nil || poolsize < 0 { + rp.Poolsize = DefaultPoolSize + } else { + rp.Poolsize = poolsize + } + } else { + rp.Poolsize = DefaultPoolSize + } + if len(configs) > 2 { + rp.Password = configs[2] + } + if len(configs) > 3 { + dbnum, err := strconv.Atoi(configs[3]) + if err != nil || dbnum < 0 { + rp.DbNum = 0 + } else { + rp.DbNum = dbnum + } + } else { + rp.DbNum = 0 + } + if len(configs) > 4 { + if configs[4] != "" { + rp.MasterName = configs[4] + } else { + rp.MasterName = "mymaster" + } + } else { + rp.MasterName = "mymaster" + } + if len(configs) > 5 { + timeout, err := strconv.Atoi(configs[4]) + if err == nil && timeout > 0 { + rp.idleTimeout = time.Duration(timeout) * time.Second + } + } + if len(configs) > 6 { + checkFrequency, err := strconv.Atoi(configs[5]) + if err == nil && checkFrequency > 0 { + rp.idleCheckFrequency = time.Duration(checkFrequency) * time.Second + } + } + if len(configs) > 7 { + retries, err := strconv.Atoi(configs[6]) + if err == nil && retries > 0 { + rp.MaxRetries = retries + } + } +} + // SessionRead read redis_sentinel session by sid -func (rp *Provider) SessionRead(sid string) (session.Store, error) { +func (rp *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { var kv map[interface{}]interface{} kvs, err := rp.poollist.Get(sid).Result() if err != nil && err != redis.Nil { @@ -189,16 +245,16 @@ func (rp *Provider) SessionRead(sid string) (session.Store, error) { } // SessionExist check redis_sentinel session exist by sid -func (rp *Provider) SessionExist(sid string) bool { +func (rp *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { c := rp.poollist if existed, err := c.Exists(sid).Result(); err != nil || existed == 0 { - return false + return false, err } - return true + return true, nil } // SessionRegenerate generate new sid for redis_sentinel session -func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { +func (rp *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { c := rp.poollist if existed, err := c.Exists(oldsid).Result(); err != nil || existed == 0 { @@ -210,22 +266,22 @@ func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) c.Rename(oldsid, sid) c.Expire(sid, time.Duration(rp.maxlifetime)*time.Second) } - return rp.SessionRead(sid) + return rp.SessionRead(context.Background(), sid) } // SessionDestroy delete redis session by id -func (rp *Provider) SessionDestroy(sid string) error { +func (rp *Provider) SessionDestroy(ctx context.Context, sid string) error { c := rp.poollist c.Del(sid) return nil } // SessionGC Impelment method, no used. -func (rp *Provider) SessionGC() { +func (rp *Provider) SessionGC(context.Context) { } // SessionAll return all activeSession -func (rp *Provider) SessionAll() int { +func (rp *Provider) SessionAll(context.Context) int { return 0 } diff --git a/server/web/session/redis_sentinel/sess_redis_sentinel_test.go b/server/web/session/redis_sentinel/sess_redis_sentinel_test.go new file mode 100644 index 00000000..f052a14a --- /dev/null +++ b/server/web/session/redis_sentinel/sess_redis_sentinel_test.go @@ -0,0 +1,106 @@ +package redis_sentinel + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/server/web/session" +) + +func TestRedisSentinel(t *testing.T) { + sessionConfig := &session.ManagerConfig{ + CookieName: "gosessionid", + EnableSetCookie: true, + Gclifetime: 3600, + Maxlifetime: 3600, + Secure: false, + CookieLifeTime: 3600, + ProviderConfig: "127.0.0.1:6379,100,,0,master", + } + globalSessions, e := session.NewManager("redis_sentinel", sessionConfig) + if e != nil { + t.Log(e) + return + } + // todo test if e==nil + go globalSessions.GC() + + r, _ := http.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + + sess, err := globalSessions.SessionStart(w, r) + if err != nil { + t.Fatal("session start failed:", err) + } + defer sess.SessionRelease(nil, w) + + // SET AND GET + err = sess.Set(nil, "username", "astaxie") + if err != nil { + t.Fatal("set username failed:", err) + } + username := sess.Get(nil, "username") + if username != "astaxie" { + t.Fatal("get username failed") + } + + // DELETE + err = sess.Delete(nil, "username") + if err != nil { + t.Fatal("delete username failed:", err) + } + username = sess.Get(nil, "username") + if username != nil { + t.Fatal("delete username failed") + } + + // FLUSH + err = sess.Set(nil, "username", "astaxie") + if err != nil { + t.Fatal("set failed:", err) + } + err = sess.Set(nil, "password", "1qaz2wsx") + if err != nil { + t.Fatal("set failed:", err) + } + username = sess.Get(nil, "username") + if username != "astaxie" { + t.Fatal("get username failed") + } + password := sess.Get(nil, "password") + if password != "1qaz2wsx" { + t.Fatal("get password failed") + } + err = sess.Flush(nil) + if err != nil { + t.Fatal("flush failed:", err) + } + username = sess.Get(nil, "username") + if username != nil { + t.Fatal("flush failed") + } + password = sess.Get(nil, "password") + if password != nil { + t.Fatal("flush failed") + } + + sess.SessionRelease(nil, w) + +} + +func TestProvider_SessionInit(t *testing.T) { + + savePath := ` +{ "save_path": "my save path", "idle_timeout": "3s"} +` + cp := &Provider{} + cp.SessionInit(context.Background(), 12, savePath) + assert.Equal(t, "my save path", cp.SavePath) + assert.Equal(t, 3*time.Second, cp.idleTimeout) + assert.Equal(t, int64(12), cp.maxlifetime) +} diff --git a/session/sess_cookie.go b/server/web/session/sess_cookie.go similarity index 77% rename from session/sess_cookie.go rename to server/web/session/sess_cookie.go index 6ad5debc..649f6510 100644 --- a/session/sess_cookie.go +++ b/server/web/session/sess_cookie.go @@ -15,6 +15,7 @@ package session import ( + "context" "crypto/aes" "crypto/cipher" "encoding/json" @@ -34,7 +35,7 @@ type CookieSessionStore struct { // Set value to cookie session. // the value are encoded as gob with hash block string. -func (st *CookieSessionStore) Set(key, value interface{}) error { +func (st *CookieSessionStore) Set(ctx context.Context, key, value interface{}) error { st.lock.Lock() defer st.lock.Unlock() st.values[key] = value @@ -42,7 +43,7 @@ func (st *CookieSessionStore) Set(key, value interface{}) error { } // Get value from cookie session -func (st *CookieSessionStore) Get(key interface{}) interface{} { +func (st *CookieSessionStore) Get(ctx context.Context, key interface{}) interface{} { st.lock.RLock() defer st.lock.RUnlock() if v, ok := st.values[key]; ok { @@ -52,7 +53,7 @@ func (st *CookieSessionStore) Get(key interface{}) interface{} { } // Delete value in cookie session -func (st *CookieSessionStore) Delete(key interface{}) error { +func (st *CookieSessionStore) Delete(ctx context.Context, key interface{}) error { st.lock.Lock() defer st.lock.Unlock() delete(st.values, key) @@ -60,7 +61,7 @@ func (st *CookieSessionStore) Delete(key interface{}) error { } // Flush Clean all values in cookie session -func (st *CookieSessionStore) Flush() error { +func (st *CookieSessionStore) Flush(context.Context) error { st.lock.Lock() defer st.lock.Unlock() st.values = make(map[interface{}]interface{}) @@ -68,12 +69,12 @@ func (st *CookieSessionStore) Flush() error { } // SessionID Return id of this cookie session -func (st *CookieSessionStore) SessionID() string { +func (st *CookieSessionStore) SessionID(context.Context) string { return st.sid } // SessionRelease Write cookie session to http response cookie -func (st *CookieSessionStore) SessionRelease(w http.ResponseWriter) { +func (st *CookieSessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { st.lock.Lock() encodedCookie, err := encodeCookie(cookiepder.block, cookiepder.config.SecurityKey, cookiepder.config.SecurityName, st.values) st.lock.Unlock() @@ -112,7 +113,7 @@ type CookieProvider struct { // securityName - recognized name in encoded cookie string // cookieName - cookie name // maxage - cookie max life time. -func (pder *CookieProvider) SessionInit(maxlifetime int64, config string) error { +func (pder *CookieProvider) SessionInit(ctx context.Context, maxlifetime int64, config string) error { pder.config = &cookieConfig{} err := json.Unmarshal([]byte(config), pder.config) if err != nil { @@ -134,7 +135,7 @@ func (pder *CookieProvider) SessionInit(maxlifetime int64, config string) error // SessionRead Get SessionStore in cooke. // decode cooke string to map and put into SessionStore with sid. -func (pder *CookieProvider) SessionRead(sid string) (Store, error) { +func (pder *CookieProvider) SessionRead(ctx context.Context, sid string) (Store, error) { maps, _ := decodeCookie(pder.block, pder.config.SecurityKey, pder.config.SecurityName, @@ -147,31 +148,31 @@ func (pder *CookieProvider) SessionRead(sid string) (Store, error) { } // SessionExist Cookie session is always existed -func (pder *CookieProvider) SessionExist(sid string) bool { - return true +func (pder *CookieProvider) SessionExist(ctx context.Context, sid string) (bool, error) { + return true, nil } // SessionRegenerate Implement method, no used. -func (pder *CookieProvider) SessionRegenerate(oldsid, sid string) (Store, error) { +func (pder *CookieProvider) SessionRegenerate(ctx context.Context, oldsid, sid string) (Store, error) { return nil, nil } // SessionDestroy Implement method, no used. -func (pder *CookieProvider) SessionDestroy(sid string) error { +func (pder *CookieProvider) SessionDestroy(ctx context.Context, sid string) error { return nil } // SessionGC Implement method, no used. -func (pder *CookieProvider) SessionGC() { +func (pder *CookieProvider) SessionGC(context.Context) { } // SessionAll Implement method, return 0. -func (pder *CookieProvider) SessionAll() int { +func (pder *CookieProvider) SessionAll(context.Context) int { return 0 } // SessionUpdate Implement method, no used. -func (pder *CookieProvider) SessionUpdate(sid string) error { +func (pder *CookieProvider) SessionUpdate(ctx context.Context, sid string) error { return nil } diff --git a/server/web/session/sess_cookie_test.go b/server/web/session/sess_cookie_test.go new file mode 100644 index 00000000..a9fc876d --- /dev/null +++ b/server/web/session/sess_cookie_test.go @@ -0,0 +1,105 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 session + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestCookie(t *testing.T) { + config := `{"cookieName":"gosessionid","enableSetCookie":false,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}` + conf := new(ManagerConfig) + if err := json.Unmarshal([]byte(config), conf); err != nil { + t.Fatal("json decode error", err) + } + globalSessions, err := NewManager("cookie", conf) + if err != nil { + t.Fatal("init cookie session err", err) + } + r, _ := http.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + sess, err := globalSessions.SessionStart(w, r) + if err != nil { + t.Fatal("set error,", err) + } + err = sess.Set(nil, "username", "astaxie") + if err != nil { + t.Fatal("set error,", err) + } + if username := sess.Get(nil, "username"); username != "astaxie" { + t.Fatal("get username error") + } + sess.SessionRelease(nil, w) + if cookiestr := w.Header().Get("Set-Cookie"); cookiestr == "" { + t.Fatal("setcookie error") + } else { + parts := strings.Split(strings.TrimSpace(cookiestr), ";") + for k, v := range parts { + nameval := strings.Split(v, "=") + if k == 0 && nameval[0] != "gosessionid" { + t.Fatal("error") + } + } + } +} + +func TestDestorySessionCookie(t *testing.T) { + config := `{"cookieName":"gosessionid","enableSetCookie":true,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}` + conf := new(ManagerConfig) + if err := json.Unmarshal([]byte(config), conf); err != nil { + t.Fatal("json decode error", err) + } + globalSessions, err := NewManager("cookie", conf) + if err != nil { + t.Fatal("init cookie session err", err) + } + + r, _ := http.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + session, err := globalSessions.SessionStart(w, r) + if err != nil { + t.Fatal("session start err,", err) + } + + // request again ,will get same sesssion id . + r1, _ := http.NewRequest("GET", "/", nil) + r1.Header.Set("Cookie", w.Header().Get("Set-Cookie")) + w = httptest.NewRecorder() + newSession, err := globalSessions.SessionStart(w, r1) + if err != nil { + t.Fatal("session start err,", err) + } + if newSession.SessionID(nil) != session.SessionID(nil) { + t.Fatal("get cookie session id is not the same again.") + } + + // After destroy session , will get a new session id . + globalSessions.SessionDestroy(w, r1) + r2, _ := http.NewRequest("GET", "/", nil) + r2.Header.Set("Cookie", w.Header().Get("Set-Cookie")) + + w = httptest.NewRecorder() + newSession, err = globalSessions.SessionStart(w, r2) + if err != nil { + t.Fatal("session start error") + } + if newSession.SessionID(nil) == session.SessionID(nil) { + t.Fatal("after destroy session and reqeust again ,get cookie session id is same.") + } +} diff --git a/session/sess_file.go b/server/web/session/sess_file.go similarity index 85% rename from session/sess_file.go rename to server/web/session/sess_file.go index c6dbf209..90de9a79 100644 --- a/session/sess_file.go +++ b/server/web/session/sess_file.go @@ -15,11 +15,12 @@ package session import ( + "context" + "errors" "fmt" "io/ioutil" "net/http" "os" - "errors" "path" "path/filepath" "strings" @@ -40,7 +41,7 @@ type FileSessionStore struct { } // Set value to file session -func (fs *FileSessionStore) Set(key, value interface{}) error { +func (fs *FileSessionStore) Set(ctx context.Context, key, value interface{}) error { fs.lock.Lock() defer fs.lock.Unlock() fs.values[key] = value @@ -48,7 +49,7 @@ func (fs *FileSessionStore) Set(key, value interface{}) error { } // Get value from file session -func (fs *FileSessionStore) Get(key interface{}) interface{} { +func (fs *FileSessionStore) Get(ctx context.Context, key interface{}) interface{} { fs.lock.RLock() defer fs.lock.RUnlock() if v, ok := fs.values[key]; ok { @@ -58,7 +59,7 @@ func (fs *FileSessionStore) Get(key interface{}) interface{} { } // Delete value in file session by given key -func (fs *FileSessionStore) Delete(key interface{}) error { +func (fs *FileSessionStore) Delete(ctx context.Context, key interface{}) error { fs.lock.Lock() defer fs.lock.Unlock() delete(fs.values, key) @@ -66,7 +67,7 @@ func (fs *FileSessionStore) Delete(key interface{}) error { } // Flush Clean all values in file session -func (fs *FileSessionStore) Flush() error { +func (fs *FileSessionStore) Flush(context.Context) error { fs.lock.Lock() defer fs.lock.Unlock() fs.values = make(map[interface{}]interface{}) @@ -74,12 +75,12 @@ func (fs *FileSessionStore) Flush() error { } // SessionID Get file session store id -func (fs *FileSessionStore) SessionID() string { +func (fs *FileSessionStore) SessionID(context.Context) string { return fs.sid } // SessionRelease Write file session to local file with Gob string -func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) { +func (fs *FileSessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { filepder.lock.Lock() defer filepder.lock.Unlock() b, err := EncodeGob(fs.values) @@ -119,7 +120,7 @@ type FileProvider struct { // SessionInit Init file session provider. // savePath sets the session files path. -func (fp *FileProvider) SessionInit(maxlifetime int64, savePath string) error { +func (fp *FileProvider) SessionInit(ctx context.Context, maxlifetime int64, savePath string) error { fp.maxlifetime = maxlifetime fp.savePath = savePath return nil @@ -128,7 +129,7 @@ func (fp *FileProvider) SessionInit(maxlifetime int64, savePath string) error { // SessionRead Read file session by sid. // if file is not exist, create it. // the file path is generated from sid string. -func (fp *FileProvider) SessionRead(sid string) (Store, error) { +func (fp *FileProvider) SessionRead(ctx context.Context, sid string) (Store, error) { invalidChars := "./" if strings.ContainsAny(sid, invalidChars) { return nil, errors.New("the sid shouldn't have following characters: " + invalidChars) @@ -176,16 +177,21 @@ func (fp *FileProvider) SessionRead(sid string) (Store, error) { // SessionExist Check file session exist. // it checks the file named from sid exist or not. -func (fp *FileProvider) SessionExist(sid string) bool { +func (fp *FileProvider) SessionExist(ctx context.Context, sid string) (bool, error) { filepder.lock.Lock() defer filepder.lock.Unlock() + if len(sid) < 2 { + SLogger.Println("min length of session id is 2 but got length: ", sid) + return false, errors.New("min length of session id is 2") + } + _, err := os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid)) - return err == nil + return err == nil, nil } // SessionDestroy Remove all files in this save path -func (fp *FileProvider) SessionDestroy(sid string) error { +func (fp *FileProvider) SessionDestroy(ctx context.Context, sid string) error { filepder.lock.Lock() defer filepder.lock.Unlock() os.Remove(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid)) @@ -193,7 +199,7 @@ func (fp *FileProvider) SessionDestroy(sid string) error { } // SessionGC Recycle files in save path -func (fp *FileProvider) SessionGC() { +func (fp *FileProvider) SessionGC(context.Context) { filepder.lock.Lock() defer filepder.lock.Unlock() @@ -203,7 +209,7 @@ func (fp *FileProvider) SessionGC() { // SessionAll Get active file session number. // it walks save path to count files. -func (fp *FileProvider) SessionAll() int { +func (fp *FileProvider) SessionAll(context.Context) int { a := &activeSession{} err := filepath.Walk(fp.savePath, func(path string, f os.FileInfo, err error) error { return a.visit(path, f, err) @@ -217,7 +223,7 @@ func (fp *FileProvider) SessionAll() int { // SessionRegenerate Generate new sid for file session. // it delete old file and create new file named from new sid. -func (fp *FileProvider) SessionRegenerate(oldsid, sid string) (Store, error) { +func (fp *FileProvider) SessionRegenerate(ctx context.Context, oldsid, sid string) (Store, error) { filepder.lock.Lock() defer filepder.lock.Unlock() diff --git a/server/web/session/sess_file_test.go b/server/web/session/sess_file_test.go new file mode 100644 index 00000000..f40de69f --- /dev/null +++ b/server/web/session/sess_file_test.go @@ -0,0 +1,427 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 session + +import ( + "context" + "fmt" + "os" + "sync" + "testing" + "time" +) + +const sid = "Session_id" +const sidNew = "Session_id_new" +const sessionPath = "./_session_runtime" + +var ( + mutex sync.Mutex +) + +func TestFileProvider_SessionInit(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(context.Background(), 180, sessionPath) + if fp.maxlifetime != 180 { + t.Error() + } + + if fp.savePath != sessionPath { + t.Error() + } +} + +func TestFileProvider_SessionExist(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(context.Background(), 180, sessionPath) + + exists, err := fp.SessionExist(context.Background(), sid) + if err != nil { + t.Error(err) + } + if exists { + t.Error() + } + + _, err = fp.SessionRead(context.Background(), sid) + if err != nil { + t.Error(err) + } + + exists, err = fp.SessionExist(context.Background(), sid) + if err != nil { + t.Error(err) + } + if !exists { + t.Error() + } +} + +func TestFileProvider_SessionExist2(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(context.Background(), 180, sessionPath) + + exists, err := fp.SessionExist(context.Background(), sid) + if err != nil { + t.Error(err) + } + if exists { + t.Error() + } + + exists, err = fp.SessionExist(context.Background(), "") + if err == nil { + t.Error() + } + if exists { + t.Error() + } + + exists, err = fp.SessionExist(context.Background(), "1") + if err == nil { + t.Error() + } + if exists { + t.Error() + } +} + +func TestFileProvider_SessionRead(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(context.Background(), 180, sessionPath) + + s, err := fp.SessionRead(context.Background(), sid) + if err != nil { + t.Error(err) + } + + _ = s.Set(nil, "sessionValue", 18975) + v := s.Get(nil, "sessionValue") + + if v.(int) != 18975 { + t.Error() + } +} + +func TestFileProvider_SessionRead1(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(context.Background(), 180, sessionPath) + + _, err := fp.SessionRead(context.Background(), "") + if err == nil { + t.Error(err) + } + + _, err = fp.SessionRead(context.Background(), "1") + if err == nil { + t.Error(err) + } +} + +func TestFileProvider_SessionAll(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(context.Background(), 180, sessionPath) + + sessionCount := 546 + + for i := 1; i <= sessionCount; i++ { + _, err := fp.SessionRead(context.Background(), fmt.Sprintf("%s_%d", sid, i)) + if err != nil { + t.Error(err) + } + } + + if fp.SessionAll(nil) != sessionCount { + t.Error() + } +} + +func TestFileProvider_SessionRegenerate(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(context.Background(), 180, sessionPath) + + _, err := fp.SessionRead(context.Background(), sid) + if err != nil { + t.Error(err) + } + + exists, err := fp.SessionExist(context.Background(), sid) + if err != nil { + t.Error(err) + } + if !exists { + t.Error() + } + + _, err = fp.SessionRegenerate(context.Background(), sid, sidNew) + if err != nil { + t.Error(err) + } + + exists, err = fp.SessionExist(context.Background(), sid) + if err != nil { + t.Error(err) + } + if exists { + t.Error() + } + + exists, err = fp.SessionExist(context.Background(), sidNew) + if err != nil { + t.Error(err) + } + if !exists { + t.Error() + } +} + +func TestFileProvider_SessionDestroy(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(context.Background(), 180, sessionPath) + + _, err := fp.SessionRead(context.Background(), sid) + if err != nil { + t.Error(err) + } + + exists, err := fp.SessionExist(context.Background(), sid) + if err != nil { + t.Error(err) + } + if !exists { + t.Error() + } + + err = fp.SessionDestroy(context.Background(), sid) + if err != nil { + t.Error(err) + } + + exists, err = fp.SessionExist(context.Background(), sid) + if err != nil { + t.Error(err) + } + if exists { + t.Error() + } +} + +func TestFileProvider_SessionGC(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(context.Background(), 1, sessionPath) + + sessionCount := 412 + + for i := 1; i <= sessionCount; i++ { + _, err := fp.SessionRead(context.Background(), fmt.Sprintf("%s_%d", sid, i)) + if err != nil { + t.Error(err) + } + } + + time.Sleep(2 * time.Second) + + fp.SessionGC(nil) + if fp.SessionAll(nil) != 0 { + t.Error() + } +} + +func TestFileSessionStore_Set(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(context.Background(), 180, sessionPath) + + sessionCount := 100 + s, _ := fp.SessionRead(context.Background(), sid) + for i := 1; i <= sessionCount; i++ { + err := s.Set(nil, i, i) + if err != nil { + t.Error(err) + } + } +} + +func TestFileSessionStore_Get(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(context.Background(), 180, sessionPath) + + sessionCount := 100 + s, _ := fp.SessionRead(context.Background(), sid) + for i := 1; i <= sessionCount; i++ { + _ = s.Set(nil, i, i) + + v := s.Get(nil, i) + if v.(int) != i { + t.Error() + } + } +} + +func TestFileSessionStore_Delete(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(context.Background(), 180, sessionPath) + + s, _ := fp.SessionRead(context.Background(), sid) + s.Set(nil, "1", 1) + + if s.Get(nil, "1") == nil { + t.Error() + } + + s.Delete(nil, "1") + + if s.Get(nil, "1") != nil { + t.Error() + } +} + +func TestFileSessionStore_Flush(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(context.Background(), 180, sessionPath) + + sessionCount := 100 + s, _ := fp.SessionRead(context.Background(), sid) + for i := 1; i <= sessionCount; i++ { + _ = s.Set(nil, i, i) + } + + _ = s.Flush(nil) + + for i := 1; i <= sessionCount; i++ { + if s.Get(nil, i) != nil { + t.Error() + } + } +} + +func TestFileSessionStore_SessionID(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(context.Background(), 180, sessionPath) + + sessionCount := 85 + + for i := 1; i <= sessionCount; i++ { + s, err := fp.SessionRead(context.Background(), fmt.Sprintf("%s_%d", sid, i)) + if err != nil { + t.Error(err) + } + if s.SessionID(nil) != fmt.Sprintf("%s_%d", sid, i) { + t.Error(err) + } + } +} + +func TestFileSessionStore_SessionRelease(t *testing.T) { + mutex.Lock() + defer mutex.Unlock() + os.RemoveAll(sessionPath) + defer os.RemoveAll(sessionPath) + fp := &FileProvider{} + + _ = fp.SessionInit(context.Background(), 180, sessionPath) + filepder.savePath = sessionPath + sessionCount := 85 + + for i := 1; i <= sessionCount; i++ { + s, err := fp.SessionRead(context.Background(), fmt.Sprintf("%s_%d", sid, i)) + if err != nil { + t.Error(err) + } + + s.Set(nil, i, i) + s.SessionRelease(nil, nil) + } + + for i := 1; i <= sessionCount; i++ { + s, err := fp.SessionRead(context.Background(), fmt.Sprintf("%s_%d", sid, i)) + if err != nil { + t.Error(err) + } + + if s.Get(nil, i).(int) != i { + t.Error() + } + } +} diff --git a/session/sess_mem.go b/server/web/session/sess_mem.go similarity index 78% rename from session/sess_mem.go rename to server/web/session/sess_mem.go index 64d8b056..27e24c73 100644 --- a/session/sess_mem.go +++ b/server/web/session/sess_mem.go @@ -16,6 +16,7 @@ package session import ( "container/list" + "context" "net/http" "sync" "time" @@ -33,7 +34,7 @@ type MemSessionStore struct { } // Set value to memory session -func (st *MemSessionStore) Set(key, value interface{}) error { +func (st *MemSessionStore) Set(ctx context.Context, key, value interface{}) error { st.lock.Lock() defer st.lock.Unlock() st.value[key] = value @@ -41,7 +42,7 @@ func (st *MemSessionStore) Set(key, value interface{}) error { } // Get value from memory session by key -func (st *MemSessionStore) Get(key interface{}) interface{} { +func (st *MemSessionStore) Get(ctx context.Context, key interface{}) interface{} { st.lock.RLock() defer st.lock.RUnlock() if v, ok := st.value[key]; ok { @@ -51,7 +52,7 @@ func (st *MemSessionStore) Get(key interface{}) interface{} { } // Delete in memory session by key -func (st *MemSessionStore) Delete(key interface{}) error { +func (st *MemSessionStore) Delete(ctx context.Context, key interface{}) error { st.lock.Lock() defer st.lock.Unlock() delete(st.value, key) @@ -59,7 +60,7 @@ func (st *MemSessionStore) Delete(key interface{}) error { } // Flush clear all values in memory session -func (st *MemSessionStore) Flush() error { +func (st *MemSessionStore) Flush(context.Context) error { st.lock.Lock() defer st.lock.Unlock() st.value = make(map[interface{}]interface{}) @@ -67,12 +68,12 @@ func (st *MemSessionStore) Flush() error { } // SessionID get this id of memory session store -func (st *MemSessionStore) SessionID() string { +func (st *MemSessionStore) SessionID(context.Context) string { return st.sid } // SessionRelease Implement method, no used. -func (st *MemSessionStore) SessionRelease(w http.ResponseWriter) { +func (st *MemSessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { } // MemProvider Implement the provider interface @@ -85,17 +86,17 @@ type MemProvider struct { } // SessionInit init memory session -func (pder *MemProvider) SessionInit(maxlifetime int64, savePath string) error { +func (pder *MemProvider) SessionInit(ctx context.Context, maxlifetime int64, savePath string) error { pder.maxlifetime = maxlifetime pder.savePath = savePath return nil } // SessionRead get memory session store by sid -func (pder *MemProvider) SessionRead(sid string) (Store, error) { +func (pder *MemProvider) SessionRead(ctx context.Context, sid string) (Store, error) { pder.lock.RLock() if element, ok := pder.sessions[sid]; ok { - go pder.SessionUpdate(sid) + go pder.SessionUpdate(nil, sid) pder.lock.RUnlock() return element.Value.(*MemSessionStore), nil } @@ -109,20 +110,20 @@ func (pder *MemProvider) SessionRead(sid string) (Store, error) { } // SessionExist check session store exist in memory session by sid -func (pder *MemProvider) SessionExist(sid string) bool { +func (pder *MemProvider) SessionExist(ctx context.Context, sid string) (bool, error) { pder.lock.RLock() defer pder.lock.RUnlock() if _, ok := pder.sessions[sid]; ok { - return true + return true, nil } - return false + return false, nil } // SessionRegenerate generate new sid for session store in memory session -func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (Store, error) { +func (pder *MemProvider) SessionRegenerate(ctx context.Context, oldsid, sid string) (Store, error) { pder.lock.RLock() if element, ok := pder.sessions[oldsid]; ok { - go pder.SessionUpdate(oldsid) + go pder.SessionUpdate(nil, oldsid) pder.lock.RUnlock() pder.lock.Lock() element.Value.(*MemSessionStore).sid = sid @@ -141,7 +142,7 @@ func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (Store, error) { } // SessionDestroy delete session store in memory session by id -func (pder *MemProvider) SessionDestroy(sid string) error { +func (pder *MemProvider) SessionDestroy(ctx context.Context, sid string) error { pder.lock.Lock() defer pder.lock.Unlock() if element, ok := pder.sessions[sid]; ok { @@ -153,7 +154,7 @@ func (pder *MemProvider) SessionDestroy(sid string) error { } // SessionGC clean expired session stores in memory session -func (pder *MemProvider) SessionGC() { +func (pder *MemProvider) SessionGC(context.Context) { pder.lock.RLock() for { element := pder.list.Back() @@ -175,12 +176,12 @@ func (pder *MemProvider) SessionGC() { } // SessionAll get count number of memory session -func (pder *MemProvider) SessionAll() int { +func (pder *MemProvider) SessionAll(context.Context) int { return pder.list.Len() } // SessionUpdate expand time of session store by id in memory session -func (pder *MemProvider) SessionUpdate(sid string) error { +func (pder *MemProvider) SessionUpdate(ctx context.Context, sid string) error { pder.lock.Lock() defer pder.lock.Unlock() if element, ok := pder.sessions[sid]; ok { diff --git a/server/web/session/sess_mem_test.go b/server/web/session/sess_mem_test.go new file mode 100644 index 00000000..e6d35476 --- /dev/null +++ b/server/web/session/sess_mem_test.go @@ -0,0 +1,58 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 session + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestMem(t *testing.T) { + config := `{"cookieName":"gosessionid","gclifetime":10, "enableSetCookie":true}` + conf := new(ManagerConfig) + if err := json.Unmarshal([]byte(config), conf); err != nil { + t.Fatal("json decode error", err) + } + globalSessions, _ := NewManager("memory", conf) + go globalSessions.GC() + r, _ := http.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + sess, err := globalSessions.SessionStart(w, r) + if err != nil { + t.Fatal("set error,", err) + } + defer sess.SessionRelease(nil, w) + err = sess.Set(nil, "username", "astaxie") + if err != nil { + t.Fatal("set error,", err) + } + if username := sess.Get(nil, "username"); username != "astaxie" { + t.Fatal("get username error") + } + if cookiestr := w.Header().Get("Set-Cookie"); cookiestr == "" { + t.Fatal("setcookie error") + } else { + parts := strings.Split(strings.TrimSpace(cookiestr), ";") + for k, v := range parts { + nameval := strings.Split(v, "=") + if k == 0 && nameval[0] != "gosessionid" { + t.Fatal("error") + } + } + } +} diff --git a/session/sess_test.go b/server/web/session/sess_test.go similarity index 100% rename from session/sess_test.go rename to server/web/session/sess_test.go diff --git a/session/sess_utils.go b/server/web/session/sess_utils.go similarity index 99% rename from session/sess_utils.go rename to server/web/session/sess_utils.go index 20915bb6..8a031dd5 100644 --- a/session/sess_utils.go +++ b/server/web/session/sess_utils.go @@ -29,7 +29,7 @@ import ( "strconv" "time" - "github.com/astaxie/beego/utils" + "github.com/astaxie/beego/core/utils" ) func init() { diff --git a/session/session.go b/server/web/session/session.go similarity index 86% rename from session/session.go rename to server/web/session/session.go index eb85360a..bb7e5bd6 100644 --- a/session/session.go +++ b/server/web/session/session.go @@ -28,6 +28,7 @@ package session import ( + "context" "crypto/rand" "encoding/hex" "errors" @@ -43,24 +44,24 @@ import ( // Store contains all data for one session process with specific id. type Store interface { - Set(key, value interface{}) error //set session value - Get(key interface{}) interface{} //get session value - Delete(key interface{}) error //delete session value - SessionID() string //back current sessionID - SessionRelease(w http.ResponseWriter) // release the resource & save data to provider & return the data - Flush() error //delete all data + Set(ctx context.Context, key, value interface{}) error //set session value + Get(ctx context.Context, key interface{}) interface{} //get session value + Delete(ctx context.Context, key interface{}) error //delete session value + SessionID(ctx context.Context) string //back current sessionID + SessionRelease(ctx context.Context, w http.ResponseWriter) // release the resource & save data to provider & return the data + Flush(ctx context.Context) error //delete all data } // Provider contains global session methods and saved SessionStores. // it can operate a SessionStore by its id. type Provider interface { - SessionInit(gclifetime int64, config string) error - SessionRead(sid string) (Store, error) - SessionExist(sid string) bool - SessionRegenerate(oldsid, sid string) (Store, error) - SessionDestroy(sid string) error - SessionAll() int //get all active session - SessionGC() + SessionInit(ctx context.Context, gclifetime int64, config string) error + SessionRead(ctx context.Context, sid string) (Store, error) + SessionExist(ctx context.Context, sid string) (bool, error) + SessionRegenerate(ctx context.Context, oldsid, sid string) (Store, error) + SessionDestroy(ctx context.Context, sid string) error + SessionAll(ctx context.Context) int //get all active session + SessionGC(ctx context.Context) } var provides = make(map[string]Provider) @@ -148,7 +149,7 @@ func NewManager(provideName string, cf *ManagerConfig) (*Manager, error) { } } - err := provider.SessionInit(cf.Maxlifetime, cf.ProviderConfig) + err := provider.SessionInit(nil, cf.Maxlifetime, cf.ProviderConfig) if err != nil { return nil, err } @@ -211,8 +212,14 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se return nil, errs } - if sid != "" && manager.provider.SessionExist(sid) { - return manager.provider.SessionRead(sid) + if sid != "" { + exists, err := manager.provider.SessionExist(nil, sid) + if err != nil { + return nil, err + } + if exists { + return manager.provider.SessionRead(nil, sid) + } } // Generate a new session @@ -221,7 +228,7 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se return nil, errs } - session, err = manager.provider.SessionRead(sid) + session, err = manager.provider.SessionRead(nil, sid) if err != nil { return nil, err } @@ -263,7 +270,7 @@ func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) { } sid, _ := url.QueryUnescape(cookie.Value) - manager.provider.SessionDestroy(sid) + manager.provider.SessionDestroy(nil, sid) if manager.config.EnableSetCookie { expiration := time.Now() cookie = &http.Cookie{Name: manager.config.CookieName, @@ -279,14 +286,14 @@ func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) { // GetSessionStore Get SessionStore by its id. func (manager *Manager) GetSessionStore(sid string) (sessions Store, err error) { - sessions, err = manager.provider.SessionRead(sid) + sessions, err = manager.provider.SessionRead(nil, sid) return } // GC Start session gc process. // it can do gc in times after gc lifetime. func (manager *Manager) GC() { - manager.provider.SessionGC() + manager.provider.SessionGC(nil) time.AfterFunc(time.Duration(manager.config.Gclifetime)*time.Second, func() { manager.GC() }) } @@ -299,7 +306,7 @@ func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Reque cookie, err := r.Cookie(manager.config.CookieName) if err != nil || cookie.Value == "" { //delete old cookie - session, _ = manager.provider.SessionRead(sid) + session, _ = manager.provider.SessionRead(nil, sid) cookie = &http.Cookie{Name: manager.config.CookieName, Value: url.QueryEscape(sid), Path: "/", @@ -309,7 +316,7 @@ func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Reque } } else { oldsid, _ := url.QueryUnescape(cookie.Value) - session, _ = manager.provider.SessionRegenerate(oldsid, sid) + session, _ = manager.provider.SessionRegenerate(nil, oldsid, sid) cookie.Value = url.QueryEscape(sid) cookie.HttpOnly = true cookie.Path = "/" @@ -333,7 +340,7 @@ func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Reque // GetActiveSession Get all active sessions count number. func (manager *Manager) GetActiveSession() int { - return manager.provider.SessionAll() + return manager.provider.SessionAll(nil) } // SetSecure Set cookie with https. diff --git a/session/ssdb/sess_ssdb.go b/server/web/session/ssdb/sess_ssdb.go similarity index 66% rename from session/ssdb/sess_ssdb.go rename to server/web/session/ssdb/sess_ssdb.go index de0c6360..0adc41bd 100644 --- a/session/ssdb/sess_ssdb.go +++ b/server/web/session/ssdb/sess_ssdb.go @@ -1,14 +1,17 @@ package ssdb import ( + "context" + "encoding/json" "errors" "net/http" "strconv" "strings" "sync" - "github.com/astaxie/beego/session" "github.com/ssdb/gossdb/ssdb" + + "github.com/astaxie/beego/server/web/session" ) var ssdbProvider = &Provider{} @@ -16,35 +19,50 @@ var ssdbProvider = &Provider{} // Provider holds ssdb client and configs type Provider struct { client *ssdb.Client - host string - port int + Host string `json:"host"` + Port int `json:"port"` maxLifetime int64 } func (p *Provider) connectInit() error { var err error - if p.host == "" || p.port == 0 { + if p.Host == "" || p.Port == 0 { return errors.New("SessionInit First") } - p.client, err = ssdb.Connect(p.host, p.port) + p.client, err = ssdb.Connect(p.Host, p.Port) return err } // SessionInit init the ssdb with the config -func (p *Provider) SessionInit(maxLifetime int64, savePath string) error { +func (p *Provider) SessionInit(ctx context.Context, maxLifetime int64, cfg string) error { p.maxLifetime = maxLifetime - address := strings.Split(savePath, ":") - p.host = address[0] + cfg = strings.TrimSpace(cfg) var err error - if p.port, err = strconv.Atoi(address[1]); err != nil { + // we think this is v2.0, using json to init the session + if strings.HasPrefix(cfg, "{") { + err = json.Unmarshal([]byte(cfg), p) + } else { + err = p.initOldStyle(cfg) + } + if err != nil { return err } return p.connectInit() } +// for v1.x +func (p *Provider) initOldStyle(savePath string) error { + address := strings.Split(savePath, ":") + p.Host = address[0] + + var err error + p.Port, err = strconv.Atoi(address[1]) + return err +} + // SessionRead return a ssdb client session Store -func (p *Provider) SessionRead(sid string) (session.Store, error) { +func (p *Provider) SessionRead(ctx context.Context, sid string) (session.Store, error) { if p.client == nil { if err := p.connectInit(); err != nil { return nil, err @@ -68,10 +86,10 @@ func (p *Provider) SessionRead(sid string) (session.Store, error) { } // SessionExist judged whether sid is exist in session -func (p *Provider) SessionExist(sid string) bool { +func (p *Provider) SessionExist(ctx context.Context, sid string) (bool, error) { if p.client == nil { if err := p.connectInit(); err != nil { - panic(err) + return false, err } } value, err := p.client.Get(sid) @@ -79,14 +97,14 @@ func (p *Provider) SessionExist(sid string) bool { panic(err) } if value == nil || len(value.(string)) == 0 { - return false + return false, nil } - return true + return true, nil } // SessionRegenerate regenerate session with new sid and delete oldsid -func (p *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - //conn.Do("setx", key, v, ttl) +func (p *Provider) SessionRegenerate(ctx context.Context, oldsid, sid string) (session.Store, error) { + // conn.Do("setx", key, v, ttl) if p.client == nil { if err := p.connectInit(); err != nil { return nil, err @@ -118,7 +136,7 @@ func (p *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) } // SessionDestroy destroy the sid -func (p *Provider) SessionDestroy(sid string) error { +func (p *Provider) SessionDestroy(ctx context.Context, sid string) error { if p.client == nil { if err := p.connectInit(); err != nil { return err @@ -129,11 +147,11 @@ func (p *Provider) SessionDestroy(sid string) error { } // SessionGC not implemented -func (p *Provider) SessionGC() { +func (p *Provider) SessionGC(context.Context) { } // SessionAll not implemented -func (p *Provider) SessionAll() int { +func (p *Provider) SessionAll(context.Context) int { return 0 } @@ -147,7 +165,7 @@ type SessionStore struct { } // Set the key and value -func (s *SessionStore) Set(key, value interface{}) error { +func (s *SessionStore) Set(ctx context.Context, key, value interface{}) error { s.lock.Lock() defer s.lock.Unlock() s.values[key] = value @@ -155,7 +173,7 @@ func (s *SessionStore) Set(key, value interface{}) error { } // Get return the value by the key -func (s *SessionStore) Get(key interface{}) interface{} { +func (s *SessionStore) Get(ctx context.Context, key interface{}) interface{} { s.lock.Lock() defer s.lock.Unlock() if value, ok := s.values[key]; ok { @@ -165,7 +183,7 @@ func (s *SessionStore) Get(key interface{}) interface{} { } // Delete the key in session store -func (s *SessionStore) Delete(key interface{}) error { +func (s *SessionStore) Delete(ctx context.Context, key interface{}) error { s.lock.Lock() defer s.lock.Unlock() delete(s.values, key) @@ -173,7 +191,7 @@ func (s *SessionStore) Delete(key interface{}) error { } // Flush delete all keys and values -func (s *SessionStore) Flush() error { +func (s *SessionStore) Flush(context.Context) error { s.lock.Lock() defer s.lock.Unlock() s.values = make(map[interface{}]interface{}) @@ -181,12 +199,12 @@ func (s *SessionStore) Flush() error { } // SessionID return the sessionID -func (s *SessionStore) SessionID() string { +func (s *SessionStore) SessionID(context.Context) string { return s.sid } // SessionRelease Store the keyvalues into ssdb -func (s *SessionStore) SessionRelease(w http.ResponseWriter) { +func (s *SessionStore) SessionRelease(ctx context.Context, w http.ResponseWriter) { b, err := session.EncodeGob(s.values) if err != nil { return diff --git a/server/web/session/ssdb/sess_ssdb_test.go b/server/web/session/ssdb/sess_ssdb_test.go new file mode 100644 index 00000000..3de5da0a --- /dev/null +++ b/server/web/session/ssdb/sess_ssdb_test.go @@ -0,0 +1,41 @@ +// Copyright 2020 +// +// Licensed 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 ssdb + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestProvider_SessionInit(t *testing.T) { + // using old style + savePath := `localhost:8080` + cp := &Provider{} + cp.SessionInit(context.Background(), 12, savePath) + assert.Equal(t, "localhost", cp.Host) + assert.Equal(t, 8080, cp.Port) + assert.Equal(t, int64(12), cp.maxLifetime) + + savePath = ` +{ "host": "localhost", "port": 8080} +` + cp = &Provider{} + cp.SessionInit(context.Background(), 12, savePath) + assert.Equal(t, "localhost", cp.Host) + assert.Equal(t, 8080, cp.Port) + assert.Equal(t, int64(12), cp.maxLifetime) +} diff --git a/staticfile.go b/server/web/staticfile.go similarity index 96% rename from staticfile.go rename to server/web/staticfile.go index 84e9aa7b..aa3f35d8 100644 --- a/staticfile.go +++ b/server/web/staticfile.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "bytes" @@ -26,9 +26,10 @@ import ( "sync" "time" - "github.com/astaxie/beego/context" - "github.com/astaxie/beego/logs" - "github.com/hashicorp/golang-lru" + "github.com/astaxie/beego/core/logs" + lru "github.com/hashicorp/golang-lru" + + "github.com/astaxie/beego/server/web/context" ) var errNotStaticRequest = errors.New("request not a static file request") @@ -202,7 +203,7 @@ func searchFile(ctx *context.Context) (string, os.FileInfo, error) { if !strings.Contains(requestPath, prefix) { continue } - if prefix != "/" && len(requestPath) > len(prefix) && requestPath[len(prefix)] != '/' { + if prefix != "/" && len(requestPath) > len(prefix) && requestPath[len(prefix)] != '/' { continue } filePath := path.Join(staticDir, requestPath[len(prefix):]) diff --git a/staticfile_test.go b/server/web/staticfile_test.go similarity index 99% rename from staticfile_test.go rename to server/web/staticfile_test.go index e46c13ec..0725a2f8 100644 --- a/staticfile_test.go +++ b/server/web/staticfile_test.go @@ -1,4 +1,4 @@ -package beego +package web import ( "bytes" diff --git a/toolbox/statistics.go b/server/web/statistics.go similarity index 86% rename from toolbox/statistics.go rename to server/web/statistics.go index fd73dfb3..98f85e96 100644 --- a/toolbox/statistics.go +++ b/server/web/statistics.go @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package toolbox +package web import ( "fmt" "sync" "time" + + "github.com/astaxie/beego/core/utils" ) // Statistics struct @@ -100,13 +102,13 @@ func (m *URLMap) GetMap() map[string]interface{} { fmt.Sprintf("% -10s", kk), fmt.Sprintf("% -16d", vv.RequestNum), fmt.Sprintf("%d", vv.TotalTime), - fmt.Sprintf("% -16s", toS(vv.TotalTime)), + fmt.Sprintf("% -16s", utils.ToShortTimeFormat(vv.TotalTime)), fmt.Sprintf("%d", vv.MaxTime), - fmt.Sprintf("% -16s", toS(vv.MaxTime)), + fmt.Sprintf("% -16s", utils.ToShortTimeFormat(vv.MaxTime)), fmt.Sprintf("%d", vv.MinTime), - fmt.Sprintf("% -16s", toS(vv.MinTime)), + fmt.Sprintf("% -16s", utils.ToShortTimeFormat(vv.MinTime)), fmt.Sprintf("%d", time.Duration(int64(vv.TotalTime)/vv.RequestNum)), - fmt.Sprintf("% -16s", toS(time.Duration(int64(vv.TotalTime)/vv.RequestNum))), + fmt.Sprintf("% -16s", utils.ToShortTimeFormat(time.Duration(int64(vv.TotalTime)/vv.RequestNum))), } resultLists = append(resultLists, result) } @@ -128,10 +130,10 @@ func (m *URLMap) GetMapData() []map[string]interface{} { "request_url": k, "method": kk, "times": vv.RequestNum, - "total_time": toS(vv.TotalTime), - "max_time": toS(vv.MaxTime), - "min_time": toS(vv.MinTime), - "avg_time": toS(time.Duration(int64(vv.TotalTime) / vv.RequestNum)), + "total_time": utils.ToShortTimeFormat(vv.TotalTime), + "max_time": utils.ToShortTimeFormat(vv.MaxTime), + "min_time": utils.ToShortTimeFormat(vv.MinTime), + "avg_time": utils.ToShortTimeFormat(time.Duration(int64(vv.TotalTime) / vv.RequestNum)), } resultLists = append(resultLists, result) } diff --git a/server/web/statistics_test.go b/server/web/statistics_test.go new file mode 100644 index 00000000..7c83e15a --- /dev/null +++ b/server/web/statistics_test.go @@ -0,0 +1,40 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 web + +import ( + "encoding/json" + "testing" + "time" +) + +func TestStatics(t *testing.T) { + StatisticsMap.AddStatistics("POST", "/api/user", "&admin.user", time.Duration(2000)) + StatisticsMap.AddStatistics("POST", "/api/user", "&admin.user", time.Duration(120000)) + StatisticsMap.AddStatistics("GET", "/api/user", "&admin.user", time.Duration(13000)) + StatisticsMap.AddStatistics("POST", "/api/admin", "&admin.user", time.Duration(14000)) + StatisticsMap.AddStatistics("POST", "/api/user/astaxie", "&admin.user", time.Duration(12000)) + StatisticsMap.AddStatistics("POST", "/api/user/xiemengjun", "&admin.user", time.Duration(13000)) + StatisticsMap.AddStatistics("DELETE", "/api/user", "&admin.user", time.Duration(1400)) + t.Log(StatisticsMap.GetMap()) + + data := StatisticsMap.GetMapData() + b, err := json.Marshal(data) + if err != nil { + t.Errorf(err.Error()) + } + + t.Log(string(b)) +} diff --git a/swagger/swagger.go b/server/web/swagger/swagger.go similarity index 100% rename from swagger/swagger.go rename to server/web/swagger/swagger.go diff --git a/template.go b/server/web/template.go similarity index 97% rename from template.go rename to server/web/template.go index 59875be7..d582dcda 100644 --- a/template.go +++ b/server/web/template.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "errors" @@ -27,8 +27,8 @@ import ( "strings" "sync" - "github.com/astaxie/beego/logs" - "github.com/astaxie/beego/utils" + "github.com/astaxie/beego/core/logs" + "github.com/astaxie/beego/core/utils" ) var ( @@ -368,14 +368,14 @@ func SetTemplateFSFunc(fnt templateFSFunc) { } // SetViewsPath sets view directory path in beego application. -func SetViewsPath(path string) *App { +func SetViewsPath(path string) *HttpServer { BConfig.WebConfig.ViewsPath = path return BeeApp } // SetStaticPath sets static directory path and proper url pattern in beego application. // if beego.SetStaticPath("static","public"), visit /static/* to load static file in folder "public". -func SetStaticPath(url string, path string) *App { +func SetStaticPath(url string, path string) *HttpServer { if !strings.HasPrefix(url, "/") { url = "/" + url } @@ -387,7 +387,7 @@ func SetStaticPath(url string, path string) *App { } // DelStaticPath removes the static folder setting in this url pattern in beego application. -func DelStaticPath(url string) *App { +func DelStaticPath(url string) *HttpServer { if !strings.HasPrefix(url, "/") { url = "/" + url } @@ -399,7 +399,7 @@ func DelStaticPath(url string) *App { } // AddTemplateEngine add a new templatePreProcessor which support extension -func AddTemplateEngine(extension string, fn templatePreProcessor) *App { +func AddTemplateEngine(extension string, fn templatePreProcessor) *HttpServer { AddTemplateExt(extension) beeTemplateEngines[extension] = fn return BeeApp diff --git a/template_test.go b/server/web/template_test.go similarity index 88% rename from template_test.go rename to server/web/template_test.go index 287faadc..b542494d 100644 --- a/template_test.go +++ b/server/web/template_test.go @@ -12,16 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "bytes" - "github.com/astaxie/beego/testdata" - "github.com/elazarl/go-bindata-assetfs" "net/http" "os" "path/filepath" "testing" + + assetfs "github.com/elazarl/go-bindata-assetfs" + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/test" ) var header = `{{define "header"}} @@ -46,7 +49,9 @@ var block = `{{define "block"}} {{end}}` func TestTemplate(t *testing.T) { - dir := "_beeTmp" + wkdir, err := os.Getwd() + assert.Nil(t, err) + dir := filepath.Join(wkdir, "_beeTmp", "TestTemplate") files := []string{ "header.tpl", "index.tpl", @@ -56,7 +61,8 @@ func TestTemplate(t *testing.T) { t.Fatal(err) } for k, name := range files { - os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0777) + dirErr := os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0777) + assert.Nil(t, dirErr) if f, err := os.Create(filepath.Join(dir, name)); err != nil { t.Fatal(err) } else { @@ -107,7 +113,9 @@ var user = ` ` func TestRelativeTemplate(t *testing.T) { - dir := "_beeTmp" + wkdir, err := os.Getwd() + assert.Nil(t, err) + dir := filepath.Join(wkdir, "_beeTmp") //Just add dir to known viewPaths if err := AddViewPath(dir); err != nil { @@ -218,7 +226,10 @@ var output = ` ` func TestTemplateLayout(t *testing.T) { - dir := "_beeTmp" + wkdir, err := os.Getwd() + assert.Nil(t, err) + + dir := filepath.Join(wkdir, "_beeTmp", "TestTemplateLayout") files := []string{ "add.tpl", "layout_blog.tpl", @@ -226,17 +237,22 @@ func TestTemplateLayout(t *testing.T) { if err := os.MkdirAll(dir, 0777); err != nil { t.Fatal(err) } + for k, name := range files { - os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0777) + dirErr := os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0777) + assert.Nil(t, dirErr) if f, err := os.Create(filepath.Join(dir, name)); err != nil { t.Fatal(err) } else { if k == 0 { - f.WriteString(add) + _, writeErr := f.WriteString(add) + assert.Nil(t, writeErr) } else if k == 1 { - f.WriteString(layoutBlog) + _, writeErr := f.WriteString(layoutBlog) + assert.Nil(t, writeErr) } - f.Close() + clErr := f.Close() + assert.Nil(t, clErr) } } if err := AddViewPath(dir); err != nil { @@ -247,6 +263,7 @@ func TestTemplateLayout(t *testing.T) { t.Fatalf("should be 2 but got %v", len(beeTemplates)) } out := bytes.NewBufferString("") + if err := beeTemplates["add.tpl"].ExecuteTemplate(out, "add.tpl", map[string]string{"Title": "Hello", "SomeVar": "val"}); err != nil { t.Fatal(err) } @@ -291,7 +308,7 @@ var outputBinData = ` func TestFsBinData(t *testing.T) { SetTemplateFSFunc(func() http.FileSystem { - return TestingFileSystem{&assetfs.AssetFS{Asset: testdata.Asset, AssetDir: testdata.AssetDir, AssetInfo: testdata.AssetInfo}} + return TestingFileSystem{&assetfs.AssetFS{Asset: test.Asset, AssetDir: test.AssetDir, AssetInfo: test.AssetInfo}} }) dir := "views" if err := AddViewPath("views"); err != nil { diff --git a/templatefunc.go b/server/web/templatefunc.go similarity index 98% rename from templatefunc.go rename to server/web/templatefunc.go index ba1ec5eb..53c99018 100644 --- a/templatefunc.go +++ b/server/web/templatefunc.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "errors" @@ -58,11 +58,11 @@ func HTML2str(html string) string { re := regexp.MustCompile(`\<[\S\s]+?\>`) html = re.ReplaceAllStringFunc(html, strings.ToLower) - //remove STYLE + // remove STYLE re = regexp.MustCompile(`\`) html = re.ReplaceAllString(html, "") - //remove SCRIPT + // remove SCRIPT re = regexp.MustCompile(`\`) html = re.ReplaceAllString(html, "") @@ -85,7 +85,7 @@ func DateFormat(t time.Time, layout string) (datestring string) { var datePatterns = []string{ // year "Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003 - "y", "06", //A two digit representation of a year Examples: 99 or 03 + "y", "06", // A two digit representation of a year Examples: 99 or 03 // month "m", "01", // Numeric representation of a month, with leading zeros 01 through 12 @@ -160,7 +160,7 @@ func NotNil(a interface{}) (isNil bool) { func GetConfig(returnType, key string, defaultVal interface{}) (value interface{}, err error) { switch returnType { case "String": - value = AppConfig.String(key) + value, err = AppConfig.String(key) case "Bool": value, err = AppConfig.Bool(key) case "Int": @@ -201,7 +201,7 @@ func Str2html(raw string) template.HTML { // Htmlquote returns quoted html string. func Htmlquote(text string) string { - //HTML编码为实体符号 + // HTML编码为实体符号 /* Encodes `text` for raw use in HTML. >>> htmlquote("<'&\\">") @@ -220,7 +220,7 @@ func Htmlquote(text string) string { // Htmlunquote returns unquoted html string. func Htmlunquote(text string) string { - //实体符号解释为HTML + // 实体符号解释为HTML /* Decodes `text` that's HTML quoted. >>> htmlunquote('<'&">') @@ -362,7 +362,7 @@ func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) e value = value[:25] t, err = time.ParseInLocation(time.RFC3339, value, time.Local) } else if strings.HasSuffix(strings.ToUpper(value), "Z") { - t, err = time.ParseInLocation(time.RFC3339, value, time.Local) + t, err = time.ParseInLocation(time.RFC3339, value, time.Local) } else if len(value) >= 19 { if strings.Contains(value, "T") { value = value[:19] diff --git a/templatefunc_test.go b/server/web/templatefunc_test.go similarity index 99% rename from templatefunc_test.go rename to server/web/templatefunc_test.go index b4c19c2e..df5cfa40 100644 --- a/templatefunc_test.go +++ b/server/web/templatefunc_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "html/template" diff --git a/tree.go b/server/web/tree.go similarity index 95% rename from tree.go rename to server/web/tree.go index 9e53003b..fc5a11a2 100644 --- a/tree.go +++ b/server/web/tree.go @@ -12,15 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "path" "regexp" "strings" - "github.com/astaxie/beego/context" - "github.com/astaxie/beego/utils" + "github.com/astaxie/beego/core/utils" + + "github.com/astaxie/beego/server/web/context" ) var ( @@ -32,13 +33,13 @@ var ( // wildcard stores params // leaves store the endpoint information type Tree struct { - //prefix set for static router + // prefix set for static router prefix string - //search fix route first + // search fix route first fixrouters []*Tree - //if set, failure to match fixrouters search then search wildcard + // if set, failure to match fixrouters search then search wildcard wildcard *Tree - //if set, failure to match wildcard search + // if set, failure to match wildcard search leaves []*leafInfo } @@ -68,13 +69,13 @@ func (t *Tree) addtree(segments []string, tree *Tree, wildcards []string, reg st filterTreeWithPrefix(tree, wildcards, reg) } } - //Rule: /login/*/access match /login/2009/11/access - //if already has *, and when loop the access, should as a regexpStr + // Rule: /login/*/access match /login/2009/11/access + // if already has *, and when loop the access, should as a regexpStr if !iswild && utils.InSlice(":splat", wildcards) { iswild = true regexpStr = seg } - //Rule: /user/:id/* + // Rule: /user/:id/* if seg == "*" && len(wildcards) > 0 && reg == "" { regexpStr = "(.+)" } @@ -221,13 +222,13 @@ func (t *Tree) addseg(segments []string, route interface{}, wildcards []string, t.addseg(segments[1:], route, wildcards, reg) params = params[1:] } - //Rule: /login/*/access match /login/2009/11/access - //if already has *, and when loop the access, should as a regexpStr + // Rule: /login/*/access match /login/2009/11/access + // if already has *, and when loop the access, should as a regexpStr if !iswild && utils.InSlice(":splat", wildcards) { iswild = true regexpStr = seg } - //Rule: /user/:id/* + // Rule: /user/:id/* if seg == "*" && len(wildcards) > 0 && reg == "" { regexpStr = "(.+)" } @@ -392,7 +393,7 @@ type leafInfo struct { } func (leaf *leafInfo) match(treePattern string, wildcardValues []string, ctx *context.Context) (ok bool) { - //fmt.Println("Leaf:", wildcardValues, leaf.wildcards, leaf.regexps) + // fmt.Println("Leaf:", wildcardValues, leaf.wildcards, leaf.regexps) if leaf.regexps == nil { if len(wildcardValues) == 0 && len(leaf.wildcards) == 0 { // static path return true @@ -499,7 +500,7 @@ func splitSegment(key string) (bool, []string, string) { continue } if start { - //:id:int and :name:string + // :id:int and :name:string if v == ':' { if len(key) >= i+4 { if key[i+1:i+4] == "int" { diff --git a/tree_test.go b/server/web/tree_test.go similarity index 99% rename from tree_test.go rename to server/web/tree_test.go index d412a348..e72bc1f9 100644 --- a/tree_test.go +++ b/server/web/tree_test.go @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "strings" "testing" - "github.com/astaxie/beego/context" + "github.com/astaxie/beego/server/web/context" ) type testinfo struct { diff --git a/unregroute_test.go b/server/web/unregroute_test.go similarity index 99% rename from unregroute_test.go rename to server/web/unregroute_test.go index 08b1b77b..c675ae7d 100644 --- a/unregroute_test.go +++ b/server/web/unregroute_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package beego +package web import ( "net/http" diff --git a/task/govenor_command.go b/task/govenor_command.go new file mode 100644 index 00000000..15e25e43 --- /dev/null +++ b/task/govenor_command.go @@ -0,0 +1,92 @@ +// Copyright 2020 +// +// Licensed 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 task + +import ( + "context" + "fmt" + "html/template" + + "github.com/pkg/errors" + + "github.com/astaxie/beego/core/governor" +) + +type listTaskCommand struct { +} + +func (l *listTaskCommand) Execute(params ...interface{}) *governor.Result { + resultList := make([][]string, 0, len(globalTaskManager.adminTaskList)) + for tname, tk := range globalTaskManager.adminTaskList { + result := []string{ + template.HTMLEscapeString(tname), + template.HTMLEscapeString(tk.GetSpec(nil)), + template.HTMLEscapeString(tk.GetStatus(nil)), + template.HTMLEscapeString(tk.GetPrev(context.Background()).String()), + } + resultList = append(resultList, result) + } + + return &governor.Result{ + Status: 200, + Content: resultList, + } +} + +type runTaskCommand struct { +} + +func (r *runTaskCommand) Execute(params ...interface{}) *governor.Result { + if len(params) == 0 { + return &governor.Result{ + Status: 400, + Error: errors.New("task name not passed"), + } + } + + tn, ok := params[0].(string) + + if !ok { + return &governor.Result{ + Status: 400, + Error: errors.New("parameter is invalid"), + } + } + + if t, ok := globalTaskManager.adminTaskList[tn]; ok { + err := t.Run(context.Background()) + if err != nil { + return &governor.Result{ + Status: 500, + Error: err, + } + } + return &governor.Result{ + Status: 200, + Content: t.GetStatus(context.Background()), + } + } else { + return &governor.Result{ + Status: 400, + Error: errors.New(fmt.Sprintf("task with name %s not found", tn)), + } + } + +} + +func registerCommands() { + governor.RegisterCommand("task", "list", &listTaskCommand{}) + governor.RegisterCommand("task", "run", &runTaskCommand{}) +} diff --git a/task/governor_command_test.go b/task/governor_command_test.go new file mode 100644 index 00000000..00ed37f2 --- /dev/null +++ b/task/governor_command_test.go @@ -0,0 +1,111 @@ +// Copyright 2020 +// +// Licensed 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 task + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +type countTask struct { + cnt int + mockErr error +} + +func (c *countTask) GetSpec(ctx context.Context) string { + return "AAA" +} + +func (c *countTask) GetStatus(ctx context.Context) string { + return "SUCCESS" +} + +func (c *countTask) Run(ctx context.Context) error { + c.cnt++ + return c.mockErr +} + +func (c *countTask) SetNext(ctx context.Context, time time.Time) { +} + +func (c *countTask) GetNext(ctx context.Context) time.Time { + return time.Now() +} + +func (c *countTask) SetPrev(ctx context.Context, time time.Time) { +} + +func (c *countTask) GetPrev(ctx context.Context) time.Time { + return time.Now() +} + +func TestRunTaskCommand_Execute(t *testing.T) { + task := &countTask{} + AddTask("count", task) + + cmd := &runTaskCommand{} + + res := cmd.Execute() + assert.NotNil(t, res) + assert.NotNil(t, res.Error) + assert.Equal(t, "task name not passed", res.Error.Error()) + + res = cmd.Execute(10) + assert.NotNil(t, res) + assert.NotNil(t, res.Error) + assert.Equal(t, "parameter is invalid", res.Error.Error()) + + res = cmd.Execute("CCCC") + assert.NotNil(t, res) + assert.NotNil(t, res.Error) + assert.Equal(t, "task with name CCCC not found", res.Error.Error()) + + res = cmd.Execute("count") + assert.NotNil(t, res) + assert.True(t, res.IsSuccess()) + + task.mockErr = errors.New("mock error") + res = cmd.Execute("count") + assert.NotNil(t, res) + assert.NotNil(t, res.Error) + assert.Equal(t, "mock error", res.Error.Error()) +} + +func TestListTaskCommand_Execute(t *testing.T) { + task := &countTask{} + + cmd := &listTaskCommand{} + + res := cmd.Execute() + + assert.True(t, res.IsSuccess()) + + _, ok := res.Content.([][]string) + assert.True(t, ok) + + AddTask("count", task) + + res = cmd.Execute() + + assert.True(t, res.IsSuccess()) + + rl, ok := res.Content.([][]string) + assert.True(t, ok) + assert.Equal(t, 1, len(rl)) +} diff --git a/toolbox/task.go b/task/task.go similarity index 74% rename from toolbox/task.go rename to task/task.go index d2a94ba9..8f25a0f3 100644 --- a/toolbox/task.go +++ b/task/task.go @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -package toolbox +package task import ( + "context" "log" "math" "sort" @@ -30,18 +31,33 @@ type bounds struct { names map[string]uint } -// The bounds for each field. -var ( - AdminTaskList map[string]Tasker +type taskManager struct { + adminTaskList map[string]Tasker taskLock sync.RWMutex stop chan bool changed chan bool - isstart bool - seconds = bounds{0, 59, nil} - minutes = bounds{0, 59, nil} - hours = bounds{0, 23, nil} - days = bounds{1, 31, nil} - months = bounds{1, 12, map[string]uint{ + started bool +} + +func newTaskManager() *taskManager { + return &taskManager{ + adminTaskList: make(map[string]Tasker), + taskLock: sync.RWMutex{}, + stop: make(chan bool), + changed: make(chan bool), + started: false, + } +} + +// The bounds for each field. +var ( + globalTaskManager *taskManager + + seconds = bounds{0, 59, nil} + minutes = bounds{0, 59, nil} + hours = bounds{0, 23, nil} + days = bounds{1, 31, nil} + months = bounds{1, 12, map[string]uint{ "jan": 1, "feb": 2, "mar": 3, @@ -82,17 +98,17 @@ type Schedule struct { } // TaskFunc task func type -type TaskFunc func() error +type TaskFunc func(ctx context.Context) error // Tasker task interface type Tasker interface { - GetSpec() string - GetStatus() string - Run() error - SetNext(time.Time) - GetNext() time.Time - SetPrev(time.Time) - GetPrev() time.Time + GetSpec(ctx context.Context) string + GetStatus(ctx context.Context) string + Run(ctx context.Context) error + SetNext(context.Context, time.Time) + GetNext(ctx context.Context) time.Time + SetPrev(context.Context, time.Time) + GetPrev(ctx context.Context) time.Time } // task error @@ -102,6 +118,8 @@ type taskerr struct { } // Task task struct +// It's not a thread-safe structure. +// Only nearest errors will be saved in ErrList type Task struct { Taskname string Spec *Schedule @@ -111,6 +129,7 @@ type Task struct { Next time.Time Errlist []*taskerr // like errtime:errinfo ErrLimit int // max length for the errlist, 0 stand for no limit + errCnt int // records the error count during the execution } // NewTask add new task with name, time and func @@ -119,20 +138,23 @@ func NewTask(tname string, spec string, f TaskFunc) *Task { task := &Task{ Taskname: tname, DoFunc: f, + // Make configurable ErrLimit: 100, SpecStr: spec, + // we only store the pointer, so it won't use too many space + Errlist: make([]*taskerr, 100, 100), } task.SetCron(spec) return task } // GetSpec get spec string -func (t *Task) GetSpec() string { +func (t *Task) GetSpec(context.Context) string { return t.SpecStr } // GetStatus get current task status -func (t *Task) GetStatus() string { +func (t *Task) GetStatus(context.Context) string { var str string for _, v := range t.Errlist { str += v.t.String() + ":" + v.errinfo + "
" @@ -141,33 +163,33 @@ func (t *Task) GetStatus() string { } // Run run all tasks -func (t *Task) Run() error { - err := t.DoFunc() +func (t *Task) Run(ctx context.Context) error { + err := t.DoFunc(ctx) if err != nil { - if t.ErrLimit > 0 && t.ErrLimit > len(t.Errlist) { - t.Errlist = append(t.Errlist, &taskerr{t: t.Next, errinfo: err.Error()}) - } + index := t.errCnt % t.ErrLimit + t.Errlist[index] = &taskerr{t: t.Next, errinfo: err.Error()} + t.errCnt++ } return err } // SetNext set next time for this task -func (t *Task) SetNext(now time.Time) { +func (t *Task) SetNext(ctx context.Context, now time.Time) { t.Next = t.Spec.Next(now) } // GetNext get the next call time of this task -func (t *Task) GetNext() time.Time { +func (t *Task) GetNext(context.Context) time.Time { return t.Next } // SetPrev set prev time of this task -func (t *Task) SetPrev(now time.Time) { +func (t *Task) SetPrev(ctx context.Context, now time.Time) { t.Prev = now } // GetPrev get prev time of this task -func (t *Task) GetPrev() time.Time { +func (t *Task) GetPrev(context.Context) time.Time { return t.Prev } @@ -182,9 +204,9 @@ func (t *Task) GetPrev() time.Time { // SetCron some signals: // *: any time // ,:  separate signal -//   -:duration +//    -:duration // /n : do as n times of time duration -///////////////////////////////////////////////////////// +// /////////////////////////////////////////////////////// // 0/30 * * * * * every 30s // 0 43 21 * * * 21:43 // 0 15 05 * * *    05:15 @@ -391,91 +413,153 @@ func dayMatches(s *Schedule, t time.Time) bool { // StartTask start all tasks func StartTask() { - taskLock.Lock() - defer taskLock.Unlock() - if isstart { - //If already started, no need to start another goroutine. - return - } - isstart = true - go run() + globalTaskManager.StartTask() } -func run() { +// StopTask stop all tasks +func StopTask() { + globalTaskManager.StopTask() +} + +// AddTask add task with name +func AddTask(taskName string, t Tasker) { + globalTaskManager.AddTask(taskName, t) +} + +// DeleteTask delete task with name +func DeleteTask(taskName string) { + globalTaskManager.DeleteTask(taskName) +} + +// ClearTask clear all tasks +func ClearTask() { + globalTaskManager.ClearTask() +} + +// StartTask start all tasks +func (m *taskManager) StartTask() { + m.taskLock.Lock() + defer m.taskLock.Unlock() + if m.started { + // If already started, no need to start another goroutine. + return + } + m.started = true + + registerCommands() + go m.run() +} + +func (m *taskManager) run() { now := time.Now().Local() - for _, t := range AdminTaskList { - t.SetNext(now) + for _, t := range m.adminTaskList { + t.SetNext(nil, now) } for { // we only use RLock here because NewMapSorter copy the reference, do not change any thing - taskLock.RLock() - sortList := NewMapSorter(AdminTaskList) - taskLock.RUnlock() + m.taskLock.RLock() + sortList := NewMapSorter(m.adminTaskList) + m.taskLock.RUnlock() sortList.Sort() var effective time.Time - if len(AdminTaskList) == 0 || sortList.Vals[0].GetNext().IsZero() { + if len(m.adminTaskList) == 0 || sortList.Vals[0].GetNext(context.Background()).IsZero() { // If there are no entries yet, just sleep - it still handles new entries // and stop requests. effective = now.AddDate(10, 0, 0) } else { - effective = sortList.Vals[0].GetNext() + effective = sortList.Vals[0].GetNext(context.Background()) } select { case now = <-time.After(effective.Sub(now)): // Run every entry whose next time was this effective time. for _, e := range sortList.Vals { - if e.GetNext() != effective { + if e.GetNext(context.Background()) != effective { break } - go e.Run() - e.SetPrev(e.GetNext()) - e.SetNext(effective) + go e.Run(nil) + e.SetPrev(context.Background(), e.GetNext(context.Background())) + e.SetNext(nil, effective) } continue - case <-changed: + case <-m.changed: now = time.Now().Local() - taskLock.Lock() - for _, t := range AdminTaskList { - t.SetNext(now) + m.taskLock.Lock() + for _, t := range m.adminTaskList { + t.SetNext(nil, now) } - taskLock.Unlock() + m.taskLock.Unlock() continue - case <-stop: + case <-m.stop: + m.taskLock.Lock() + if m.started { + m.started = false + } + m.taskLock.Unlock() return } } } // StopTask stop all tasks -func StopTask() { - taskLock.Lock() - defer taskLock.Unlock() - if isstart { - isstart = false - stop <- true - } - +func (m *taskManager) StopTask() { + go func() { + m.stop <- true + }() } // AddTask add task with name -func AddTask(taskname string, t Tasker) { - taskLock.Lock() - defer taskLock.Unlock() - t.SetNext(time.Now().Local()) - AdminTaskList[taskname] = t - if isstart { - changed <- true +func (m *taskManager) AddTask(taskname string, t Tasker) { + isChanged := false + m.taskLock.Lock() + t.SetNext(nil, time.Now().Local()) + m.adminTaskList[taskname] = t + if m.started { + isChanged = true } + m.taskLock.Unlock() + + if isChanged { + go func() { + m.changed <- true + }() + } + } // DeleteTask delete task with name -func DeleteTask(taskname string) { - taskLock.Lock() - defer taskLock.Unlock() - delete(AdminTaskList, taskname) - if isstart { - changed <- true +func (m *taskManager) DeleteTask(taskname string) { + isChanged := false + + m.taskLock.Lock() + delete(m.adminTaskList, taskname) + if m.started { + isChanged = true + } + m.taskLock.Unlock() + + if isChanged { + go func() { + m.changed <- true + }() + } +} + +// ClearTask clear all tasks +func (m *taskManager) ClearTask() { + isChanged := false + + m.taskLock.Lock() + m.adminTaskList = make(map[string]Tasker) + if m.started { + isChanged = true + } + m.taskLock.Unlock() + + if isChanged { + go func() { + m.changed <- true + }() } } @@ -505,13 +589,13 @@ func (ms *MapSorter) Sort() { func (ms *MapSorter) Len() int { return len(ms.Keys) } func (ms *MapSorter) Less(i, j int) bool { - if ms.Vals[i].GetNext().IsZero() { + if ms.Vals[i].GetNext(context.Background()).IsZero() { return false } - if ms.Vals[j].GetNext().IsZero() { + if ms.Vals[j].GetNext(context.Background()).IsZero() { return true } - return ms.Vals[i].GetNext().Before(ms.Vals[j].GetNext()) + return ms.Vals[i].GetNext(context.Background()).Before(ms.Vals[j].GetNext(context.Background())) } func (ms *MapSorter) Swap(i, j int) { ms.Vals[i], ms.Vals[j] = ms.Vals[j], ms.Vals[i] @@ -628,7 +712,5 @@ func all(r bounds) uint64 { } func init() { - AdminTaskList = make(map[string]Tasker) - stop = make(chan bool) - changed = make(chan bool) + globalTaskManager = newTaskManager() } diff --git a/task/task_test.go b/task/task_test.go new file mode 100644 index 00000000..2cb807ce --- /dev/null +++ b/task/task_test.go @@ -0,0 +1,117 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed 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 task + +import ( + "context" + "errors" + "fmt" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestParse(t *testing.T) { + m := newTaskManager() + defer m.ClearTask() + tk := NewTask("taska", "0/30 * * * * *", func(ctx context.Context) error { + fmt.Println("hello world") + return nil + }) + err := tk.Run(nil) + if err != nil { + t.Fatal(err) + } + m.AddTask("taska", tk) + m.StartTask() + time.Sleep(3 * time.Second) + m.StopTask() +} + +func TestModifyTaskListAfterRunning(t *testing.T) { + m := newTaskManager() + defer m.ClearTask() + tk := NewTask("taskb", "0/30 * * * * *", func(ctx context.Context) error { + fmt.Println("hello world") + return nil + }) + err := tk.Run(nil) + if err != nil { + t.Fatal(err) + } + m.AddTask("taskb", tk) + m.StartTask() + go func() { + m.DeleteTask("taskb") + }() + go func() { + m.AddTask("taskb1", tk) + }() + + time.Sleep(3 * time.Second) + m.StopTask() +} + +func TestSpec(t *testing.T) { + m := newTaskManager() + defer m.ClearTask() + wg := &sync.WaitGroup{} + wg.Add(2) + tk1 := NewTask("tk1", "0 12 * * * *", func(ctx context.Context) error { fmt.Println("tk1"); return nil }) + tk2 := NewTask("tk2", "0,10,20 * * * * *", func(ctx context.Context) error { fmt.Println("tk2"); wg.Done(); return nil }) + tk3 := NewTask("tk3", "0 10 * * * *", func(ctx context.Context) error { fmt.Println("tk3"); wg.Done(); return nil }) + + m.AddTask("tk1", tk1) + m.AddTask("tk2", tk2) + m.AddTask("tk3", tk3) + m.StartTask() + defer m.StopTask() + + select { + case <-time.After(200 * time.Second): + t.FailNow() + case <-wait(wg): + } +} + +func TestTask_Run(t *testing.T) { + cnt := -1 + task := func(ctx context.Context) error { + cnt++ + fmt.Printf("Hello, world! %d \n", cnt) + return errors.New(fmt.Sprintf("Hello, world! %d", cnt)) + } + tk := NewTask("taska", "0/30 * * * * *", task) + for i := 0; i < 200; i++ { + e := tk.Run(nil) + assert.NotNil(t, e) + } + + l := tk.Errlist + assert.Equal(t, 100, len(l)) + assert.Equal(t, "Hello, world! 100", l[0].errinfo) + assert.Equal(t, "Hello, world! 101", l[1].errinfo) +} + +func wait(wg *sync.WaitGroup) chan bool { + ch := make(chan bool) + go func() { + wg.Wait() + ch <- true + }() + return ch +} diff --git a/testdata/Makefile b/test/Makefile similarity index 100% rename from testdata/Makefile rename to test/Makefile diff --git a/testdata/bindata.go b/test/bindata.go similarity index 99% rename from testdata/bindata.go rename to test/bindata.go index beade103..196ea95c 100644 --- a/testdata/bindata.go +++ b/test/bindata.go @@ -5,19 +5,20 @@ // views/index.tpl // DO NOT EDIT! -package testdata +package test import ( "bytes" "compress/gzip" "fmt" - "github.com/elazarl/go-bindata-assetfs" "io" "io/ioutil" "os" "path/filepath" "strings" "time" + + assetfs "github.com/elazarl/go-bindata-assetfs" ) func bindataRead(data []byte, name string) ([]byte, error) { diff --git a/testdata/views/blocks/block.tpl b/test/views/blocks/block.tpl similarity index 100% rename from testdata/views/blocks/block.tpl rename to test/views/blocks/block.tpl diff --git a/testdata/views/header.tpl b/test/views/header.tpl similarity index 100% rename from testdata/views/header.tpl rename to test/views/header.tpl diff --git a/testdata/views/index.tpl b/test/views/index.tpl similarity index 100% rename from testdata/views/index.tpl rename to test/views/index.tpl