|
5 | 5 | stop/0 |
6 | 6 | ]). |
7 | 7 |
|
8 | | --export([ |
9 | | - init/2 |
10 | | -]). |
| 8 | +-define(PORT, 8080). |
11 | 9 |
|
12 | 10 | %% public |
13 | 11 | start() -> |
14 | | - application:ensure_all_started(cowboy), |
15 | | - Dispatch = cowboy_router:compile([{'_', [ |
16 | | - {"/:test", ?MODULE, []}]}]), |
17 | | - {ok, _} = cowboy:start_clear(?MODULE, [{port, 8080}], #{ |
18 | | - env => #{dispatch => Dispatch}, |
19 | | - max_keepalive => infinity, |
20 | | - request_timeout => infinity |
21 | | - }). |
| 12 | + Self = self(), |
| 13 | + Pid = spawn(fun () -> init(Self) end), |
| 14 | + receive |
| 15 | + {Pid, started} -> |
| 16 | + {ok, Pid} |
| 17 | + after 5000 -> |
| 18 | + {error, timeout} |
| 19 | + end. |
22 | 20 |
|
23 | 21 | stop() -> |
24 | | - cowboy:stop_listener(?MODULE). |
25 | | - |
26 | | -%% cowboy callbacks |
27 | | -init(Req, State) -> |
28 | | - case cowboy_req:binding(test, Req) of |
29 | | - <<"1">> -> |
30 | | - reply(200, <<"Hello world!">>, Req, State); |
31 | | - <<"2">> -> |
32 | | - Body = [<<"Hello world!">> || _ <- lists:seq(1, 1000)], |
33 | | - reply(200, Body, Req, State); |
34 | | - <<"3">> -> |
35 | | - {ok, Body, Req2} = cowboy_req:read_body(Req), |
36 | | - reply(200, Body, Req2, State); |
37 | | - <<"4">> -> |
38 | | - Req2 = cowboy_req:stream_reply(200, Req), |
39 | | - ok = cowboy_req:stream_body("Hello", nofin, Req2), |
40 | | - ok = cowboy_req:stream_body(" world!", fin, Req2), |
41 | | - {ok, Req2, State}; |
42 | | - <<"5">> -> |
43 | | - Verb = cowboy_req:method(Req), |
44 | | - reply(200, Verb, Req, State) |
| 22 | + case whereis(?MODULE) of |
| 23 | + undefined -> |
| 24 | + ok; |
| 25 | + Pid -> |
| 26 | + exit(Pid, kill), |
| 27 | + ok |
45 | 28 | end. |
46 | 29 |
|
47 | 30 | %% private |
48 | | -reply(StatusCode, Body, Req, State) -> |
49 | | - Req2 = cowboy_req:reply(StatusCode, #{ |
50 | | - <<"Content-Type">> => <<"text/plain">>, |
51 | | - <<"Connection">> => <<"Keep-Alive">> |
52 | | - }, Body, Req), |
53 | | - {ok, Req2, State}. |
| 31 | +init(Parent) -> |
| 32 | + register(?MODULE, self()), |
| 33 | + {ok, LSocket} = gen_tcp:listen(?PORT, [ |
| 34 | + binary, |
| 35 | + {active, false}, |
| 36 | + {backlog, 128}, |
| 37 | + {packet, http_bin}, |
| 38 | + {reuseaddr, true} |
| 39 | + ]), |
| 40 | + Parent ! {self(), started}, |
| 41 | + accept(LSocket). |
| 42 | + |
| 43 | +accept(LSocket) -> |
| 44 | + {ok, Socket} = gen_tcp:accept(LSocket), |
| 45 | + Pid = spawn_link(fun () -> |
| 46 | + receive go -> connection(Socket) end |
| 47 | + end), |
| 48 | + ok = gen_tcp:controlling_process(Socket, Pid), |
| 49 | + Pid ! go, |
| 50 | + accept(LSocket). |
| 51 | + |
| 52 | +connection(Socket) -> |
| 53 | + case gen_tcp:recv(Socket, 0) of |
| 54 | + {ok, {http_request, Method, {abs_path, Path}, _Version}} -> |
| 55 | + ContentLength = headers(Socket, 0), |
| 56 | + Body = body(Socket, ContentLength), |
| 57 | + respond(Socket, Method, Path, Body), |
| 58 | + connection(Socket); |
| 59 | + {ok, _} -> |
| 60 | + gen_tcp:close(Socket); |
| 61 | + {error, _} -> |
| 62 | + gen_tcp:close(Socket) |
| 63 | + end. |
| 64 | + |
| 65 | +headers(Socket, ContentLength) -> |
| 66 | + case gen_tcp:recv(Socket, 0) of |
| 67 | + {ok, {http_header, _, 'Content-Length', _, Value}} -> |
| 68 | + headers(Socket, binary_to_integer(Value)); |
| 69 | + {ok, {http_header, _, _, _, _}} -> |
| 70 | + headers(Socket, ContentLength); |
| 71 | + {ok, http_eoh} -> |
| 72 | + ContentLength |
| 73 | + end. |
| 74 | + |
| 75 | +body(_Socket, 0) -> |
| 76 | + <<>>; |
| 77 | +body(Socket, ContentLength) -> |
| 78 | + ok = inet:setopts(Socket, [{packet, raw}]), |
| 79 | + {ok, Body} = gen_tcp:recv(Socket, ContentLength), |
| 80 | + ok = inet:setopts(Socket, [{packet, http_bin}]), |
| 81 | + Body. |
| 82 | + |
| 83 | +respond(Socket, Method, <<"/1">>, _Body) -> |
| 84 | + reply(Socket, Method, <<"Hello world!">>); |
| 85 | +respond(Socket, Method, <<"/2">>, _Body) -> |
| 86 | + reply(Socket, Method, binary:copy(<<"Hello world!">>, 1000)); |
| 87 | +respond(Socket, Method, <<"/3">>, Body) -> |
| 88 | + reply(Socket, Method, Body); |
| 89 | +respond(Socket, Method, <<"/4">>, _Body) -> |
| 90 | + chunked_reply(Socket, Method, [<<"Hello">>, <<" world!">>]); |
| 91 | +respond(Socket, Method, <<"/5">>, _Body) -> |
| 92 | + reply(Socket, Method, method(Method)). |
| 93 | + |
| 94 | +method(Method) when is_atom(Method) -> |
| 95 | + atom_to_binary(Method, utf8); |
| 96 | +method(Method) when is_binary(Method) -> |
| 97 | + Method. |
| 98 | + |
| 99 | +reply(Socket, Method, Body) -> |
| 100 | + Headers = [ |
| 101 | + <<"HTTP/1.1 200 OK\r\n">>, |
| 102 | + <<"Connection: Keep-Alive\r\n">>, |
| 103 | + <<"Content-Type: text/plain\r\n">>, |
| 104 | + <<"Content-Length: ">>, integer_to_binary(iolist_size(Body)), |
| 105 | + <<"\r\n\r\n">> |
| 106 | + ], |
| 107 | + case Method of |
| 108 | + 'HEAD' -> |
| 109 | + ok = gen_tcp:send(Socket, Headers); |
| 110 | + _ -> |
| 111 | + ok = gen_tcp:send(Socket, [Headers, Body]) |
| 112 | + end. |
| 113 | + |
| 114 | +chunked_reply(Socket, Method, Chunks) -> |
| 115 | + Headers = [ |
| 116 | + <<"HTTP/1.1 200 OK\r\n">>, |
| 117 | + <<"Connection: Keep-Alive\r\n">>, |
| 118 | + <<"Content-Type: text/plain\r\n">>, |
| 119 | + <<"Transfer-Encoding: chunked\r\n\r\n">> |
| 120 | + ], |
| 121 | + case Method of |
| 122 | + 'HEAD' -> |
| 123 | + ok = gen_tcp:send(Socket, Headers); |
| 124 | + _ -> |
| 125 | + Encoded = [[integer_to_binary(byte_size(Chunk), 16), <<"\r\n">>, |
| 126 | + Chunk, <<"\r\n">>] || Chunk <- Chunks], |
| 127 | + ok = gen_tcp:send(Socket, [Headers, Encoded, <<"0\r\n\r\n">>]) |
| 128 | + end. |
0 commit comments