@ -0,0 +1,238 @@ | |||||
-module(utList). | |||||
-define(TRUE, true). | |||||
-define(FALSE, false). | |||||
-define(BREAK, break). | |||||
-define(BREAK(Value), {?BREAK, Value}). | |||||
-define(CONTINUE, continue). | |||||
-define(CONTINUE(Value), {?CONTINUE, Value}). | |||||
-define(UNDEFINED, undefined). | |||||
-compile([export_all, nowarn_export_all]). | |||||
while(Fun, CurData) -> | |||||
case Fun(CurData) of | |||||
?BREAK -> CurData; | |||||
?BREAK(BreakData) -> BreakData; | |||||
?CONTINUE -> | |||||
while(Fun, CurData); | |||||
?CONTINUE(Continue) -> | |||||
while(Fun, Continue) | |||||
end. | |||||
%% For loop. | |||||
for(N, Fun) -> | |||||
for(1, N, 1, Fun). | |||||
for(Start, End, Fun) -> | |||||
for(Start, End, 1, Fun). | |||||
for(End, End, _Step, Fun) -> | |||||
Fun(End); | |||||
for(Start, End, Step, Fun) -> | |||||
Fun(Start), | |||||
for(Start + Step, End, Step, Fun). | |||||
%% 带返回状态的for循环 @return {ok, State} | |||||
forWithState(Index, Max, _Fun, State) when Index > Max -> | |||||
{ok, State}; | |||||
forWithState(Max, Max, Fun, State) -> | |||||
Fun(Max, State); | |||||
forWithState(Index, Max, Fun, State) -> | |||||
{ok, NewState} = Fun(Index, State), | |||||
forWithState(Index + 1, Max, Fun, NewState). | |||||
forWhile(Fun, State) -> | |||||
forWhile(?CONTINUE, Fun, State). | |||||
forWhile(?BREAK, _Fun, State) -> | |||||
State; | |||||
forWhile(?CONTINUE, Fun, State) -> | |||||
{Next, NewState} = Fun(State), | |||||
forWhile(Next, Fun, NewState). | |||||
%% 可以重复获取 | |||||
randFormList([]) -> | |||||
false; | |||||
randFormList(List) -> | |||||
lists:nth(rand:uniform_s(1, length(List)), List). | |||||
%% 不能重复获取 | |||||
randFormListOnce([]) -> | |||||
{false, []}; | |||||
randFormListOnce(List) -> | |||||
Nth = rand:uniform_s(1, length(List)), | |||||
NthData = lists:nth(Nth, List), | |||||
{NthData, lists:delete(NthData, List)}. | |||||
randDelete(RemainList) -> | |||||
Pos = rand:uniform_s(1, length(RemainList)), | |||||
remove_nth(Pos, RemainList). | |||||
randDelete(DelCount, List) -> | |||||
FunDel = | |||||
fun(_, RemainList) -> | |||||
RemainList(RemainList) | |||||
end, | |||||
lists:foldl(FunDel, List, lists:seq(1, DelCount)). | |||||
%%随机从集合中选出指定个数的元素length(List) >= Num | |||||
%%[1,2,3,4,5,6,7,8,9]中选出三个不同的数字[1,2,4] | |||||
getRandFromList(Num, List) -> | |||||
ListSize = length(List), | |||||
FunDel = | |||||
fun(N, List1) -> | |||||
Random = rand:uniform_s(1, (ListSize - N + 1)), | |||||
Elem = lists:nth(Random, List1), | |||||
lists:delete(Elem, List1) | |||||
end, | |||||
Result = lists:foldl(FunDel, List, lists:seq(1, Num)), | |||||
List -- Result. | |||||
%%打乱列表函数 List =[] 返回 打乱后列表 List2 | |||||
confuseList(List) -> | |||||
confuseList(List, []). | |||||
confuseList(List, Result) -> | |||||
confuseList(List, rand:uniform_s(1, length(List)), Result). | |||||
confuseList(_List, 0, Result) -> | |||||
Result; | |||||
confuseList(List, Nth, Result) -> | |||||
{NthData, Remain} = extract_member(rand:uniform_s(1, Nth), List), | |||||
confuseList(Remain, length(Remain), [NthData | Result]). | |||||
%%提取列表中第Nth个元素,{NthData, RemainList} | |||||
extract_member(Nth, List) -> | |||||
extract_member(Nth, List, []). | |||||
extract_member(1, List1, List2) -> | |||||
case List1 of | |||||
[R] -> | |||||
{R, List2}; | |||||
[R | L] -> | |||||
{R, List2 ++ L} | |||||
end; | |||||
extract_member(Nth, [R | List1], List2) -> | |||||
extract_member(Nth - 1, List1, List2 ++ [R]). | |||||
%%根数Tuple元素数量,把List转成Tuple 返回:{TupleList, RemainList} | |||||
list_to_tuple(List, TupleCount) -> | |||||
list_to_tuple(List, TupleCount, []). | |||||
list_to_tuple(List, TupleCount, Result) when length(List) >= TupleCount -> | |||||
{List1, List2} = lists:split(TupleCount, List), | |||||
list_to_tuple(List2, TupleCount, [list_to_tuple(List1) | Result]); | |||||
list_to_tuple(List, _TupleCount, Result) -> | |||||
{lists:reverse(Result), List}. | |||||
%%计算元素在列表中出现几次(5, [1,2,3,5,5,5,3]) ----> 3 | |||||
repeatCount(Element, List) -> | |||||
repeatCount(Element, List, 0). | |||||
repeatCount(Element, List, Count) -> | |||||
case lists:member(Element, List) of | |||||
false -> Count; | |||||
true -> | |||||
repeatCount(Element, lists:delete(Element, List), Count + 1) | |||||
end. | |||||
remove_nth(RemainList, Pos) -> | |||||
Head = lists:sublist(RemainList, 1, Pos - 1), | |||||
End = lists:sublist(RemainList, Pos + 1, length(RemainList)), | |||||
Head ++ End. | |||||
%%删除指定元素返回:(5, [1,2,4,4,5,5,4,5]) -> [1,2,4,4,4] | |||||
remove_element(Element, List) -> | |||||
case lists:member(Element, List) of | |||||
false -> List; | |||||
true -> remove_element(Element, lists:delete(Element, List)) | |||||
end. | |||||
%%删除重复元素 [1,2,3,2,3,4] ---> [1,2,3,4] | |||||
remove_repeat(List) -> | |||||
remove_repeat(List, []). | |||||
remove_repeat([], Result) -> | |||||
Result; | |||||
remove_repeat([L | List], Result) -> | |||||
case lists:member(L, Result) of | |||||
false -> remove_repeat(List, Result ++ [L]); | |||||
true -> remove_repeat(List, Result) | |||||
end. | |||||
%%根据Key去查List中元素的Nth位相等的匹配到的元素Fun(Element)返回 | |||||
find_elements(Key, Nth, List, Fun) -> | |||||
InnerFun = fun(Element) -> | |||||
case element(Nth, Element) =:= Key of | |||||
true -> | |||||
{true, Fun(Element)}; | |||||
false -> | |||||
false | |||||
end | |||||
end, | |||||
lists:filtermap(InnerFun, List). | |||||
%%对list进行去重,排序 | |||||
%%Replicat 0不去重,1去重 | |||||
%%Sort 0不排序,1排序 | |||||
filter_list(List, Replicat, Sort) -> | |||||
if Replicat == 0 andalso Sort == 0 -> | |||||
List; | |||||
true -> | |||||
if Replicat == 1 andalso Sort == 1 -> | |||||
lists:usort(List); | |||||
true -> | |||||
if Sort == 1 -> | |||||
lists:sort(List); | |||||
true -> | |||||
lists:reverse(filter_replicat(List, [])) | |||||
end | |||||
end | |||||
end. | |||||
%%list去重 | |||||
filter_replicat([], List) -> | |||||
List; | |||||
filter_replicat([H | Rest], List) -> | |||||
Bool = lists:member(H, List), | |||||
List1 = | |||||
if Bool == true -> | |||||
[[] | List]; | |||||
true -> | |||||
[H | List] | |||||
end, | |||||
List2 = lists:filter(fun(T) -> T =/= [] end, List1), | |||||
filter_replicat(Rest, List2). | |||||
%%交集 | |||||
get_intersection_list(A, B) when is_list(A) andalso is_list(B) -> | |||||
lists:filter(fun(X) -> lists:member(X, A) end, B). | |||||
%%并集 | |||||
get_unite_list(A, B) when is_list(A) andalso is_list(B) -> | |||||
C = A ++ B, | |||||
lists:usort(C). | |||||
%%差集 | |||||
get_subtract_list(A, B) when is_list(A) andalso is_list(B) -> | |||||
Insection = get_intersection_list(A, B), | |||||
Unite = get_unite_list(A, B), | |||||
lists:filter(fun(X) -> lists:member(X, Insection) =:= false end, Unite). | |||||
merge(L) -> | |||||
merge(L, []). | |||||
merge([{Key, Value} | T], AccList) -> | |||||
NewAccList = | |||||
case lists:keyfind(Key, 1, AccList) of | |||||
false -> | |||||
[{Key, Value} | AccList]; | |||||
{Key, OldValue} -> | |||||
lists:keyreplace(Key, 1, AccList, {Key, OldValue + Value}) | |||||
end, | |||||
merge(T, NewAccList); | |||||
merge([], AccList) -> | |||||
AccList. |
@ -0,0 +1,74 @@ | |||||
-module(utMisc). | |||||
-compile([export_all, nowarn_export_all]). | |||||
%% 日志记录函数 | |||||
fileLog(T, F, A, Mod, Line) -> | |||||
{ok, Fl} = file:open("logs/error_log.txt", [write, append]), | |||||
Format = list_to_binary("#" ++ T ++ " ~s[~w:~w] " ++ F ++ "\r\n~n"), | |||||
{{Y, M, D}, {H, I, S}} = erlang:localtime(), | |||||
Date = list_to_binary([integer_to_list(Y), "-", integer_to_list(M), "-", integer_to_list(D), " ", integer_to_list(H), ":", integer_to_list(I), ":", integer_to_list(S)]), | |||||
io:format(Fl, unicode:characters_to_list(Format), [Date, Mod, Line] ++ A), | |||||
file:close(Fl). | |||||
compile_base_data(Table, ModName, IDPoses) -> | |||||
ModNameString = com_util:term_to_string(ModName), | |||||
HeadString = | |||||
"-module(" ++ ModNameString ++ "). | |||||
-compile(export_all). | |||||
", | |||||
BaseDataList = db_base:select_all(Table, "*", []), | |||||
ContentString = | |||||
lists:foldl(fun(BaseData0, PreString) -> | |||||
FunChange = | |||||
fun(Field) -> | |||||
if is_integer(Field) -> Field; | |||||
true -> | |||||
case com_util:bitstring_to_term(Field) of | |||||
undefined -> | |||||
Field; | |||||
Term -> | |||||
Term | |||||
end | |||||
end | |||||
end, | |||||
BaseData = [FunChange(Item) || Item <- BaseData0], | |||||
Base = list_to_tuple([Table | BaseData]), | |||||
BaseString = com_util:term_to_string(Base), | |||||
IDs = [element(Pos, Base) || Pos <- IDPoses], | |||||
IDList0 = lists:foldl(fun(ID, PreString2) -> | |||||
IDList = | |||||
if erlang:is_integer(ID) -> | |||||
integer_to_list(ID); | |||||
true -> | |||||
ID | |||||
end, | |||||
PreString2 ++ "," ++ IDList | |||||
end, [], IDs), | |||||
[_ | IDList] = IDList0, | |||||
PreString ++ | |||||
"get(" ++ | |||||
IDList ++ | |||||
") ->" ++ | |||||
BaseString ++ | |||||
"; | |||||
" | |||||
end | |||||
, "", BaseDataList), | |||||
_List0 = [",_" || _Pos <- IDPoses], | |||||
[_ | _List] = lists:flatten(_List0), | |||||
ErrorString = "get(" ++ _List ++ ") -> undefined. | |||||
", | |||||
FinalString = HeadString ++ ContentString ++ ErrorString, | |||||
%% ?PRINT("string=~s~n",[FinalString]), | |||||
try | |||||
{Mod, Code} = dynamic_compile:from_string(FinalString), | |||||
code:load_binary(Mod, ModNameString ++ ".erl", Code) | |||||
catch | |||||
Type:Error -> io:format("Error compiling (~p): ~p~n", [Type, Error]) | |||||
end, | |||||
ok. | |||||
@ -0,0 +1,129 @@ | |||||
-module(utRate). | |||||
-export([happen/1, | |||||
choose/1, | |||||
choose_n_uniq/2, | |||||
range/1, | |||||
collect/2, | |||||
select_n/2, %% return N unique items | |||||
collect_n/2, %% return N items, same item may appear than once | |||||
collect_one/1, | |||||
rand_list/1, | |||||
rand_list_n/2]). | |||||
-on_load(init/0). | |||||
init() -> | |||||
% mtwist:seed(time_utils:now()), | |||||
rand:seed(exs1024, erlang:timestamp()), | |||||
ok. | |||||
happen(Rate) when Rate == 1.0 -> true; | |||||
happen(Rate) when Rate < 1 -> | |||||
happen(Rate * 10000); | |||||
happen(Rate) -> | |||||
% RandomValue = mtwist:uniform(10000), | |||||
RandomValue = rand:uniform(10000), | |||||
compare(RandomValue, Rate). | |||||
%% Rates = [{RateA, ValueA}, {RateB, ValueB}, {default, DefaultValue}], | |||||
choose([{_Rate, Value}]) -> Value; | |||||
choose(Rates) -> | |||||
{_Rate, Value} = do_choose(Rates), | |||||
Value. | |||||
do_choose(Rates) -> | |||||
MaxValue = lists:foldl(fun({Rate, _}, Result) -> | |||||
Result + Rate | |||||
end, 0, Rates), | |||||
RandomValue = rand:uniform(MaxValue), | |||||
{Rate, Value} = choose(Rates, RandomValue, 0), | |||||
{Rate, Value}. | |||||
choose([{Rate, Value}|Rates], RandomValue, Offset) -> | |||||
case RandomValue =< Rate + Offset of | |||||
true -> {Rate, Value}; | |||||
false -> choose(Rates, RandomValue, Offset + Rate) | |||||
end. | |||||
choose_n_uniq(Rates, N) -> | |||||
choose_n_uniq(Rates, N, []). | |||||
choose_n_uniq(_Rates, 0, Result) -> Result; | |||||
choose_n_uniq(Rates, N, Result) -> | |||||
{Rate, Value} = do_choose(Rates), | |||||
NRates = lists:delete({Rate, Value}, Rates), | |||||
choose_n_uniq(NRates, N - 1, [Value|Result]). | |||||
%% Range = [{RateA, ValueA}, {RateB, ValueB}, {default, DefaultValue}], | |||||
%% select the happened Rate | |||||
range(Range) -> | |||||
% select(mtwist:uniform(10000), Range). | |||||
select(rand:uniform(10000), Range). | |||||
select(RandomValue, [{Rate, Value}|_Range]) when RandomValue =< Rate -> Value; | |||||
select(_RandomValue, [{default, Value}]) -> Value; | |||||
select(RandomValue, [{_Rate, _Value}|Range]) -> | |||||
select(RandomValue, Range). | |||||
compare(A, B) when A < B -> true; | |||||
compare(_A, _B) -> false. | |||||
%% Rand return N elements from Rates | |||||
%% Rates = [{RateA, ValueA}, {RateB, ValueB}, {RateC, ValueC}], | |||||
%% N =< length(Rates) | |||||
collect(Rates, N) -> | |||||
MaxValue = lists:foldl(fun({Rate, _}, Result) -> | |||||
Result + Rate | |||||
end, 0, Rates), | |||||
collect(N, Rates, MaxValue, []). | |||||
collect(0, _Rates, _MaxValue, Result) -> Result; | |||||
collect(N, Rates, MaxValue, Result) -> | |||||
% RandValue = mtwist:uniform(MaxValue), | |||||
RandValue = rand:uniform(MaxValue), | |||||
{Rate, Value} = choose(Rates, RandValue, 0), | |||||
NewRates = lists:delete({Rate, Value}, Rates), | |||||
collect(N - 1, NewRates, MaxValue - Rate, [Value|Result]). | |||||
%% Select N elements from List | |||||
select_n(List, N) -> select_n(List, N, []). | |||||
select_n([], _N, Result) -> Result; | |||||
select_n(_List, 0, Result) -> Result; | |||||
select_n(List, N, Result) -> | |||||
Len = length(List), | |||||
% Index = mtwist:uniform(Len) + 1, | |||||
Index = rand:uniform(Len), | |||||
Elem = lists:nth(Index, List), | |||||
select_n(lists:delete(Elem, List), N - 1, [Elem|Result]). | |||||
collect_n(List, N) -> collect_n(List, N, []). | |||||
collect_n(_List, 0, Result) -> Result; | |||||
collect_n(List, N, Result) -> | |||||
Len = length(List), | |||||
% Index = trunc(mtwist:uniform(Len)) + 1, | |||||
Index = rand:uniform(Len), | |||||
Elem = lists:nth(Index, List), | |||||
collect_n(List, N - 1, [Elem|Result]). | |||||
collect_one(List) -> | |||||
[Elem] = collect_n(List, 1), | |||||
Elem. | |||||
rand_list(List) -> | |||||
[X || {_, X} <- lists:sort([{rand:uniform(), N} || N <- List])]. | |||||
rand_list_n(List, Limit) -> | |||||
SortedList = lists:sort([{rand:uniform(), N} || N <- List]), | |||||
{Result, _} = misc_utils:each(fun({_Fator, Elem}, {Acc, Idx}) -> | |||||
if | |||||
Idx =:= Limit -> | |||||
{break, {Acc, Idx}}; | |||||
Idx < Limit -> | |||||
{[Elem|Acc], Idx + 1} | |||||
end | |||||
end, {[], 0}, SortedList), | |||||
Result. |
@ -0,0 +1,349 @@ | |||||
%%%------------------------------------------------------------------- | |||||
%%% @author root | |||||
%%% @copyright (C) 2015, <COMPANY> | |||||
%%% @doc | |||||
%%% | |||||
%%% @end | |||||
%%% Created : 30. 五月 2015 下午8:32 | |||||
%%%------------------------------------------------------------------- | |||||
%% Copyright (c) 2007 | |||||
%% Mats Cronqvist <mats.cronqvist@ericsson.com> | |||||
%% Chris Newcombe <chris.newcombe@gmail.com> | |||||
%% Jacob Vorreuter <jacob.vorreuter@gmail.com> | |||||
%% | |||||
%% 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. | |||||
%%%------------------------------------------------------------------- | |||||
%%% File : dynamic_compile.erl | |||||
%%% Description : | |||||
%%% Authors : Mats Cronqvist <mats.cronqvist@ericsson.com> | |||||
%%% Chris Newcombe <chris.newcombe@gmail.com> | |||||
%%% Jacob Vorreuter <jacob.vorreuter@gmail.com> | |||||
%%% TODO : | |||||
%%% - add support for limit include-file depth (and prevent circular references) | |||||
%%% prevent circular macro expansion set FILE correctly when -module() is found | |||||
%%% -include_lib support $ENVVAR in include filenames | |||||
%%% substitute-stringize (??MACRO) | |||||
%%% -undef/-ifdef/-ifndef/-else/-endif | |||||
%%% -file(File, Line) | |||||
%%%------------------------------------------------------------------- | |||||
-module(dynamic_compile). | |||||
%% API | |||||
-export([init/0, load_from_string/1, load_from_string/2]). | |||||
-export([from_string/1, from_string/2, get_forms_from_src/1]). | |||||
-import(lists, [reverse/1, keyreplace/4]). | |||||
-export([module_src/1, module_lt/1, modules/0]). | |||||
-record(module, {name = 0, loadtime = 0, src}). | |||||
init() -> | |||||
ets:new(?MODULE, [public, set, named_table, {keypos, #module.name}]), | |||||
ok. | |||||
module_src(Module) -> | |||||
init_if_notinit(), | |||||
case ets:lookup(?MODULE, Module) of | |||||
[] -> false; | |||||
[#module{src = Src}] -> Src | |||||
end. | |||||
module_lt(Module) -> | |||||
init_if_notinit(), | |||||
case ets:lookup(?MODULE, Module) of | |||||
[] -> false; | |||||
[#module{loadtime = Lt}] -> Lt | |||||
end. | |||||
modules() -> | |||||
init_if_notinit(), | |||||
ets:tab2list(?MODULE). | |||||
module_new(Module, Src) -> | |||||
ets:insert(?MODULE, #module{name = Module, src = Src}). | |||||
init_if_notinit() -> | |||||
case ets:info(?MODULE) of | |||||
undefined -> | |||||
init(); | |||||
_ -> | |||||
ignore | |||||
end. | |||||
%%==================================================================== | |||||
%% API | |||||
%%==================================================================== | |||||
%%-------------------------------------------------------------------- | |||||
%% Function: | |||||
%% Description: | |||||
%% Compile module from string and load into VM | |||||
%%-------------------------------------------------------------------- | |||||
load_from_string(CodeStr) -> | |||||
load_from_string(CodeStr, []). | |||||
load_from_string(CodeStr, CompileFormsOptions) -> | |||||
{Mod, Bin} = from_string(CodeStr, CompileFormsOptions), | |||||
code:load_binary(Mod, [], Bin), | |||||
ok. | |||||
%% module_new(Mod, CodeStr). | |||||
%%-------------------------------------------------------------------- | |||||
%% Function: | |||||
%% Description: | |||||
%% Returns a binary that can be used with | |||||
%% code:load_binary(Module, ModuleFilenameForInternalRecords, Binary). | |||||
%%-------------------------------------------------------------------- | |||||
from_string(CodeStr) -> | |||||
from_string(CodeStr, []). | |||||
% takes Options as for compile:forms/2 | |||||
from_string(CodeStr, CompileFormsOptions) -> | |||||
%% Initialise the macro dictionary with the default predefined macros, | |||||
%% (adapted from epp.erl:predef_macros/1 | |||||
Filename = "compiled_from_string", | |||||
%%Machine = list_to_atom(erlang:system_info(machine)), | |||||
Ms0 = dict:new(), | |||||
% Ms1 = dict:store('FILE', {[], "compiled_from_string"}, Ms0), | |||||
% Ms2 = dict:store('LINE', {[], 1}, Ms1), % actually we might add special code for this | |||||
% Ms3 = dict:store('MODULE', {[], undefined}, Ms2), | |||||
% Ms4 = dict:store('MODULE_STRING', {[], undefined}, Ms3), | |||||
% Ms5 = dict:store('MACHINE', {[], Machine}, Ms4), | |||||
% InitMD = dict:store(Machine, {[], true}, Ms5), | |||||
InitMD = Ms0, | |||||
%% From the docs for compile:forms: | |||||
%% When encountering an -include or -include_dir directive, the compiler searches for header files in the following directories: | |||||
%% 1. ".", the current working directory of the file server; | |||||
%% 2. the base name of the compiled file; | |||||
%% 3. the directories specified using the i option. The directory specified last is searched first. | |||||
%% In this case, #2 is meaningless. | |||||
IncludeSearchPath = ["." | reverse([Dir || {i, Dir} <- CompileFormsOptions])], | |||||
{RevForms, _OutMacroDict} = scan_and_parse(CodeStr, Filename, 1, [], InitMD, IncludeSearchPath), | |||||
Forms = [{attribute, 0, file, {"compiled_from_string", 0}} | reverse([{eof, 0} | RevForms])], | |||||
%% note: 'binary' is forced as an implicit option, whether it is provided or not. | |||||
case compile:forms(Forms, CompileFormsOptions) of | |||||
{ok, ModuleName, CompiledCodeBinary} when is_binary(CompiledCodeBinary) -> | |||||
{ModuleName, CompiledCodeBinary}; | |||||
{ok, ModuleName, CompiledCodeBinary, []} when is_binary(CompiledCodeBinary) -> % empty warnings list | |||||
{ModuleName, CompiledCodeBinary}; | |||||
{ok, _ModuleName, _CompiledCodeBinary, Warnings} -> | |||||
throw({?MODULE, warnings, Warnings}); | |||||
Other -> | |||||
throw({?MODULE, compile_forms, Other}) | |||||
end. | |||||
get_forms_from_src(Src) -> | |||||
case scan_and_parse(Src, none, 1, [], dict:new(), []) of | |||||
{RevForms, _} -> [{attribute, 0, file, {"compiled_from_string", 0}} | reverse([{eof, 0} | RevForms])] | |||||
end. | |||||
%%==================================================================== | |||||
%% Internal functions | |||||
%%==================================================================== | |||||
%%% Code from Mats Cronqvist | |||||
%%% See http://www.erlang.org/pipermail/erlang-questions/2007-March/025507.html | |||||
%%%## 'scan_and_parse' | |||||
%%% | |||||
%%% basically we call the OTP scanner and parser (erl_scan and | |||||
%%% erl_parse) line-by-line, but check each scanned line for (or | |||||
%%% definitions of) macros before parsing. | |||||
%% returns {ReverseForms, FinalMacroDict} | |||||
scan_and_parse([], _CurrFilename, _CurrLine, RevForms, MacroDict, _IncludeSearchPath) -> | |||||
{RevForms, MacroDict}; | |||||
scan_and_parse(RemainingText, CurrFilename, CurrLine, RevForms, MacroDict, IncludeSearchPath) -> | |||||
case scanner(RemainingText, CurrLine, MacroDict) of | |||||
{tokens, NLine, NRemainingText, Toks} -> | |||||
{ok, Form0} = erl_parse:parse_form(Toks), | |||||
{Form, Forms} = normalize_record(Form0), | |||||
scan_and_parse(NRemainingText, CurrFilename, NLine, [Form | Forms ++ RevForms], MacroDict, IncludeSearchPath); | |||||
{macro, NLine, NRemainingText, NMacroDict} -> | |||||
scan_and_parse(NRemainingText, CurrFilename, NLine, RevForms, NMacroDict, IncludeSearchPath); | |||||
{include, NLine, NRemainingText, IncludeFilename} -> | |||||
IncludeFileRemainingTextents = read_include_file(IncludeFilename, IncludeSearchPath), | |||||
%%io:format("include file ~p contents: ~n~p~nRemainingText = ~p~n", [IncludeFilename,IncludeFileRemainingTextents, RemainingText]), | |||||
%% Modify the FILE macro to reflect the filename | |||||
%%IncludeMacroDict = dict:store('FILE', {[],IncludeFilename}, MacroDict), | |||||
IncludeMacroDict = MacroDict, | |||||
%% Process the header file (inc. any nested header files) | |||||
{RevIncludeForms, IncludedMacroDict} = scan_and_parse(IncludeFileRemainingTextents, IncludeFilename, 1, [], IncludeMacroDict, IncludeSearchPath), | |||||
%io:format("include file results = ~p~n", [R]), | |||||
%% Restore the FILE macro in the NEW MacroDict (so we keep any macros defined in the header file) | |||||
%%NMacroDict = dict:store('FILE', {[],CurrFilename}, IncludedMacroDict), | |||||
NMacroDict = IncludedMacroDict, | |||||
%% Continue with the original file | |||||
scan_and_parse(NRemainingText, CurrFilename, NLine, RevIncludeForms ++ RevForms, NMacroDict, IncludeSearchPath); | |||||
done -> | |||||
scan_and_parse([], CurrFilename, CurrLine, RevForms, MacroDict, IncludeSearchPath) | |||||
end. | |||||
scanner(Text, Line, MacroDict) -> | |||||
case erl_scan:tokens([], Text, Line) of | |||||
{done, {ok, Toks, NLine}, LeftOverChars} -> | |||||
case pre_proc(Toks, MacroDict) of | |||||
{tokens, NToks} -> {tokens, NLine, LeftOverChars, NToks}; | |||||
{macro, NMacroDict} -> {macro, NLine, LeftOverChars, NMacroDict}; | |||||
{include, Filename} -> {include, NLine, LeftOverChars, Filename} | |||||
end; | |||||
{more, _Continuation} -> | |||||
%% This is supposed to mean "term is not yet complete" (i.e. a '.' has | |||||
%% not been reached yet). | |||||
%% However, for some bizarre reason we also get this if there is a comment after the final '.' in a file. | |||||
%% So we check to see if Text only consists of comments. | |||||
case is_only_comments(Text) of | |||||
true -> | |||||
done; | |||||
false -> | |||||
throw({incomplete_term, _Continuation, Text, Line}) | |||||
end | |||||
end. | |||||
is_only_comments(Text) -> is_only_comments(Text, not_in_comment). | |||||
is_only_comments([], _) -> true; | |||||
is_only_comments([$ | T], not_in_comment) -> | |||||
is_only_comments(T, not_in_comment); % skipping whitspace outside of comment | |||||
is_only_comments([$\t | T], not_in_comment) -> | |||||
is_only_comments(T, not_in_comment); % skipping whitspace outside of comment | |||||
is_only_comments([$\n | T], not_in_comment) -> | |||||
is_only_comments(T, not_in_comment); % skipping whitspace outside of comment | |||||
is_only_comments([$% | T], not_in_comment) -> is_only_comments(T, in_comment); % found start of a comment | |||||
is_only_comments(_, not_in_comment) -> false; | |||||
% found any significant char NOT in a comment | |||||
is_only_comments([$\n | T], in_comment) -> is_only_comments(T, not_in_comment); % found end of a comment | |||||
is_only_comments([_ | T], in_comment) -> is_only_comments(T, in_comment). % skipping over in-comment chars | |||||
%%%## 'pre-proc' | |||||
%%% | |||||
%%% have to implement a subset of the pre-processor, since epp insists | |||||
%%% on running on a file. | |||||
%%% only handles 2 cases; | |||||
%% -define(MACRO, something). | |||||
%% -define(MACRO(VAR1,VARN),{stuff,VAR1,more,stuff,VARN,extra,stuff}). | |||||
pre_proc([{'-', _}, {atom, _, define}, {'(', _}, {_, _, Name} | DefToks], MacroDict) -> | |||||
false = dict:is_key(Name, MacroDict), | |||||
case DefToks of | |||||
[{',', _} | Macro] -> | |||||
{macro, dict:store(Name, {[], macro_body_def(Macro, [])}, MacroDict)}; | |||||
[{'(', _} | Macro] -> | |||||
{macro, dict:store(Name, macro_params_body_def(Macro, []), MacroDict)} | |||||
end; | |||||
pre_proc([{'-', _}, {atom, _, include}, {'(', _}, {string, _, Filename}, {')', _}, {dot, _}], _MacroDict) -> | |||||
{include, Filename}; | |||||
pre_proc(Toks, MacroDict) -> | |||||
{tokens, subst_macros(Toks, MacroDict)}. | |||||
macro_params_body_def([{')', _}, {',', _} | Toks], RevParams) -> | |||||
{reverse(RevParams), macro_body_def(Toks, [])}; | |||||
macro_params_body_def([{var, _, Param} | Toks], RevParams) -> | |||||
macro_params_body_def(Toks, [Param | RevParams]); | |||||
macro_params_body_def([{',', _}, {var, _, Param} | Toks], RevParams) -> | |||||
macro_params_body_def(Toks, [Param | RevParams]). | |||||
macro_body_def([{')', _}, {dot, _}], RevMacroBodyToks) -> | |||||
reverse(RevMacroBodyToks); | |||||
macro_body_def([Tok | Toks], RevMacroBodyToks) -> | |||||
macro_body_def(Toks, [Tok | RevMacroBodyToks]). | |||||
subst_macros(Toks, MacroDict) -> | |||||
reverse(subst_macros_rev(Toks, MacroDict, [])). | |||||
%% returns a reversed list of tokes | |||||
subst_macros_rev([{'?', _}, {_, LineNum, 'LINE'} | Toks], MacroDict, RevOutToks) -> | |||||
%% special-case for ?LINE, to avoid creating a new MacroDict for every line in the source file | |||||
subst_macros_rev(Toks, MacroDict, [{integer, LineNum, LineNum}] ++ RevOutToks); | |||||
subst_macros_rev([{'?', _}, {_, _, Name}, {'(', _} = Paren | Toks], MacroDict, RevOutToks) -> | |||||
case dict:fetch(Name, MacroDict) of | |||||
{[], MacroValue} -> | |||||
%% This macro does not have any vars, so ignore the fact that the invocation is followed by "(...stuff" | |||||
%% Recursively expand any macro calls inside this macro's value | |||||
%% TODO: avoid infinite expansion due to circular references (even indirect ones) | |||||
RevExpandedOtherMacrosToks = subst_macros_rev(MacroValue, MacroDict, []), | |||||
subst_macros_rev([Paren | Toks], MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks); | |||||
ParamsAndBody -> | |||||
%% This macro does have vars. | |||||
%% Collect all of the passe arguments, in an ordered list | |||||
{NToks, Arguments} = subst_macros_get_args(Toks, []), | |||||
%% Expand the varibles | |||||
ExpandedParamsToks = subst_macros_subst_args_for_vars(ParamsAndBody, Arguments), | |||||
%% Recursively expand any macro calls inside this macro's value | |||||
%% TODO: avoid infinite expansion due to circular references (even indirect ones) | |||||
RevExpandedOtherMacrosToks = subst_macros_rev(ExpandedParamsToks, MacroDict, []), | |||||
subst_macros_rev(NToks, MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks) | |||||
end; | |||||
subst_macros_rev([{'?', _}, {_, _, Name} | Toks], MacroDict, RevOutToks) -> | |||||
%% This macro invocation does not have arguments. | |||||
%% Therefore the definition should not have parameters | |||||
{[], MacroValue} = dict:fetch(Name, MacroDict), | |||||
%% Recursively expand any macro calls inside this macro's value | |||||
%% TODO: avoid infinite expansion due to circular references (even indirect ones) | |||||
RevExpandedOtherMacrosToks = subst_macros_rev(MacroValue, MacroDict, []), | |||||
subst_macros_rev(Toks, MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks); | |||||
subst_macros_rev([Tok | Toks], MacroDict, RevOutToks) -> | |||||
subst_macros_rev(Toks, MacroDict, [Tok | RevOutToks]); | |||||
subst_macros_rev([], _MacroDict, RevOutToks) -> RevOutToks. | |||||
subst_macros_get_args([{')', _} | Toks], RevArgs) -> | |||||
{Toks, reverse(RevArgs)}; | |||||
subst_macros_get_args([{',', _}, {var, _, ArgName} | Toks], RevArgs) -> | |||||
subst_macros_get_args(Toks, [ArgName | RevArgs]); | |||||
subst_macros_get_args([{var, _, ArgName} | Toks], RevArgs) -> | |||||
subst_macros_get_args(Toks, [ArgName | RevArgs]). | |||||
subst_macros_subst_args_for_vars({[], BodyToks}, []) -> | |||||
BodyToks; | |||||
subst_macros_subst_args_for_vars({[Param | Params], BodyToks}, [Arg | Args]) -> | |||||
NBodyToks = keyreplace(Param, 3, BodyToks, {var, 1, Arg}), | |||||
subst_macros_subst_args_for_vars({Params, NBodyToks}, Args). | |||||
read_include_file(Filename, IncludeSearchPath) -> | |||||
case file:path_open(IncludeSearchPath, Filename, [read, raw, binary]) of | |||||
{ok, IoDevice, FullName} -> | |||||
{ok, Data} = file:read(IoDevice, filelib:file_size(FullName)), | |||||
file:close(IoDevice), | |||||
binary_to_list(Data); | |||||
{error, Reason} -> | |||||
throw({failed_to_read_include_file, Reason, Filename, IncludeSearchPath}) | |||||
end. | |||||
normalize_record({attribute, La, record, {Record, Fields}} = Form) -> | |||||
case epp:normalize_typed_record_fields(Fields) of | |||||
{typed, NewFields} -> | |||||
{{attribute, La, record, {Record, NewFields}}, | |||||
[{attribute, La, type, | |||||
{{record, Record}, Fields, []}}]}; | |||||
not_typed -> | |||||
{Form, []} | |||||
end; | |||||
normalize_record(Form) -> | |||||
{Form, []}. |
@ -0,0 +1,85 @@ | |||||
-module(dynamic_kvs). | |||||
-define(boot_type_supervisor, supervisor). | |||||
-define(boot_type_worker, worker). | |||||
-define(boot_type_simple_worker, simple_worker). | |||||
-type boot_type() :: ?boot_type_simple_worker | ?boot_type_supervisor | ?boot_type_worker. | |||||
-define(MATH_INT32_MAX, 4294967296). | |||||
-record(boot, {module :: atom(), type :: boot_type(), hasname = true :: boolean(), params :: any()}). | |||||
-export([start/1, start/0, get_name/1, init/1, handle_call/3, new/1, new/2]). | |||||
%% API | |||||
-export([set/3]). | |||||
-record(dynamic_kvs, { | |||||
modules = [] :: [{Mod :: module(), [{Key :: atom(), Val :: any()}]}] | |||||
}). | |||||
start(FatherPID) -> | |||||
boot:start_child(FatherPID, #boot{module = ?MODULE, params = {}, type = ?boot_type_worker}). | |||||
start() -> | |||||
boot:start(#boot{module = ?MODULE, params = {}, type = ?boot_type_worker}). | |||||
get_name(_) -> | |||||
?MODULE. | |||||
init(_) -> | |||||
{ok, #dynamic_kvs{modules = []}}. | |||||
new(ModuleName) -> | |||||
new(ModuleName, []). | |||||
new(ModuleName, Kvs) -> | |||||
boot:call(?MODULE, {new, ModuleName, Kvs}). | |||||
set(ModuleName, Key, Val) -> | |||||
boot:call(?MODULE, {set, ModuleName, Key, Val}). | |||||
handle_call({new, ModuleName, Kvs}, _, DynamicKvs) -> | |||||
gen(ModuleName, Kvs), | |||||
NewDynamicKvs = | |||||
case lists:keyfind(ModuleName, 1, DynamicKvs#dynamic_kvs.modules) of | |||||
false -> | |||||
DynamicKvs#dynamic_kvs{modules = [{ModuleName, Kvs} | DynamicKvs#dynamic_kvs.modules]}; | |||||
_ -> | |||||
DynamicKvs | |||||
end, | |||||
{reply, ok, NewDynamicKvs}; | |||||
handle_call({set, ModuleName, Key, Val}, _, DynamicKvs) -> | |||||
case lists:keytake(ModuleName, 1, DynamicKvs#dynamic_kvs.modules) of | |||||
false -> | |||||
{reply, {error, not_find_module}, DynamicKvs}; | |||||
{value, {_, Kvs}, RemainMods} -> | |||||
NewKvs = com_lists:keyreplace(Key, 1, Kvs, {Key, Val}), | |||||
gen(ModuleName, NewKvs), | |||||
{reply, ok, DynamicKvs#dynamic_kvs{modules = [{ModuleName, NewKvs} | RemainMods]}} | |||||
end. | |||||
concat(List) -> | |||||
lists:concat(List). | |||||
gen(ModuleName, Kvs) -> | |||||
Meta = meta:new(ModuleName), | |||||
Fun = | |||||
fun({K, V}, MetaTemp) -> | |||||
FunStr = concat([K, "() ->\n\t", com_util:term_to_string(V), ".\n"]), | |||||
case meta:add_func(MetaTemp, FunStr) of | |||||
{ok, MetaTemp2} -> MetaTemp2; | |||||
Error -> | |||||
io:format("module(~p) define func(~s) error ~p~n", [ModuleName, FunStr, Error]), | |||||
exit(Error) | |||||
end | |||||
end, | |||||
FinalMeta = lists:foldl(Fun, Meta, Kvs), | |||||
FinalMeta2 = meta:add_func(FinalMeta, concat(["set(Key, Val) ->\n\tdynamic_kvs:set(", ModuleName, ", Key, Val).\n"])), | |||||
case meta:compile(FinalMeta2) of | |||||
{error, Error} -> | |||||
io:format("error ~p", [Error]), | |||||
ok; | |||||
ok -> | |||||
ok | |||||
end. |
@ -0,0 +1,29 @@ | |||||
-module(meta_session). | |||||
-include("meta.hrl"). | |||||
%% parse transform 'meta_session' | |||||
-export([parse_transform/2, funstring_get/1]). | |||||
parse_transform(Forms, _Options) -> | |||||
case meta:new_from_forms(Forms) of | |||||
{ok, Meta} -> | |||||
Module = meta:get_modulename(Meta), | |||||
{ok, MetaGet} = meta:add_func(Meta, funstring_get(Module)), | |||||
{ok, MetaSet} = meta:add_func(MetaGet, funstring_set(Module)), | |||||
{ok, MetaDel} = meta:add_func(MetaSet, funstring_del(Module)), | |||||
MetaFinal = MetaDel, | |||||
meta:to_forms(MetaFinal); | |||||
Error -> | |||||
io:format("parse_transform error-----------> ~p ~n", [Error]), | |||||
Forms | |||||
end. | |||||
funstring_get(Module) -> | |||||
"get(Key, Default) -> com_util:dic_get({" ++ Module ++ ",Key},Default).". | |||||
funstring_set(Module) -> | |||||
"set(Key, Value) -> com_util:dic_set({" ++ Module ++ ",Key},Value).". | |||||
funstring_del(Module) -> | |||||
"del(Key) -> com_util:dic_erase({" ++ Module ++ ",Key}).". |
@ -0,0 +1,186 @@ | |||||
%% The MIT License (MIT) | |||||
%% | |||||
%% Copyright (c) 2014-2024 | |||||
%% Savin Max <mafei.198@gmail.com> | |||||
%% | |||||
%% 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. | |||||
%% | |||||
%% @doc Erlang module for automatically reloading modified modules | |||||
%% during development. | |||||
-module(reloader). | |||||
-include_lib("kernel/include/file.hrl"). | |||||
-behaviour(gen_server). | |||||
-export([start/0, start_link/0]). | |||||
-export([register_after_reload/1]). | |||||
-export([stop/0]). | |||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). | |||||
-export([all_changed/0]). | |||||
-export([is_changed/1]). | |||||
-export([reload_modules/1]). | |||||
-record(state, {last, | |||||
tref, | |||||
after_reload_callback}). | |||||
%% External API | |||||
%% @spec start() -> ServerRet | |||||
%% @doc Start the reloader. | |||||
start() -> | |||||
gen_server:start({local, ?MODULE}, ?MODULE, [], []). | |||||
%% @spec start_link() -> ServerRet | |||||
%% @doc Start the reloader. | |||||
start_link() -> | |||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). | |||||
%% @spec stop() -> ok | |||||
%% @doc Stop the reloader. | |||||
stop() -> | |||||
gen_server:call(?MODULE, stop). | |||||
register_after_reload(Fun) -> | |||||
gen_server:call(?MODULE, {register_after_reload, Fun}). | |||||
%% gen_server callbacks | |||||
%% @spec init([]) -> {ok, State} | |||||
%% @doc gen_server init, opens the server in an initial state. | |||||
init([]) -> | |||||
{ok, TRef} = timer:send_interval(timer:seconds(1), doit), | |||||
{ok, #state{last = stamp(), tref = TRef}}. | |||||
%% @spec handle_call(Args, From, State) -> tuple() | |||||
%% @doc gen_server callback. | |||||
handle_call(stop, _From, State) -> | |||||
{stop, shutdown, stopped, State}; | |||||
handle_call({register_after_reload, Fun}, _From, State) -> | |||||
{reply, ok, State#state{after_reload_callback = Fun}}; | |||||
handle_call(_Req, _From, State) -> | |||||
{reply, {error, badrequest}, State}. | |||||
%% @spec handle_cast(Cast, State) -> tuple() | |||||
%% @doc gen_server callback. | |||||
handle_cast(_Req, State) -> | |||||
{noreply, State}. | |||||
%% @spec handle_info(Info, State) -> tuple() | |||||
%% @doc gen_server callback. | |||||
handle_info(doit, #state{after_reload_callback = Fun} = State) -> | |||||
Now = stamp(), | |||||
ReloadedModules = doit(State#state.last, Now), | |||||
if | |||||
ReloadedModules =/= [] andalso Fun =/= undefined -> | |||||
Fun(ReloadedModules); | |||||
true -> ok | |||||
end, | |||||
{noreply, State#state{last = Now}}; | |||||
handle_info(_Info, State) -> | |||||
{noreply, State}. | |||||
%% @spec terminate(Reason, State) -> ok | |||||
%% @doc gen_server termination callback. | |||||
terminate(_Reason, State) -> | |||||
{ok, cancel} = timer:cancel(State#state.tref), | |||||
ok. | |||||
%% @spec code_change(_OldVsn, State, _Extra) -> State | |||||
%% @doc gen_server code_change callback (trivial). | |||||
code_change(_Vsn, State, _Extra) -> | |||||
{ok, State}. | |||||
%% @spec reload_modules([atom()]) -> [{module, atom()} | {error, term()}] | |||||
%% @doc code:purge/1 and code:load_file/1 the given list of modules in order, | |||||
%% return the results of code:load_file/1. | |||||
reload_modules(Modules) -> | |||||
[begin code:purge(M), code:load_file(M) end || M <- Modules]. | |||||
%% @spec all_changed() -> [atom()] | |||||
%% @doc Return a list of beam modules that have changed. | |||||
all_changed() -> | |||||
[M || {M, Fn} <- code:all_loaded(), is_list(Fn), is_changed(M)]. | |||||
%% @spec is_changed(atom()) -> boolean() | |||||
%% @doc true if the loaded module is a beam with a vsn attribute | |||||
%% and does not match the on-disk beam file, returns false otherwise. | |||||
is_changed(M) -> | |||||
try | |||||
module_vsn(M:module_info()) =/= module_vsn(code:get_object_code(M)) | |||||
catch _:_ -> | |||||
false | |||||
end. | |||||
%% Internal API | |||||
module_vsn({M, Beam, _Fn}) -> | |||||
{ok, {M, Vsn}} = beam_lib:version(Beam), | |||||
Vsn; | |||||
module_vsn(L) when is_list(L) -> | |||||
{_, Attrs} = lists:keyfind(attributes, 1, L), | |||||
{_, Vsn} = lists:keyfind(vsn, 1, Attrs), | |||||
Vsn. | |||||
doit(From, To) -> | |||||
lists:foldl(fun({Module, Filename}, Acc) -> | |||||
case is_list(Filename) of | |||||
false -> Acc; | |||||
true -> | |||||
case file:read_file_info(Filename) of | |||||
{ok, #file_info{mtime = Mtime}} when Mtime >= From, Mtime < To -> | |||||
case reload(Module) of | |||||
error -> Acc; | |||||
_ -> [Module|Acc] | |||||
end; | |||||
{ok, _} -> | |||||
% unmodified; | |||||
Acc; | |||||
{error, enoent} -> | |||||
%% The Erlang compiler deletes existing .beam files if | |||||
%% recompiling fails. Maybe it's worth spitting out a | |||||
%% warning here, but I'd want to limit it to just once. | |||||
% gone; | |||||
Acc; | |||||
{error, Reason} -> | |||||
io:format("Error reading ~s's file info: ~p~n", | |||||
[Filename, Reason]), | |||||
% error | |||||
Acc | |||||
end | |||||
end | |||||
end, [], code:all_loaded()). | |||||
reload(Module) -> | |||||
io:format("Reloading ~p ...", [Module]), | |||||
code:purge(Module), | |||||
case code:load_file(Module) of | |||||
{module, Module} -> | |||||
io:format(" ok.~n"), | |||||
reload; | |||||
{error, Reason} -> | |||||
io:format(" fail: ~p.~n", [Reason]), | |||||
error | |||||
end. | |||||
stamp() -> | |||||
erlang:localtime(). |
@ -0,0 +1,159 @@ | |||||
-module(http). | |||||
-behaviour(gen_server). | |||||
-define(JSON_CONTENT, {"Content-Type", "application/json"}). | |||||
-define(HTTP_CLIENT_TIMEOUT, 10000). | |||||
%% Connection pool size: 100, Each connection queue size: 100 | |||||
-define(HTTP_CLIENT_OPTIONS, [{max_sessions, 100}, {max_pipeline_size, 100}]). | |||||
-define(SERVER, ?MODULE). | |||||
-define(RESEND_AFTER_DELAY, 20000). % 20 secs | |||||
-define(MAX_RETRY, 13). | |||||
%% API | |||||
-export([start_link/0, | |||||
request/3, | |||||
async_request/3, | |||||
queue_request/3, | |||||
queue_request/4]). | |||||
%% gen_server callbacks | |||||
-export([init/1, | |||||
handle_call/3, | |||||
handle_cast/2, | |||||
handle_info/2, | |||||
terminate/2, | |||||
code_change/3]). | |||||
-record(state, {}). | |||||
request(Url, Method, Params) -> | |||||
request(Url, Method, Params, false). | |||||
async_request(Url, Method, Params) -> | |||||
request(Url, Method, Params, true). | |||||
request(Url, Method, Params, IsAsync) -> | |||||
Options = case IsAsync of | |||||
true -> ?HTTP_CLIENT_OPTIONS ++ [{stream_to, self()}]; | |||||
false -> ?HTTP_CLIENT_OPTIONS | |||||
end, | |||||
JsonString = case Params of | |||||
[] -> Params; | |||||
_ -> jsx:encode(Params) | |||||
end, | |||||
ibrowse:send_req(Url, [?JSON_CONTENT], Method, JsonString, Options, ?HTTP_CLIENT_TIMEOUT). | |||||
%%%=================================================================== | |||||
%%% API | |||||
%%%=================================================================== | |||||
start_link() -> | |||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). | |||||
queue_request(Url, Method, Params) -> | |||||
gen_server:cast(?SERVER, {queue_request, Url, Method, Params, undefined}). | |||||
queue_request(Url, Method, Params, CallBack) -> | |||||
gen_server:cast(?SERVER, {queue_request, Url, Method, Params, CallBack}). | |||||
%%%=================================================================== | |||||
%%% gen_server callbacks | |||||
%%%=================================================================== | |||||
-record(request, {uuid, url, method, params, retry}). | |||||
init([]) -> | |||||
erlang:send_after(?RESEND_AFTER_DELAY, self(), resend_all_requests), | |||||
{ok, #state{}}. | |||||
handle_call(_Request, _From, State) -> | |||||
Reply = ok, | |||||
{reply, Reply, State}. | |||||
handle_cast({queue_request, Url, Method, Params, CallBack}, State) -> | |||||
Uuid = uuid_factory:gen(), | |||||
Request = #request{uuid = Uuid, | |||||
url = Url, | |||||
method = Method, | |||||
params = Params, | |||||
retry = 0}, | |||||
shared_data:write(Request), | |||||
if | |||||
CallBack =:= undefined -> ok; | |||||
true -> game_counter:set({request, Uuid}, CallBack) | |||||
end, | |||||
do_send_request(Request), | |||||
{noreply, State}. | |||||
handle_info(resend_all_requests, State) -> | |||||
ets:foldl(fun(Request, _) -> | |||||
do_send_request(Request) | |||||
end, unused, request), | |||||
{noreply, State}; | |||||
handle_info({resend_requst, Uuid}, State) -> | |||||
case shared_data:find(request, Uuid) of | |||||
undefined -> ok; | |||||
Request -> do_send_request(Request) | |||||
end, | |||||
{noreply, State}; | |||||
handle_info({ibrowse_async_headers, ReqId, Code, _Headers}, State) -> | |||||
%% error_logger:info_msg("handle_info:Response:~p, ~p~n", [ReqId, Code]), | |||||
put({code, ReqId}, Code), | |||||
{noreply, State}; | |||||
handle_info({ibrowse_async_response, ReqId, Response}, State) -> | |||||
Code = get({code, ReqId}), | |||||
Uuid = get({req_id, ReqId}), | |||||
%% error_logger:info_msg("handle_info:Response: ~p~n", [Response]), | |||||
if | |||||
Code =:= "200" -> | |||||
case game_counter:get({request, Uuid}) of | |||||
undefined -> ok; | |||||
CallBack -> | |||||
CallBack(Response), | |||||
game_counter:del({request, Uuid}) | |||||
end, | |||||
shared_data:find(request, Uuid), | |||||
erase({req_id, ReqId}), | |||||
erase({code, ReqId}); | |||||
true -> | |||||
case shared_data:find(request, Uuid) of | |||||
undefined -> ok; | |||||
Request -> | |||||
NewRequest = Request#request{retry = Request#request.retry + 1}, | |||||
Retry = NewRequest#request.retry, | |||||
if | |||||
Retry > ?MAX_RETRY -> | |||||
shared_data:delete(request, Uuid); | |||||
true -> | |||||
shared_data:write(NewRequest), | |||||
Delay = math:pow(2, Retry) * ?RESEND_AFTER_DELAY, | |||||
logger:info("Http Resend Dealy: ~p, Uuid: ~p~n", [Delay, Uuid]), | |||||
erlang:send_after(trunc(Delay), self(), {resend_requst, Uuid}) | |||||
end | |||||
end | |||||
end, | |||||
{noreply, State}; | |||||
handle_info({ibrowse_async_response_end, _ReqId}, State) -> | |||||
{noreply, State}; | |||||
handle_info(Msg, State) -> | |||||
error_logger:info_msg("handle_info Msg: ~p~n", [Msg]), | |||||
{noreply, State}. | |||||
terminate(_Reason, _State) -> | |||||
ok. | |||||
code_change(_OldVsn, State, _Extra) -> | |||||
{ok, State}. | |||||
%%%=================================================================== | |||||
%%% Internal functions | |||||
%%%=================================================================== | |||||
do_send_request(Request) -> | |||||
%% error_logger:info_msg("do_send_request: ~p", [Request]), | |||||
{ibrowse_req_id, _ReqId} = async_request(Request#request.url, | |||||
Request#request.method, | |||||
Request#request.params), | |||||
%% 临时处理 | |||||
shared_data:delete(request, Request#request.uuid). | |||||
%% put({req_id, ReqId}, Request#request.uuid). | |||||
%% 临时处理 | |||||
@ -0,0 +1,344 @@ | |||||
%%====================================================================== | |||||
%% | |||||
%% Leo Commons | |||||
%% | |||||
%% Copyright (c) 2012-2017 Rakuten, Inc. | |||||
%% | |||||
%% This file is provided to you under the Apache License, | |||||
%% Version 2.0 (the "License"); you may not use this file | |||||
%% except in compliance with the License. You may obtain | |||||
%% a copy of the License at | |||||
%% | |||||
%% http://www.apache.org/licenses/LICENSE-2.0 | |||||
%% | |||||
%% Unless required by applicable law or agreed to in writing, | |||||
%% software distributed under the License is distributed on an | |||||
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |||||
%% KIND, either express or implied. See the License for the | |||||
%% specific language governing permissions and limitations | |||||
%% under the License. | |||||
%% | |||||
%% --------------------------------------------------------------------- | |||||
%% leo_http - Utils for HTTP/S3-API | |||||
%% | |||||
%% @doc leo_http is utilities for HTTP and S3-API | |||||
%% @reference https://github.com/leo-project/leo_commons/blob/master/src/leo_http.erl | |||||
%% @end | |||||
%%====================================================================== | |||||
-module(leo_http). | |||||
-author('Yoshiyuki Kanno'). | |||||
-author('Yosuke Hara'). | |||||
-export([key/2, key/3, | |||||
get_headers/2, get_headers/3, get_amz_headers/1, | |||||
get_headers4cow/2, get_headers4cow/3, get_amz_headers4cow/1, | |||||
rfc1123_date/1, web_date/1, url_encode/2 | |||||
]). | |||||
-include_lib("eunit/include/eunit.hrl"). | |||||
-define(S3_DEFAULT_ENDPOINT, <<"s3.amazonaws.com">>). | |||||
-define(SLASH_BIN, <<"/">>). | |||||
-define(EMPYT_BIN, <<>>). | |||||
%% @doc Retrieve a filename(KEY) from Host and Path. | |||||
%% | |||||
-spec(key(Host, Path) -> | |||||
{binary(), binary()} when Host::binary(), | |||||
Path::binary()). | |||||
key(Host, Path) -> | |||||
key([?S3_DEFAULT_ENDPOINT], Host, Path). | |||||
%% @doc Retrieve a filename(KEY) from Host and Path. | |||||
%% | |||||
-spec(key(EndPoints, Host, Path) -> | |||||
{binary(), binary()} when EndPoints::[binary()], | |||||
Host::binary(), | |||||
Path::binary()). | |||||
key(EndPoints, Host, Path) -> | |||||
EmptyBin = <<>>, | |||||
EndPoint = case lists:foldl( | |||||
fun(E, Acc) -> | |||||
case binary:match(Host, E) of | |||||
nomatch -> | |||||
Acc; | |||||
{_,_} -> | |||||
case (byte_size(Acc) < byte_size(E)) of | |||||
true -> E; | |||||
false -> Acc | |||||
end | |||||
end | |||||
end, EmptyBin, EndPoints) of | |||||
EmptyBin -> | |||||
[]; | |||||
Ret -> | |||||
Ret | |||||
end, | |||||
key_1(EndPoint, Host, Path). | |||||
%% @doc Retrieve a filename(KEY) from Host and Path. | |||||
%% @private | |||||
key_1(EndPoint, Host, Path) -> | |||||
Index = case EndPoint of | |||||
[] -> | |||||
0; | |||||
_ -> | |||||
case binary:match(Host, EndPoint) of | |||||
nomatch -> | |||||
0; | |||||
{Pos, _} -> | |||||
Pos + 1 | |||||
end | |||||
end, | |||||
key_2(Index, Host, Path). | |||||
%% @doc "S3-Bucket" is equal to the host. | |||||
%% @private | |||||
key_2(0, Host, Path) -> | |||||
case binary:match(Path, [?SLASH_BIN]) of | |||||
nomatch -> | |||||
{Host, <<Host/binary, ?SLASH_BIN/binary>>}; | |||||
_ -> | |||||
[_, Top|_] = binary:split(Path, [?SLASH_BIN], [global]), | |||||
case Top of | |||||
Host -> | |||||
Path2 = binary:part(Path, {1, byte_size(Path) -1}), | |||||
case binary:split(Path2, [?SLASH_BIN], [global]) of | |||||
[] -> | |||||
{?EMPYT_BIN, Path2}; | |||||
[Bucket|_] -> | |||||
{Bucket, Path2} | |||||
end; | |||||
_ -> | |||||
{Host, <<Host/binary, Path/binary>>} | |||||
end | |||||
end; | |||||
%% @doc "S3-Bucket" is included in the path | |||||
%% @private | |||||
key_2(1,_Host, ?SLASH_BIN) -> | |||||
{?EMPYT_BIN, ?SLASH_BIN}; | |||||
key_2(1,_Host, Path) -> | |||||
case binary:match(Path, [?SLASH_BIN]) of | |||||
nomatch -> | |||||
{?EMPYT_BIN, ?SLASH_BIN}; | |||||
_ -> | |||||
Path2 = binary:part(Path, {1, byte_size(Path) -1}), | |||||
case binary:split(Path2, [?SLASH_BIN], [global]) of | |||||
[] -> | |||||
{?EMPYT_BIN, Path2}; | |||||
[Bucket|_] -> | |||||
{Bucket, Path2} | |||||
end | |||||
end; | |||||
%% @doc "S3-Bucket" is included in the host | |||||
%% @private | |||||
key_2(Index, Host, Path) -> | |||||
Bucket = binary:part(Host, {0, Index -2}), | |||||
{Bucket, <<Bucket/binary, Path/binary>>}. | |||||
%% @doc Retrieve AMZ-S3-related headers | |||||
%% assume that TreeHeaders is generated by mochiweb_header | |||||
%% | |||||
-spec(get_headers(TreeHeaders, FilterFun) -> | |||||
list() when TreeHeaders::gb_trees:tree(), | |||||
FilterFun::function()). | |||||
get_headers(TreeHeaders, FilterFun) when is_function(FilterFun) -> | |||||
Iter = gb_trees:iterator(TreeHeaders), | |||||
get_headers(Iter, FilterFun, []). | |||||
%% @doc Retrieve AMZ-S3-related headers | |||||
%% assume that TreeHeaders is generated by mochiweb_header | |||||
%% | |||||
-spec(get_headers(TreeHeaders, FilterFun, Acc) -> | |||||
list() when TreeHeaders::gb_trees:iter(), | |||||
FilterFun::function(), | |||||
Acc::[any()] | |||||
). | |||||
get_headers(Iter, FilterFun, Acc) -> | |||||
case gb_trees:next(Iter) of | |||||
none -> | |||||
Acc; | |||||
{Key, Val, Iter2} -> | |||||
case FilterFun(Key) of | |||||
true -> get_headers(Iter2, FilterFun, [Val|Acc]); | |||||
false -> get_headers(Iter2, FilterFun, Acc) | |||||
end | |||||
end. | |||||
%% @doc Retrieve headers for cowboy | |||||
%% | |||||
-spec(get_headers4cow(Headers, FilterFun) -> | |||||
list() when Headers::[any()], | |||||
FilterFun::function()). | |||||
get_headers4cow(Headers, FilterFun) when is_function(FilterFun) -> | |||||
get_headers4cow(Headers, FilterFun, []). | |||||
%% @doc Retrieve headers for Cowboy | |||||
%% | |||||
-spec(get_headers4cow(Headers, FilterFun, Acc) -> | |||||
[{string(), string()}] when Headers::[{binary(), binary()}], | |||||
FilterFun::function(), | |||||
Acc::[any()]). | |||||
get_headers4cow([], _FilterFun, Acc) -> | |||||
Acc; | |||||
get_headers4cow([{K, V}|Rest], FilterFun, Acc) when is_binary(K) -> | |||||
case FilterFun(K) of | |||||
true -> | |||||
get_headers4cow(Rest, FilterFun, | |||||
[{binary_to_list(K), binary_to_list(V)}|Acc]); | |||||
false -> | |||||
get_headers4cow(Rest, FilterFun, Acc) | |||||
end; | |||||
get_headers4cow([_|Rest], FilterFun, Acc) -> | |||||
get_headers4cow(Rest, FilterFun, Acc). | |||||
%% @doc Retrieve AMZ-S3-related headers | |||||
%% | |||||
-spec(get_amz_headers(TreeHeaders) -> | |||||
list() when TreeHeaders::gb_trees:tree()). | |||||
get_amz_headers(TreeHeaders) -> | |||||
get_headers(TreeHeaders, fun is_amz_header/1). | |||||
%% @doc Retrieve AMZ-S3-related headers for Cowboy | |||||
%% | |||||
-spec(get_amz_headers4cow(ListHeaders) -> | |||||
[{string(), string()}] when ListHeaders::[{binary(), binary()}]). | |||||
get_amz_headers4cow(ListHeaders) -> | |||||
get_headers4cow(ListHeaders, fun is_amz_header/1). | |||||
%% @doc Retrieve RFC-1123 formated data | |||||
%% | |||||
-spec(rfc1123_date(DateSec) -> | |||||
string() when DateSec::integer()). | |||||
rfc1123_date(DateSec) -> | |||||
%% NOTE: | |||||
%% Don't use http_util:rfc1123 on R14B*. | |||||
%% In this func, There is no error handling for `local_time_to_universe` | |||||
%% So badmatch could occur. This result in invoking huge context switched. | |||||
{{Y,M,D},{H,MI,S}} = calendar:gregorian_seconds_to_datetime(DateSec), | |||||
Mon = month(M), | |||||
W = weekday(Y,M,D), | |||||
lists:flatten( | |||||
io_lib:format("~3s, ~2.10.0B ~3s ~4.10B ~2.10.0B:~2.10.0B:~2.10.0B GMT", | |||||
[W, D, Mon, Y, H, MI, S])). | |||||
%% @doc Convert gregorian seconds to date formated data( YYYY-MM-DDTHH:MI:SS000Z ) | |||||
%% | |||||
-spec(web_date(GregSec) -> | |||||
string() when GregSec::integer()). | |||||
web_date(GregSec) when is_integer(GregSec) -> | |||||
{{Y,M,D},{H,MI,S}} = calendar:gregorian_seconds_to_datetime(GregSec), | |||||
lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0B.000Z",[Y,M,D,H,MI,S])). | |||||
%%-------------------------------------------------------------------- | |||||
%%% INTERNAL FUNCTIONS | |||||
%%-------------------------------------------------------------------- | |||||
%% @doc Is it AMZ-S3's header? | |||||
%% @private | |||||
-spec(is_amz_header(string()|binary()) -> | |||||
boolean()). | |||||
is_amz_header(<<"x-amz-", _Rest/binary>>) -> true; | |||||
is_amz_header(<<"X-Amz-", _Rest/binary>>) -> true; | |||||
is_amz_header(Key) when is_binary(Key) -> | |||||
false; | |||||
is_amz_header(Key) -> | |||||
(string:str(string:to_lower(Key), "x-amz-") == 1). | |||||
%% @doc Get weekday as string | |||||
%% @private | |||||
-spec(weekday(pos_integer(), pos_integer(), pos_integer()) -> | |||||
string()). | |||||
weekday(Y, M, D) -> | |||||
weekday(calendar:day_of_the_week(Y, M, D)). | |||||
weekday(1) -> "Mon"; | |||||
weekday(2) -> "Tue"; | |||||
weekday(3) -> "Wed"; | |||||
weekday(4) -> "Thu"; | |||||
weekday(5) -> "Fri"; | |||||
weekday(6) -> "Sat"; | |||||
weekday(7) -> "Sun". | |||||
%% @doc Get month as string | |||||
%% @private | |||||
-spec(month(pos_integer()) -> | |||||
string()). | |||||
month( 1) -> "Jan"; | |||||
month( 2) -> "Feb"; | |||||
month( 3) -> "Mar"; | |||||
month( 4) -> "Apr"; | |||||
month( 5) -> "May"; | |||||
month( 6) -> "Jun"; | |||||
month( 7) -> "Jul"; | |||||
month( 8) -> "Aug"; | |||||
month( 9) -> "Sep"; | |||||
month(10) -> "Oct"; | |||||
month(11) -> "Nov"; | |||||
month(12) -> "Dec". | |||||
%% @doc URL encode a string binary. | |||||
%% The `noplus' option disables the default behaviour of quoting space | |||||
%% characters, `\s', as `+'. The `upper' option overrides the default behaviour | |||||
%% of writing hex numbers using lowecase letters to using uppercase letters | |||||
%% instead. | |||||
-spec url_encode(binary(), [noplus|upper|noslash]) -> binary(). | |||||
url_encode(Bin, Opts) -> | |||||
Plus = not lists:member(noplus, Opts), | |||||
Upper = lists:member(upper, Opts), | |||||
Slash = not lists:member(noslash, Opts), | |||||
url_encode(Bin, <<>>, Plus, Upper, Slash). | |||||
-spec url_encode(binary(), binary(), boolean(), boolean(), boolean()) -> binary(). | |||||
url_encode(<<C, Rest/binary>>, Acc, P=Plus, U=Upper, S=Slash) -> | |||||
if C >= $0, | |||||
C =< $9 -> | |||||
url_encode(Rest, <<Acc/binary, C>>, P, U, S); | |||||
C >= $A, | |||||
C =< $Z -> | |||||
url_encode(Rest, <<Acc/binary, C>>, P, U, S); | |||||
C >= $a, | |||||
C =< $z -> | |||||
url_encode(Rest, <<Acc/binary, C>>, P, U, S); | |||||
C =:= $.; | |||||
C =:= $-; | |||||
C =:= $~; | |||||
C =:= $_ -> | |||||
url_encode(Rest, <<Acc/binary, C>>, P, U, S); | |||||
C =:= $/ , not Slash -> | |||||
url_encode(Rest, <<Acc/binary, $/>>, P, U, S); | |||||
C =:= $ , Plus -> | |||||
url_encode(Rest, <<Acc/binary, $+>>, P, U, S); | |||||
true -> | |||||
H = C band 16#F0 bsr 4, | |||||
L = C band 16#0F, | |||||
H1 = if Upper -> to_hexu(H); true -> to_hexl(H) end, | |||||
L1 = if Upper -> to_hexu(L); true -> to_hexl(L) end, | |||||
url_encode(Rest, <<Acc/binary, $%, H1, L1>>, P, U, S) | |||||
end; | |||||
url_encode(<<>>, Acc, _Plus, _Upper, _Slash) -> | |||||
Acc. | |||||
-spec to_hexu(byte()) -> byte(). | |||||
to_hexu(C) when C < 10 -> $0 + C; | |||||
to_hexu(C) when C < 17 -> $A + C - 10. | |||||
-spec to_hexl(byte()) -> byte(). | |||||
to_hexl(C) when C < 10 -> $0 + C; | |||||
to_hexl(C) when C < 17 -> $a + C - 10. | |||||
@ -0,0 +1,55 @@ | |||||
-module(utHttpOn). | |||||
%% API | |||||
-export([]). | |||||
%% API | |||||
-export([ | |||||
req/6, %% Post, Put, Delete Only | |||||
req_get/3, %% Get Only | |||||
url_encode/1 | |||||
]). | |||||
-export([get_unique_ref/1, get_conv_ref/0]). | |||||
get_conv_ref() -> | |||||
get_unique_ref(20). | |||||
get_unique_ref(Length) -> | |||||
IntPass = random_num(36, Length), | |||||
list_to_binary(lists:flatten(io_lib:format("~.36B", [IntPass]))). | |||||
random_num(NumeralSystemBase, Length) -> | |||||
Min = round(math:pow(NumeralSystemBase, Length - 1)), | |||||
Max = round(math:pow(NumeralSystemBase, Length)), | |||||
crypto:rand_uniform(Min, Max). | |||||
req(Method, Url, Headers, ContType, Body, HttpOpts) | |||||
when Method == post orelse Method == put orelse Method == delete -> | |||||
HttpClOpts = [{sync, true}, {body_format, binary}], | |||||
Resp = httpc:request(Method, {eu_types:to_list(Url), Headers, ContType, Body}, HttpOpts, HttpClOpts), | |||||
minimize_resp(Resp). | |||||
req_get(Url, Headers, HttpOpts) -> | |||||
HttpClOpts = [{sync, true}, {body_format, binary}], | |||||
Resp = httpc:request(get, {eu_types:to_list(Url), Headers}, HttpOpts, HttpClOpts), | |||||
minimize_resp(Resp). | |||||
minimize_resp(Resp) -> | |||||
case Resp of | |||||
{ok, {{_NewVrsn, 200, _}, _Headers, RespBody}} -> | |||||
{ok, 200, RespBody}; | |||||
{ok, {{_NewVrsn, HttpCode, _}, _Headers, RespBody}} -> | |||||
{error, HttpCode, RespBody}; | |||||
Any -> Any | |||||
end. | |||||
url_encode(Data) -> | |||||
url_encode(Data, ""). | |||||
url_encode([], Acc) -> | |||||
Acc; | |||||
url_encode([{Key, Value} | R], "") -> | |||||
url_encode(R, edoc_lib:escape_uri(Key) ++ "=" ++ edoc_lib:escape_uri(Value)); | |||||
url_encode([{Key, Value} | R], Acc) -> | |||||
url_encode(R, Acc ++ "&" ++ edoc_lib:escape_uri(Key) ++ "=" ++ edoc_lib:escape_uri(Value)). |
@ -0,0 +1,88 @@ | |||||
-module(utHttpUrl). | |||||
%% API | |||||
-export([]). | |||||
-export([ | |||||
url_encode_request_body/1, | |||||
url_encode/1, | |||||
integer_to_hex/1, | |||||
get_ip_from_headers/2, | |||||
get_x_real_ip/1 | |||||
]). | |||||
url_encode_request_body(Args) -> | |||||
lists:concat( | |||||
lists:foldl( | |||||
fun(Rec, []) -> [Rec]; (Rec, Ac) -> [Rec, "&" | Ac] end, | |||||
[], | |||||
[K ++ "=" ++ url_encode(V) || {K, V} <- Args] | |||||
) | |||||
). | |||||
%% encode url params | |||||
url_encode(T) when is_binary(T) -> | |||||
binary_to_list(T); | |||||
url_encode([H | T]) -> | |||||
if | |||||
H >= $a, $z >= H -> | |||||
[H | url_encode(T)]; | |||||
H >= $A, $Z >= H -> | |||||
[H | url_encode(T)]; | |||||
H >= $0, $9 >= H -> | |||||
[H | url_encode(T)]; | |||||
H == $_; H == $.; H == $-; H == $/; H == $: -> % FIXME: more.. | |||||
[H | url_encode(T)]; | |||||
true -> | |||||
case integer_to_hex(H) of | |||||
[X, Y] -> | |||||
[$%, X, Y | url_encode(T)]; | |||||
[X] -> | |||||
[$%, $0, X | url_encode(T)] | |||||
end | |||||
end; | |||||
url_encode([]) -> []. | |||||
integer_to_hex(I) -> | |||||
case catch erlang:integer_to_list(I, 16) of | |||||
{'EXIT', _} -> | |||||
old_integer_to_hex(I); | |||||
Int -> | |||||
Int | |||||
end. | |||||
old_integer_to_hex(I) when I < 10 -> | |||||
integer_to_list(I); | |||||
old_integer_to_hex(I) when I < 16 -> | |||||
[I - 10 + $A]; | |||||
old_integer_to_hex(I) when I >= 16 -> | |||||
N = trunc(I / 16), | |||||
old_integer_to_hex(N) ++ old_integer_to_hex(I rem 16). | |||||
get_ip_from_headers(PeerIp, Headers) -> | |||||
RealIp = proplists:get_value(<<"x-real-ip">>, Headers), | |||||
ForwardedForRaw = proplists:get_value(<<"x-forwarded-for">>, Headers), | |||||
ForwardedFor = case ForwardedForRaw of | |||||
undefined -> undefined; | |||||
ForwardedForRaw -> | |||||
case re:run(ForwardedForRaw, "^(?<first_ip>[^\\,]+)", | |||||
[{capture, [first_ip], binary}]) of | |||||
{match, [FirstIp]} -> FirstIp; | |||||
_Any -> undefined | |||||
end | |||||
end, | |||||
{ok, PeerAddr} = if | |||||
is_binary(RealIp) -> inet_parse:address(binary_to_list(RealIp)); | |||||
is_binary(ForwardedFor) -> inet_parse:address(binary_to_list(ForwardedFor)); | |||||
true -> {ok, PeerIp} | |||||
end, | |||||
PeerAddr. | |||||
get_x_real_ip(Headers) -> | |||||
RealIp = proplists:get_value(<<"x-real-ip">>, Headers), | |||||
RealIp. |
@ -0,0 +1,29 @@ | |||||
-module(utSocket). | |||||
-export([ | |||||
socket2ip/1 | |||||
, socket2port/1 | |||||
, str2ip/1 | |||||
]). | |||||
%% socket转IP | |||||
socket2ip(Socket) -> | |||||
case inet:peername(Socket) of | |||||
{ok, {{A, B, C, D}, _}} -> | |||||
string:join(lists:map(fun com_type:to_list/1, [A, B, C, D]), "."); | |||||
_ -> | |||||
"" | |||||
end. | |||||
socket2port(Socket) -> | |||||
case inet:peername(Socket) of | |||||
{ok, {_, Port}} -> Port; | |||||
_ -> 0 | |||||
end. | |||||
str2ip(IP) when is_list(IP) -> | |||||
[A1, A2, A3, A4] = string:tokens(IP, "."), | |||||
{list_to_integer(A1), list_to_integer(A2), list_to_integer(A3), list_to_integer(A4)}; | |||||
str2ip(IP) when is_tuple(IP) -> | |||||
IP. |