Damn ugly HTTP interface "equivalent" for machi_cr_client.erl basic API

This goes to show that mixing implementation and protocol and API
and lots of other stuff ... is cool for the quick hack to do one thing
but really sucks when trying to do more than one thing.

* Proof-of-concept only: add HTTP/1.0'ish 'PUT' interface to be the
rough equivalent of machi_cr_client:append_chunk/3
* Proof-of-concept only: add HTTP/1.0'ish 'GET' interface to be the
rough equivalent of machi_cr_client:read_chunk/4

Example use: `append_chunk`

    % curl http://127.0.0.1:4444/foo -0 -T /etc/hosts -v
    * Hostname was NOT found in DNS cache
    *   Trying 127.0.0.1...
    * Connected to 127.0.0.1 (127.0.0.1) port 4444 (#0)
    > PUT /foo HTTP/1.0
    > User-Agent: curl/7.37.1
    > Host: 127.0.0.1:4444
    > Accept: */*
    > Content-Length: 338
    >
    * We are completely uploaded and fine
    * HTTP 1.0, assume close after body
    < HTTP/1.0 201 Created
    < Location: foo.50EI18AX.21
    < X-Offset: 3052
    < X-Size: 338
    <
    * Closing connection 0

Example_use: `read_chunk`

    curl 'http://127.0.0.1:4444/foo.50EI18AX.21?offset=3052&size=338' -0 -v
    * Hostname was NOT found in DNS cache
    *   Trying 127.0.0.1...
    * Connected to 127.0.0.1 (127.0.0.1) port 4444 (#0)
    > GET /foo.50EI18AX.21?offset=3052&size=338 HTTP/1.0
    > User-Agent: curl/7.37.1
    > Host: 127.0.0.1:4444
    > Accept: */*
    >
    * HTTP 1.0, assume close after body
    < HTTP/1.0 200 OK
    < Content-Length: 338
    <
    ##
    # Host Database
    #
    # localhost is used to configure the loopback interface
    # when the system is booting.  Do not change this entry.
    ##
    127.0.0.1	localhost
    127.0.0.1	test.localhost
    255.255.255.255	broadcasthost
    ::1             localhost
    fe80::1%lo0	localhost

    # Xxxxxxx Yyyyy
    192.168.99.222	zzzzz

    127.0.0.1   aaaaaaaa.bb.ccccccccc.com
    * Closing connection 0
This commit is contained in:
Scott Lystig Fritchie 2015-05-22 17:40:53 +09:00
parent 6575872c88
commit 6cebf39723

View file

@ -96,6 +96,10 @@
props = [] :: list() % proplist
}).
-record(http_goop, {
len % content-length
}).
start_link([{FluName, TcpPort, DataDir}|Rest])
when is_atom(FluName), is_integer(TcpPort), is_list(DataDir) ->
{ok, spawn_link(fun() -> main2(FluName, TcpPort, DataDir, Rest) end)}.
@ -328,6 +332,10 @@ net_server_loop(Sock, #state{flu_name=FluName, data_dir=DataDir}=S) ->
do_projection_command(Sock, LenHex, S);
<<"WEDGE-STATUS\n">> ->
do_wedge_status(FluName, Sock);
<<"PUT ", _/binary>>=PutLine ->
http_server_hack(FluName, PutLine, Sock, S);
<<"GET ", _/binary>>=PutLine ->
http_server_hack(FluName, PutLine, Sock, S);
_ ->
machi_util:verb("Else Got: ~p\n", [Line]),
io:format(user, "TODO: Else Got: ~p\n", [Line]),
@ -446,6 +454,21 @@ do_net_server_readwrite_common(Sock, OffsetHex, LenHex, FileBin, DataDir,
do_net_server_readwrite_common2(Sock, OffsetHex, LenHex, FileBin, DataDir,
FileOpts, DoItFun,
EpochID, Wedged_p, CurrentEpochId) ->
NoSuchFileFun = fun(Sck) ->
ok = gen_tcp:send(Sck, <<"ERROR NO-SUCH-FILE\n">>)
end,
BadIoFun = fun(Sck) ->
ok = gen_tcp:send(Sck, <<"ERROR BAD-IO\n">>)
end,
do_net_server_readwrite_common2(Sock, OffsetHex, LenHex, FileBin, DataDir,
FileOpts, DoItFun,
EpochID, Wedged_p, CurrentEpochId,
NoSuchFileFun, BadIoFun).
do_net_server_readwrite_common2(Sock, OffsetHex, LenHex, FileBin, DataDir,
FileOpts, DoItFun,
EpochID, Wedged_p, CurrentEpochId,
NoSuchFileFun, BadIoFun) ->
<<Offset:64/big>> = machi_util:hexstr_to_bin(OffsetHex),
<<Len:32/big>> = machi_util:hexstr_to_bin(LenHex),
{_, Path} = machi_util:make_data_filename(DataDir, FileBin),
@ -464,12 +487,11 @@ do_net_server_readwrite_common2(Sock, OffsetHex, LenHex, FileBin, DataDir,
FileOpts, DoItFun,
EpochID, Wedged_p, CurrentEpochId);
{error, enoent} when OptsHasRead ->
ok = gen_tcp:send(Sock, <<"ERROR NO-SUCH-FILE\n">>);
ok = NoSuchFileFun(Sock);
_Else ->
ok = gen_tcp:send(Sock, <<"ERROR BAD-IO\n">>)
ok = BadIoFun(Sock)
end.
do_net_server_write(Sock, OffsetHex, LenHex, FileBin, DataDir,
EpochID, Wedged_p, CurrentEpochId) ->
CSumPath = machi_util:make_checksum_filename(DataDir, FileBin),
@ -816,3 +838,108 @@ make_listener_regname(BaseName) ->
make_projection_server_regname(BaseName) ->
list_to_atom(atom_to_list(BaseName) ++ "_pstore2").
http_server_hack(FluName, Line1, Sock, S) ->
{ok, {http_request, HttpOp, URI0, _HttpV}, _x} =
erlang:decode_packet(http_bin, Line1, [{line_length,4095}]),
MyURI = case URI0 of
{abs_path, Path} -> <<"/", Rest/binary>> = Path,
Rest;
_ -> URI0
end,
Hdrs = http_harvest_headers(Sock),
G = digest_header_goop(Hdrs, #http_goop{}),
case HttpOp of
'PUT' ->
http_server_hack_put(Sock, G, FluName, MyURI);
'GET' ->
http_server_hack_get(Sock, G, FluName, MyURI, S)
end,
ok = gen_tcp:close(Sock),
exit(normal).
http_server_hack_put(Sock, G, FluName, MyURI) ->
ok = inet:setopts(Sock, [{packet, raw}]),
{ok, Chunk} = gen_tcp:recv(Sock, G#http_goop.len, 60*1000),
CSum = machi_util:checksum_chunk(Chunk),
try
FluName ! {seq_append, self(), MyURI, Chunk, CSum, 0}
catch error:badarg ->
error_logger:error_msg("Message send to ~p gave badarg, make certain server is running with correct registered name\n", [?MODULE])
end,
receive
{assignment, Offset, File} ->
Out = io_lib:format("HTTP/1.0 201 Created\r\nLocation: ~s\r\n"
"X-Offset: ~w\r\nX-Size: ~w\r\n\r\n",
[File, Offset, byte_size(Chunk)]),
ok = gen_tcp:send(Sock, Out);
wedged ->
ok = gen_tcp:send(Sock, <<"HTTP/1.0 499 WEDGED\r\n\r\n">>)
after 10*1000 ->
ok = gen_tcp:send(Sock, <<"HTTP/1.0 499 TIMEOUT\r\n\r\n">>)
end.
http_server_hack_get(Sock, _G, _FluName, MyURI, S) ->
DataDir = S#state.data_dir,
{Wedged_p, CurrentEpochId} = ets:lookup_element(S#state.etstab, epoch, 2),
EpochID = <<"unused">>,
NoSuchFileFun = fun(Sck) ->
ok = gen_tcp:send(Sck, "HTTP/1.0 455 NOT-WRITTEN\r\n\r\n")
end,
BadIoFun = fun(Sck) ->
ok = gen_tcp:send(Sck, "HTTP/1.0 466 BAD-IO\r\n\r\n")
end,
DoItFun = fun(FH, Offset, Len) ->
case file:pread(FH, Offset, Len) of
{ok, Bytes} when byte_size(Bytes) == Len ->
Hdrs = io_lib:format("HTTP/1.0 200 OK\r\nContent-Length: ~w\r\n\r\n", [Len]),
gen_tcp:send(Sock, [Hdrs, Bytes]);
{ok, Bytes} ->
machi_util:verb("ok read but wanted ~p got ~p: ~p @ offset ~p\n",
[Len, size(Bytes), Bytes, Offset]),
ok = gen_tcp:send(Sock, "HTTP/1.0 455 PARTIAL-READ\r\n\r\n");
eof ->
ok = gen_tcp:send(Sock, "HTTP/1.0 455 NOT-WRITTEN\r\n\r\n");
_Else2 ->
machi_util:verb("Else2 ~p ~p ~P\n",
[Offset, Len, _Else2, 20]),
ok = gen_tcp:send(Sock, "HTTP/1.0 466 ERROR BAD-READ\r\n\r\n")
end
end,
[File, OptsBin] = binary:split(MyURI, <<"?">>),
Opts = split_uri_options(OptsBin),
OffsetHex = machi_util:int_to_hexstr(proplists:get_value(offset, Opts), 64),
LenHex = machi_util:int_to_hexstr(proplists:get_value(size, Opts), 32),
do_net_server_readwrite_common2(Sock, OffsetHex, LenHex, File, DataDir,
[read, binary, raw], DoItFun,
EpochID, Wedged_p, CurrentEpochId,
NoSuchFileFun, BadIoFun).
http_harvest_headers(Sock) ->
ok = inet:setopts(Sock, [{packet, httph}]),
http_harvest_headers(gen_tcp:recv(Sock, 0, ?SERVER_CMD_READ_TIMEOUT),
Sock, []).
http_harvest_headers({ok, http_eoh}, _Sock, Acc) ->
Acc;
http_harvest_headers({error, _}, _Sock, _Acc) ->
[];
http_harvest_headers({ok, Hdr}, Sock, Acc) ->
http_harvest_headers(gen_tcp:recv(Sock, 0, ?SERVER_CMD_READ_TIMEOUT),
Sock, [Hdr|Acc]).
digest_header_goop([], G) ->
G;
digest_header_goop([{http_header, _, 'Content-Length', _, Str}|T], G) ->
digest_header_goop(T, G#http_goop{len=list_to_integer(Str)});
digest_header_goop([_H|T], G) ->
digest_header_goop(T, G).
split_uri_options(OpsBin) ->
L = binary:split(OpsBin, <<"&">>),
[case binary:split(X, <<"=">>) of
[<<"offset">>, Bin] ->
{offset, binary_to_integer(Bin)};
[<<"size">>, Bin] ->
{size, binary_to_integer(Bin)}
end || X <- L].