Introduction
Little (LFE) HTTP Client -- A light-weight LFE wrapper around lhttpc
lhc is library is meant to be a dead-simple HTTP client for LFE projects. That's all there is to it. It uses lhttpc under the hood, which can be a little cumbersome for those new to Erlang when used directly; as such, lhc will likely provide a welcome alternative.
Dependencies
To use lhc, you need to have the following:
- Erlang
rebar3
Since version 0.2.0, the lhc library has moved to using rebar3
and no longer supports the old version of rebar. If, for whatever reason, your project cannot use rebar3
, you'll need to use version 0.1.0 of the lhc library.
Installation
To use the
rebar3
LFE plugin, update yourrebar.config
file like the following:
{plugins, [
{rebar3_lfe, "0.4.4"}}}
]}.
{provider_hooks, [
{pre, [{compile, {lfe, compile}}]}
]}.
Then update the
deps
directive in yourrebar.config
to pull down the latest lhc:
{deps, [
...
{lhc, "0.4.0"},
]}.
Once your project has added the lhc dependency, you're ready to execute the
rebar3
command for compiling LFE projects:
$ rebar3 lfe compile
If you want to compile both LFE and any Erlang code you have in your project, you can do both with one command:
$ rebar3 compile
The API
Each of the API functions that map to an HTTP verb have at least one arity
that supports setting lhc options which are passed to lhc:request
.
These options may have zero or more of the following:
return
- return type. Can be one ofbody
(default),status
,headers
orall
callback
- the function that gets called once a result is obtained from thelhttpc
client library
The lhc examples below are run against a YAWS REST server demo store. You can download and run the "store" yourself when you clone the repo.
For POST
ing data, we use the ljson LFE
library. To make this available to your project, simply add it to your
rebar.config
file, per the instructions on the ljson project README.
start
It is required to start lhc before using the API:
> (lhc:start)
(#(inets ok) #(ssl ok) #(lhttpc ok) #(lhc ok))
If you attempt to use the API without having first started it, you will see errors like the following:
> (lhc:get "http://localhost:8000/demos/store3/orders")
#(error
#(exit
#(noproc
#(gen_server call
(undefined #(socket <0.35.0> "google.com" 80 false) infinity)))
(#(lhttpc_manager ensure_call 6
(#(file "src/lhttpc_manager.erl") #(line 234)))
#(lhttpc_client execute 9 (#(file "src/lhttpc_client.erl") #(line 158)))
#(lhttpc_client request 9 (#(file "src/lhttpc_client.erl") #(line 99)))
#(proc_lib init_p_do_apply 3 (#(file "proc_lib.erl") #(line 239))))))
This function simply starts the lhc LFE HTTP client. This is required for any use of the lhc client library.
get
Perform a simple
GET
:
> (lhc:get "http://localhost:8000/demos/store3/orders")
"{\"result\": \"You got a list of orders.\"}"
>
lhc:get/1
- takes a URLlhc:get/2
- takes a URL and lhc optionslhc:get/3
- takes a URL, list of headers, and lhc options
Depending upon the return
option (default being body
) and callback
option, each of these will return a parsed result iof the content obtained by
lhttpc
.
head
To just get the headers from the server:
> (lhc:head "http://localhost:8000/demos/store3/")
(#("Content-Type" "application/json")
#("Date" "Thu, 27 Aug 2015 14:39:31 GMT")
#("Server" "Yaws 2.0"))
>
lhc:head/1
- takes a URLlhc:head/2
- takes a URL and lhc optionslhc:head/3
- takes a URL, list of headers, and lhc options
Returns just the parsed headers of the result from lhttpc
. By defualt, this
is a list of tuples, each being a key/value pair of header name and head value.
post
To
POST
, we first create a payload. The demo REST API we're testing against can take any string value, but many applications will expect JSON data. Let's use JSON here:
> (set payload (ljson:encode '(#(make #"Volvo") #(model #"P1800"))))
#"{"make":"Volvo","model":"P1800"}"
>
With our payload in hand, we can now
POST
to create a new order:
> (lhc:post "http://localhost:8000/demos/store3/order" payload)
"{\"result\": \"{\"order-id\": 124}\"}"
>
lhc:post/1
- takes a URL, making an empty dataPOST
lhc:post/2
- takes a URL andPOST
datalhc:post/3
- takes a URL,POST
data, and lhc optionslhc:post/4
- takes a URL,POST
data, a list of headers, and lhc options
put
To
PUT
, we first create a payload similar to what we did forPOST
but with the modified data we want:
> (set payload (ljson:encode '(#(make #"Volvo") #(model #"2015 P1800"))))
#"{"make":"Volvo","model":"2015 P1800"}"
>
We can now
PUT
to update our order:
> (lhc:put "http://localhost:8000/demos/store3/order/124" payload)
"{\"result\": \"You updated all of order 124.\"}"
>
lhc:put/1
- takes a URL, making an empty dataPUT
lhc:put/2
- takes a URL andPUT
datalhc:put/3
- takes a URL,PUT
data, and lhc optionslhc:put/4
- takes a URL,PUT
data, a list of headers, and lhc options
delete
DELETE
ing is as simple as passing the appropriate URL:
> (lhc:delete "http://localhost:8000/demos/store3/order/124")
"{\"result\": \"You deleted order 124.\"}"
>
lhc:delete/1
- takes a URLlhc:delete/2
- takes a URL and lhc optionslhc:delete/3
- takes a URL, list of headers, and lhc options
trace
Note that TRACE
isn't implemented in YAWS so we don't have example code to
share. Likewise, lhc:trace
is untested against a server implementing
TRACE
-- please
let us know
if you run into usage problems with this function.
lhc:trace/1
- takes a URLlhc:trace/2
- takes a URL and datalhc:trace/3
- takes a URL, data, and lhc optionslhc:trace/4
- takes a URL, data, a list of headers, and lhc options
options
lhc provides support for requesting the allowed methods for a given URL:
> (lhc:options "http://localhost:8000/demos/store3/order/124")
"{\"result\": \"You got the allowed method for order/124: GET, PUT, POST, DELETE, and OPTIONS.\"}"
lhc:options/1
- takes a URLlhc:options/2
- takes a URL and lhc optionslhc:options/3
- takes a URL, list of headers, and lhc options
connect
TBD
(Note that this HTTP verb is not supported by YAWS, so we'll likely save it for very last.)
patch
Let's define the partial data we want to send:
> (set payload (ljson:encode '(#(model #"2015 P1800"))))
#"{"model":"2015 P1800"}"
>
We can now
PATCH
our order, updating only the part that we wanted to change:
> (lhc:patch "http://localhost:8000/demos/store3/order/124" payload)
"{\"result\": \"You updated part of order 124.\"}"
>
PATCH
is useful for large data sets where you only one a part of the data
updated. You can use PATCH
to update just the bits you're interested in,
without having to send the entire payload like is recommended with PUT
.
lhc:patch/1
- takes a URL, making an empty dataPATCH
lhc:patch/2
- takes a URL andPATCH
datalhc:patch/3
- takes a URL,PATCH
data, and lhc optionslhc:patch/4
- takes a URL,PATCH
data, a list of headers, and lhc options
request
[Code ready, need docs]
parse-results
[Code ready, need docs]
Custom Results Parsing
[Code ready, need docs]
From Erlang
Basic usage from Erlang is straight-forward:
1> lhc:start().
[{inets,ok},{ssl,ok},{lhttpc,ok},{lhc,ok}]
2> lhc:get("http://localhost:8000/demos/store3/orders").
"{\"result\": \"You got a list of orders.\"}"
3> Payload = ljson:encode([{make,<<"Volvo">>},{model,<<"P1800">>}]).
<<"{\"make\":\"Volvo\",\"model\":\"P1800\"}">>
4> lhc:post("http://localhost:8000/demos/store3/order", Payload).
"{\"result\": \"{\"order-id\": 124}\"}"
Using lhc from Erlang is very straight-forward; there aren't even hypens in most module or function names, so no need to escape any atoms!
Why would you want to, you ask? Well, it might be convenient to use a library that offers consistent usage patterns (and function calls) across a selection of HTTP clients in the Erlang ecosystem. You and your developers could write your HTTP code once, and then change backend clients as the needs arose, without having to change any code (just some small configuration settings).
Take a look at the sample usage to the right to get a sense of using lhc from Erlang.
URL Functions
lhc has been updated to use the new LFE library yuri for handling all its URL-parsing needs.
(examples TBD)
Backends
WARNING!!!
lhc used to and will again support the concept of pluings, allowing users to wrap their preferred HTTP client library.
Currently backend support has been suspended and the httpc client that comes bundled with Erlang is hard-coded.
Old backends:
Planned backends currently include the following:
Selecting
Edit the backend value in the
lhc
section of yourlfe.config
file. See the lhclfe.config
for an example of this. Once edited, runningstart
will use that value:
> (lhc:start)
(#(inets ok) #(ssl ok) #(lhttpc ok) #(lhc ok))
> (lhc:get-backend)
lhttpc
>
You can also start lhc with your preferred backend:
> (lhc:start 'httpc)
(#(inets ok) #(ssl ok) #(lhc ok))
>
You can change backends at any time:
> (lhc:change-backend 'lhttpc)
(#(backend #(previous httpc) #(current lhttpc)))
>
Bakends my be selected one of three ways:
- setting the
backend
value in thelhc
section of thelfe.config
file - Starting lhc with a particular bacend.
- Changing the backend after lhc has been started.
Backend Information
Find out which backend you are using:
> (lhc:get-backend)
lhttpc
>
Find out which backend is configured in your
lfe.config
file:
> (lhc:get-backend-cfg)
lhttpc
Find out which module holds the backend functions:
> (lhc:get-backend-module)
lhc-backend
Several convenience functions are provided in support of lhc backends.
See examples to the right.
User Agent
The lhc user agent string:
"LFE Little HTTP Client/0.1.0 (LFE 0.10.0-dev; Erlang 18; backend=lhttpc) (+http://github.com/lfex/lhc)"
The lhc user agent will show up in any HTTP server log files where client user agent strings are recorded. To the right is an example of the lhc user agent string.
Previous Versions
Documentation is available for all previous releases:
License
Copyright © 2014-2023 Duncan McGreggor oubiwann@gmail.com
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.