I'm writing code in Erlang that accepts HTTP requests. I have working code which is shown below.
The problem I have is that I'm unsure about the returned result of gen_tcp:recv
.
I create a listening socket and accept sockets using
{ok, ListenSock}=gen_tcp:listen(Port, [list,{active, false},{packet,http}])
{ok, Sock}=gen_tcp:accept(ListenSock),
I accept a GET request (or any other) using
{ok, {http_request, Method, Path, Version}} = gen_tcp:recv(Sock, 0),
handle_get(Sock, Path);
Then, to get url parameters (CGI parameters, e.g., ?foo=1&bar=2
) I have to match Path
with a structure {abs_path, RelativePath}
.
handle_get(Sock, ReqPath) ->
{abs_path, RelPath} = ReqPath,
Parameters = string:substr(RelPath, string:str(RelPath, "?") + 1),
While I was reading through the docs of Erlang about gen_tcp
and more specifically the recv
method I found the page describing HttpPacket
.
The grammar on the page clearly shows that Path
in HttpPacket
, and in this case the HttpRequest
type, can have multiple types of HttpUri
.
HttpRequest = {http_request, HttpMethod, HttpUri, HttpVersion}
HttpUri = '*'
| {absoluteURI,
http | https,
Host :: HttpString,
Port :: inet:port_number() | undefined,
Path :: HttpString}
| {scheme, Scheme :: HttpString, HttpString}
| {abs_path, HttpString}
| HttpString
I understand that I have to support each of these possible cases, however I am not sure. I am also wondering how I can test these cases. I have tried using curl
and RESTClient
in Firefox and both of them make gen_tcp:recv
return abs_path
.
So to be clear, how is determined whether the request holds {abs_path, HttpString}
, {scheme, Scheme :: HttpString, HttpString}
or {absoluteURI,...}
and do I need to support all of them?
start(Port)->
{ok, ListenSock}=gen_tcp:listen(Port, [list,{active, false},{packet,http}]),
loop(ListenSock).
loop(ListenSock) ->
{ok, Sock}=gen_tcp:accept(ListenSock),
spawn(?MODULE, handle_request, [Sock]),
loop(ListenSock).
%% Takes a TCP socket and receives
%% http://erlang.org/doc/man/erlang.html#decode_packet-3
handle_request(Sock) ->
{ok, {http_request, Method, Path, _Version}} = gen_tcp:recv(Sock, 0),
case (Method) of
'GET' ->
handle_get(Sock, Path);
_ ->
send_unsupported_error(Sock)
end.
handle_get(Sock, ReqPath) ->
{abs_path, RelPath} = ReqPath,
Parameters = string:substr(RelPath, string:str(RelPath, "?") + 1),
%% Debugging
ParsedParms = httpd:parse_query(Parameters),
io:fwrite("Full Path: ~p~nParameters: ~p~n", [RelPath, ParsedParms]),
%% End Debugging
send_accept(Sock).
You can use a simple network-capable client such as netcat (/usr/bin/nc
on my system) to send whatever form of request you like. For example, the following connects to a web server listening on localhost
port 8000 and sends a GET
request where the path is a URL (note that the $
denotes a shell prompt):
$ nc localhost 8000
GET http://stackoverflow.com HTTP/1.1
$
The nc
program reads from its standard input. Be sure to hit enter twice after the GET
line to properly indicate the end of the HTTP headers. This results in the gen_tcp:recv
call on the server returning:
{absoluteURI,http,"stackoverflow.com",undefined,"/"}
Similarly, the following will return a path from gen_tcp:recv
that's not an {abs_path, ...}
tuple, but rather just "../foo"
:
$ nc localhost 8000
GET ../foo HTTP/1.1
$
You can easily set up test variations like these in text files and feed them into nc
using stdin redirection.