@ -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. |