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 your rebar.config file like the following:

{plugins, [
   {rebar3_lfe, "0.4.4"}}}
  ]}.
{provider_hooks, [
   {pre, [{compile, {lfe, compile}}]}
  ]}.

Then update the deps directive in your rebar.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 of body (default), status, headers or all
  • callback - the function that gets called once a result is obtained from the lhttpc 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 POSTing 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 URL
  • lhc:get/2 - takes a URL and lhc options
  • lhc: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.

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 URL
  • lhc:head/2 - takes a URL and lhc options
  • lhc: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 data POST
  • lhc:post/2 - takes a URL and POST data
  • lhc:post/3 - takes a URL, POST data, and lhc options
  • lhc: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 for POST 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 data PUT
  • lhc:put/2 - takes a URL and PUT data
  • lhc:put/3 - takes a URL, PUT data, and lhc options
  • lhc:put/4 - takes a URL, PUT data, a list of headers, and lhc options

delete

DELETEing 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 URL
  • lhc:delete/2 - takes a URL and lhc options
  • lhc: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 URL
  • lhc:trace/2 - takes a URL and data
  • lhc:trace/3 - takes a URL, data, and lhc options
  • lhc: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 URL
  • lhc:options/2 - takes a URL and lhc options
  • lhc: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 data PATCH
  • lhc:patch/2 - takes a URL and PATCH data
  • lhc:patch/3 - takes a URL, PATCH data, and lhc options
  • lhc: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 your lfe.config file. See the lhc lfe.config for an example of this. Once edited, running start 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 the lhc section of the lfe.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.