Harbor as HTTP server
The harbor server can be used as a HTTP server. You can use the
function harbor.http.register
to register HTTP handlers.
Its parameters are are follow:
harbor.http.register(port=8080,method="GET",uri,handler)
where:
port
is the port where to receive incoming connectionsmethod
is for the http method (or verb), one of:"GET"
,"PUT"
,"POST"
,"DELETE"
,"OPTIONS"
and"HEAD"
uri
is used to match requested uri. Perl regular expressions are accepted.handler
is the function used to process requests.
handler
function has type:
(~protocol:string, ~data:string,
~headers:[(string*string)], string)->'a))->unit
where 'a is either string or ()->string
where:
protocol
is the HTTP protocol used by the client. Currently, one of"HTTP/1.0"
or"HTTP/1.1"
data
is the data passed during a POST requestheaders
is the list of HTTP headers sent by the clientstring
is the (unparsed) uri requested by the client, e.g.:"/foo?var=bar"
The handler
function returns HTTP and HTML data to be
sent to the client, for instance:
HTTP/1.1 200 OK\r\n\
Content-type: text/html\r\n\
Content-Length: 35\r\n\
\r\n\
<html><body>It works!</body></html>
(\r\n
should always be used for line return in HTTP
content)
The handler is a string getter, which means that it can be
of either type string
or type ()->string
.
The former is used to returned the response in one call while the later
can be used to returned bigger response without having to load the whole
response string in memory, for instane in the case of a file.
For convenience, two functions, http_response
and
http_response_stream
are provided to create a HTTP response
string. http_response
has the following type:
(?protocol:string,?code:int,?headers:[(string*string)],
?data:string)->string
where:
protocol
is the HTTP protocol of the response (defaultHTTP/1.1
)code
is the response code (default200
)headers
is the response headers. It defaults to[]
but an appropriate"Content-Length"
header is added if not set by the user anddata
is not empty.data
is an optional response data (default""
)
http_response_stream
has the following type:
(?protocol:string,?code:int,?headers:[(string*string)],
data_len:int,data:()->string)->string
where:
protocol
is the HTTP protocol of the response (defaultHTTP/1.1
)code
is the response code (default200
)headers
is the response headers. It defaults to[]
but an appropriate"Content-Length"
header is added if not set by the user anddata
is not empty.data_len
is the length of the streamed responsedata
is the response stream
Thess functions can be used to create your own HTTP interface. Some examples are:
Redirect Icecast’s pages
Some source clients using the harbor may also request pages that are served by an icecast server, for instance listeners statistics. In this case, you can register the following handler:
# Redirect all files other
# than /admin.* to icecast,
# located at localhost:8000
def redirect_icecast(~protocol,~data,~headers,uri) =
http_response(
protocol=protocol,
code=301,
headers=[("Location","http://localhost:8000#{uri}")]
)end
# Register this handler at port 8005
# (provided harbor sources are also served
# from this port).
harbor.http.register(port=8005,method="GET",
"^/(?!admin)",
redirect_icecast)
Another alternative, less recommended, is to directly fetch the page’s content from the Icecast server:
# Serve all files other
# than /admin.* by fetching data
# from Icecast, located at localhost:8000
def proxy_icecast(~protocol,~data,~headers,uri) =
def f(x) =
# Replace Host
if string.capitalize(fst(x)) == "HOST" then
"Host: localhost:8000"
else
"#{fst(x)}: #{snd(x)}"
end
end
headers = list.map(f,headers)
headers = string.concat(separator="\r\n",headers)
request =
"#{method} #{uri} #{protocol}\r\n\
#{headers}\r\n\r\n"
get_process_output("echo #{quote(request)} | \
nc localhost 8000")
end
# Register this handler at port 8005
# (provided harbor sources are also served
# from this port).
harbor.http.register(port=8005,method="GET",
"^/(?!admin)",
proxy_icecast)
This method is not recommended because some servers may not close the
socket after serving a request, causing nc
and liquidsoap
to hang.
Get metadata
You can use harbor to register HTTP services to fecth/set the
metadata of a source. For instance, using the JSON
export function json_of
:
meta = ref []
# s = some source
# Update current metadata
# converted in UTF8
def update_meta(m) =
m = metadata.export(m)
recode = string.recode(out_enc="UTF-8")
def f(x) =
recode(fst(x)),recode(snd(x)))
(end
list.map(f,m)
meta := end
# Apply update_metadata
# every time we see a new
# metadata
s = on_metadata(update_meta,s)
# Return the json content
# of meta
def get_meta(~protocol,~data,~headers,uri) =
m = !meta
http_response(
protocol=protocol,
code=200,
headers=[("Content-Type","application/json; charset=utf-8")],
data=json_of(m)
)end
# Register get_meta at port 700
harbor.http.register(port=7000,method="GET","/getmeta",get_meta)
Once the script is running, a GET/POST request for
/getmeta
at port 7000
returns the
following:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"genre": "Soul",
"album": "The Complete Stax-Volt Singles: 1959-1968 (Disc 8)",
"artist": "Astors",
"title": "Daddy Didn't Tell Me"
}
Which can be used with AJAX-based backends to fetch the current
metadata of source s
Set metadata
Using insert_metadata
, you can register a GET handler
that updates the metadata of a given source. For instance:
# s = some source
# x is of type ((metadata)->unit)*source
# first part is a function used to update
# metadata and second part is the source
# whose metadata are updated
x = insert_metadata(s)
# Get the function
insert = fst(x)
# Redefine s as the new source
s = snd(x)
# The handler
def set_meta(~protocol,~data,~headers,uri) =
# Split uri of the form request?foo=bar&...
# into (request,[("foo","bar"),..])
x = url.split(uri)
# Filter out unusual metadata
meta = metadata.export(snd(x))
# Grab the returned message
ret =
if meta != [] then
insert(meta)
"OK!"
else
"No metadata to add!"
end
# Return response
http_response(
protocol=protocol,
code=200,
headers=[("Content-Type","text/html")],
data="<html><body><b>#{ret}</b></body></html>"
)end
# Register handler on port 700
harbor.http.register(port=7000,method="GET","/setmeta",set_meta)
Now, a request of the form
http://server:7000/setmeta?title=foo
will update the
metadata of source s
with [("title","foo")]
.
You can use this handler, for instance, in a custom HTML form.
Limitations
When using harbor’s HTTP server, please be warned that the server is not meant to be used under heavy load. Therefore, it should not be exposed to your users/listeners if you expect many of them. In this case, you should use it as a backend/middle-end and have some kind of caching between harbor and the final user. In particular, the harbor server is not meant to server big files because it loads their entire content in memory before sending them. However, the harbor HTTP server is fully equipped to serve any kind of CGI script.