Let's implement a simple loop, using gen_tcp to listen on a particular port, and spawn a handler process for each incoming request (note that we have not defined handle_request/1 yet). When setting up the initial socket, we are going to specify that incoming packets be packaged according as HttpPacket types, as defined in decode_packet/3 built-in function (BIF).
start(Port)-> {ok, ListenSock}=gen_tcp:listen(Port, [list,{active, false},{packet,http}]), ?MODULE:loop(ListenSock). loop(ListenSock) -> {ok, Sock}=gen_tcp:accept(ListenSock), spawn(?MODULE, handle_request, [Sock]), ?MODULE:loop(ListenSock).
Since we are going to deal with POST request data we will need to differentiate between POST methods and non-POST methods (i.e. GET, PUT, etc.). The 2nd element of the HttpRequest tuple ({http_request, HttpMethod, HttpUri, HttpVersion}) will provide us with an atom matching one of the seven HTTP method types, so we can dispatch requests to the appropriate handler for a given method type.
handle_request(Sock) -> {ok, {http_request, Method, Path, Version}}=gen_tcp:recv(Sock, 0), case (Method) of 'POST' -> handle_post(Sock); _ -> send_unsupported_error(Sock) end.
In order to retrieve the data contained in the body of a POST request we will first need to determine the length of the data. This information can be found in the Content-Length header, and we can get this information by iterating over the headers with a case statement and a little recursion, returning once we find the value we are looking for:
get_content_length(Sock) -> case gen_tcp:recv(Sock, 0, 60000) of {ok, {http_header, _, 'Content-Length', _, Length}} -> list_to_integer(Length); {ok, {http_header, _, Header, _, _}} -> get_content_length(Sock) end.
Now, all we need to do is iterate over the remaining headers until we get to the http_eoh atom indicated the end of header ("eoh") information...we can then turn off the HTTP type wrapping and read the raw POST data from the socket.
get_body(Sock, Length) -> case gen_tcp:recv(Sock, 0) of {ok, http_eoh} ->inet:setopts(Sock, [{packet, raw}]),{ok,Body}=gen_tcp:recv(Sock, Length),Body; _ -> get_body(Sock, Length) end.Putting it all together, our handle_post/1 function would look something like this:
handle_post(Sock) -> Length=get_content_length(Sock), PostBody=get_body(Sock, Length), io:fwrite(PostBody), send_accept(Sock).
While not really in scope of this article, let's fill in a few functions we referenced above.
send_accept(Sock) -> gen_tcp:send(Sock, "HTTP/1.1 202 Accepted\r\nConnection: close\r\nContent-Type: text/html; charset=UTF-8\r\nCache-Control: no-cache\r\n\r\n"), gen_tcp:close(Sock). send_unsupported_error(Sock) -> gen_tcp:send(Sock, "HTTP/1.1 405 Method Not Allowed\r\nConnection: close\r\nAllow: POST\r\nContent-Type: text/html; charset=UTF-8\r\nCache-Control: no-cache\r\n\r\n"), gen_tcp:close(Sock).