rest_client Module
     __________________________________________________________

   Table of Contents

   1. Admin Guide

        1.1. Overview
        1.2. TCP Connection Reusage
        1.3. Dependencies

              1.3.1. OpenSIPS Modules
              1.3.2. External Libraries or Applications

        1.4. Exported Parameters

              1.4.1. curl_timeout (integer)
              1.4.2. connection_timeout (integer)
              1.4.3. connect_poll_interval (integer)
              1.4.4. max_async_transfers (integer)
              1.4.5. max_transfer_size (integer)
              1.4.6. ssl_verifypeer (integer)
              1.4.7. ssl_verifyhost (integer)
              1.4.8. ssl_capath (integer)
              1.4.9. curl_http_version (integer)
              1.4.10. enable_expect_100 (boolean)
              1.4.11. no_concurrent_connects (boolean)
              1.4.12. curl_conn_lifetime (integer)

        1.5. Exported Functions

              1.5.1. rest_get(url, body_pv, [ctype_pv],
                      [retcode_pv])

              1.5.2. rest_post(url, send_body, [send_ctype],
                      recv_body_pv, [recv_ctype_pv], [retcode_pv])

              1.5.3. rest_put(url, send_body, [send_ctype],
                      recv_body_pv[, [recv_ctype_pv][,
                      [retcode_pv]]])

              1.5.4. rest_append_hf(txt)
              1.5.5. rest_init_client_tls(tls_client_domain)

        1.6. Exported Asynchronous Functions

              1.6.1. rest_get(url, body_pv[, [ctype_pv][,
                      [retcode_pv]]])

              1.6.2. rest_post(url, send_body_pv, [send_ctype_pv],
                      recv_body_pv[, [recv_ctype_pv][,
                      [retcode_pv]]])

              1.6.3. rest_put(url, send_body_pv, [send_ctype_pv],
                      recv_body_pv[, [recv_ctype_pv][,
                      [retcode_pv]]])

        1.7. Exported script transformations

              1.7.1. {rest.escape}
              1.7.2. {rest.unescape}

   2. Contributors

        2.1. By Commit Statistics
        2.2. By Commit Activity

   3. Documentation

        3.1. Contributors

   List of Tables

   2.1. Top contributors by DevScore^(1), authored commits^(2) and
          lines added/removed^(3)

   2.2. Most recently active contributors^(1) to this module

   List of Examples

   1.1. Setting the curl_timeout parameter
   1.2. Setting the connection_timeout parameter
   1.3. Setting the connect_poll_interval parameter
   1.4. Setting the max_async_transfers parameter
   1.5. Setting the max_transfer_size parameter
   1.6. Setting the ssl_verifypeer parameter
   1.7. Setting the ssl_verifyhost parameter
   1.8. Setting the ssl_capath parameter
   1.9. Setting the curl_http_version parameter
   1.10. Setting the enable_expect_100 parameter
   1.11. Setting the no_concurrent_connects parameter
   1.12. Setting the curl_conn_lifetime parameter
   1.13. rest_get usage
   1.14. rest_post usage
   1.15. rest_put usage
   1.16. rest_append_hf usage
   1.17. rest_init_client_tls usage
   1.18. async rest_get usage
   1.19. async rest_post usage
   1.20. async rest_put usage
   1.21. rest.escape usage
   1.22. rest.unescape usage

Chapter 1. Admin Guide

1.1. Overview

   The rest_client module provides a means of interacting with an
   HTTP server by doing RESTful queries, such as GET, POST and
   PUT.

1.2. TCP Connection Reusage

   Unless specified otherwise by the server through a "Connection:
   close" indication, the module will keep and reuse the TCP
   connections it creates as much as possible, regardless if the
   script writer performs blocking or asynchronous HTTP requests.
   These connections are not shared among OpenSIPS workers — each
   worker maintains its own set of connections.

1.3. Dependencies

1.3.1. OpenSIPS Modules

   The following modules must be loaded before this module:
     * No dependencies on other OpenSIPS modules..

1.3.2. External Libraries or Applications

   The following libraries or applications must be installed
   before running OpenSIPS with this module loaded:
     * libcurl.

1.4. Exported Parameters

1.4.1. curl_timeout (integer)

   The maximum allowed time for any HTTP(S) transfer to complete.
   This interval is inclusive of the initial connect time window,
   hence the value of this parameter must be greater than or equal
   to connection_timeout.

   Default value is “20” seconds.

   Example 1.1. Setting the curl_timeout parameter
...
modparam("rest_client", "curl_timeout", 10)
...

1.4.2. connection_timeout (integer)

   The maximum allowed time to establish a connection with the
   server.

   Default value is “20” seconds.

   Example 1.2. Setting the connection_timeout parameter
...
modparam("rest_client", "connection_timeout", 4)
...

1.4.3. connect_poll_interval (integer)

   Only relevant with async requests. Allows complete control over
   how quickly we want to detect libcurl's completed blocking
   TCP/TLS handshakes, so the async transfers can be put in the
   background. A lower connect_poll_interval may speed up all
   async HTTP transfers, but will also increase CPU usage.

   Default value is “20” milliseconds.

   Example 1.3. Setting the connect_poll_interval parameter
...
modparam("rest_client", "connect_poll_interval", 2)
...

1.4.4. max_async_transfers (integer)

   Maximum number of asynchronous HTTP transfers a single OpenSIPS
   worker is allowed to run simultaneously. As long as this
   threshold is reached for a worker, all new async transfers it
   attempts to perform will be done in a blocking manner, with
   appropriate logging warnings.

   Default value is “100”.

   Example 1.4. Setting the max_async_transfers parameter
...
modparam("rest_client", "max_async_transfers", 300)
...

1.4.5. max_transfer_size (integer)

   The maximum allowed size of a single transfer (download).
   Reaching this limit during a transfer will cause the transfer
   to stop immediately, returning error -10 at script level. A
   value of 0 will disable the check.

   Default value is “10240” (KB).

   Example 1.5. Setting the max_transfer_size parameter
...
modparam("rest_client", "max_transfer_size", 64)
...

1.4.6. ssl_verifypeer (integer)

   Set this to 0 in order to disable the verification of the
   remote peer's certificate. Verification is done using a default
   bundle of CA certificates which come with libcurl.

   Default value is “1” (enabled).

   Example 1.6. Setting the ssl_verifypeer parameter
...
modparam("rest_client", "ssl_verifypeer", 0)
...

1.4.7. ssl_verifyhost (integer)

   Set this to 0 in order to disable the verification that the
   remote peer actually corresponds to the server listed in the
   certificate.

   Default value is “1” (enabled).

   Example 1.7. Setting the ssl_verifyhost parameter
...
modparam("rest_client", "ssl_verifyhost", 0)
...

1.4.8. ssl_capath (integer)

   An optional path for CA certificates to be used for host
   verifications.

   Example 1.8. Setting the ssl_capath parameter
...
modparam("rest_client", "ssl_capath", "/home/opensips/ca_certificates")
...

1.4.9. curl_http_version (integer)

   Use a specific HTTP version for all requests. Possible values:

     * 0 (default) - use whatever is deemed fit by libcurl
     * 1 - enforce HTTP 1.0 requests
     * 2 - enforce HTTP 1.1 requests
     * 3 - attempt HTTP 2 requests. Fall back to HTTP 1.1 if HTTP
       2 cannot be negotiated with the server. Requires libcurl
       7.33.0+.
     * 4 - attempt HTTP 2 over TLS (HTTPS) only. Fall back to HTTP
       1.1 if HTTP 2 cannot be negotiated with the HTTPS server.
       For clear text HTTP servers, use HTTP 1.1. Requires libcurl
       7.47.0+.
     * 5 - Issue non-TLS HTTP requests using HTTP 2 without HTTP
       1.1 Upgrade. It requires prior knowledge that the server
       supports HTTP 2 straight away. HTTPS requests will still do
       HTTP/2 the standard way with negotiated protocol version in
       the TLS handshake. Requires libcurl 7.49.0+.

   more details here, where the documentation for this setting was
   inspired (read: pilfered) from

   Example 1.9. Setting the curl_http_version parameter
...
modparam("rest_client", "curl_http_version", 3)
...

1.4.10. enable_expect_100 (boolean)

   Include a "Expect: 100-continue" HTTP header field whenever the
   body size of a POST or PUT request exceeds 1024 bytes. Once
   enabled, the timeout for waiting for a "100 Continue" reply
   from the server is 1 second, after which the body upload will
   begin.

   Default value is “false” (disabled).

   Example 1.10. Setting the enable_expect_100 parameter
...
modparam("rest_client", "enable_expect_100", true)
...

1.4.11. no_concurrent_connects (boolean)

   Set to true in order to only allow one OpenSIPS worker to
   connect to a given URL hostname at a time. While a worker is
   connecting, all other workers will receive error code -4
   (already connecting) when attempting to perform any rest_client
   operation to the same hostname, regardless if the operation is
   sync or async.

   For sync transfers, the scope of the worker process
   serialization extends to the entire cURL transfer (TCP connect
   + upload + download), as all three phases take place within a
   single cURL library call.

   This parameter may be useful in order to prevent system outages
   caused by concurrent blocking of all OpenSIPS workers on a
   failed (hanging) HTTP service, with no more free workers being
   left to process incoming SIP packets.

   Default value is “false” (disabled).

   Example 1.11. Setting the no_concurrent_connects parameter
...
modparam("rest_client", "no_concurrent_connects", true)
...

1.4.12. curl_conn_lifetime (integer)

   Only relevant when no_concurrent_connects is enabled. By
   setting this parameter, script developers can leverage the
   connection reusage capabilities of libcURL and entirely skip
   the "no concurrent transfers" logic on a given SIP worker,
   should that worker already be known to have a TCP connection to
   the target URL hostname (established by a previous rest_xxx()
   function call).

   The parameter denotes the lifetime, in seconds, of TCP
   connections kept within libcURL for reusage, a setting which is
   often operating system dependant, and which may also be
   affected by enabling/disabling keepalives. Consult your
   operating system's and/or libcurl's documentation for further
   information on the max lifetime of your cURL TCP connections.

   Default value is 0 (disabled).

   Example 1.12. Setting the curl_conn_lifetime parameter
...
modparam("rest_client", "curl_conn_lifetime", 1800)
...

1.5. Exported Functions

1.5.1.  rest_get(url, body_pv, [ctype_pv], [retcode_pv])

   Perform a blocking HTTP GET on the given url and return a
   representation of the resource.

   Parameters:
     * url (string)
     * body_pv (var) - output variable which will hold the body of
       the HTTP response.
     * ctype_pv (var, optional) - output variable which will
       contain the value of the "Content-Type:" header of the
       response.
     * retcode_pv (var, optional) - output variable which will
       retain the status code of the HTTP response. A 0 status
       code value means no HTTP reply arrived at all.

   Return Codes
     * 1 - Success
     * -1 - Connection Refused.
     * -2 - Connection Timeout (the connection_timeout was
       exceeded before a TCP connection could be established)
     * -3 - Transfer Timeout (the curl_timeout was exceeded before
       the last byte was received). The retcode_pv may be set to
       200 or 0, depending whether a 200 OK was received or not.
       If it was, the body_pv will contain partially downloaded
       data, use at your own risk! (we recommend you only use this
       data for logging / debugging purposes)
     * -4 - Already Connecting (another OpenSIPS worker is already
       connecting to this URL hostname. Consult
       no_concurrent_connects for more info).
     * -10 - Internal Error (out of memory, unexpected libcurl
       error, etc.)

   This function can be used from any route.

   Example 1.13. rest_get usage
...
# Example of querying a REST service to get the credit of an account
$var(rc) = rest_get("https://getcredit.org/?account=$fU",
                    $var(credit),
                    $var(ct),
                    $var(rcode));
if ($var(rc) < 0) {
        xlog("rest_get() failed with $var(rc), acc=$fU\n");
        send_reply(500, "Server Internal Error");
        exit;
}

if ($var(rcode) != 200) {
        xlog("L_INFO", "rest_get() rcode=$var(rcode), acc=$fU\n");
        send_reply(403, "Forbidden");
        exit;
}
...

1.5.2.  rest_post(url, send_body, [send_ctype], recv_body_pv,
[recv_ctype_pv], [retcode_pv])

   Perform a blocking HTTP POST on the given url.

   Note that the send_body parameter can also accept a
   format-string but it cannot be larger than 1024 bytes. For
   larger messages, you must build them in a pseudo-variable and
   pass it to the function.

   Parameters:
     * url (string)
     * send_body (string) - The request body.
     * send_ctype (string, optional) - The MIME Content-Type
       header for the request. The default is
       "application/x-www-form-urlencoded"
     * recv_body_pv (var) - output variable which will hold the
       body of the HTTP response.
     * recv_ctype_pv (var, optional) - output variable which will
       contain the value of the "Content-Type" header of the
       response
     * retcode_pv (var, optional) - output variable which will
       retain the status code of the HTTP response. A 0 status
       code value means no HTTP reply arrived at all.

   Return Codes
     * 1 - Success
     * -1 - Connection Refused.
     * -2 - Connection Timeout (the connection_timeout was
       exceeded before a TCP connection could be established)
     * -3 - Transfer Timeout (the curl_timeout was exceeded before
       the last byte was received). The retcode_pv may be set to
       200 or 0, depending whether a 200 OK was received or not.
       If it was, the body_pv will contain partially downloaded
       data, use at your own risk! (we recommend you only use this
       data for logging / debugging purposes)
     * -4 - Already Connecting (another OpenSIPS worker is already
       connecting to this URL hostname. Consult
       no_concurrent_connects for more info).
     * -10 - Internal Error (out of memory, unexpected libcurl
       error, etc.)

   This function can be used from any route.

   Example 1.14. rest_post usage
...
# Creating a resource using a RESTful service with an HTTP POST request
$var(rc) = rest_post("https://myserver.org/register_user",
                     $fU, , $var(body), $var(ct), $var(rcode));
if ($var(rc) < 0) {
        xlog("rest_post() failed with $var(rc), user=$fU\n");
        send_reply(500, "Server Internal Error 1");
        exit;
}

if ($var(rcode) != 200) {
        xlog("rest_post() rcode=$var(rcode), user=$fU\n");
        send_reply(500, "Server Internal Error 2");
        exit;
}
...


1.5.3.  rest_put(url, send_body, [send_ctype], recv_body_pv[,
[recv_ctype_pv][, [retcode_pv]]])

   Perform a blocking HTTP PUT on the given url.

   Similar to rest_post(), the send_body_pv parameter can also
   accept a format-string but it cannot be larger than 1024 bytes.
   For larger messages, you must build them in a pseudo-variable
   and pass it to the function.

   Parameters:
     * url (string)
     * send_body (string) - The request body.
     * send_ctype (string, optional) - The MIME Content-Type
       header for the request. The default is
       "application/x-www-form-urlencoded"
     * recv_body_pv (var) - output variable which will hold the
       body of the HTTP response.
     * recv_ctype_pv (var, optional) - output variable which will
       contain the value of the "Content-Type" header of the
       response
     * retcode_pv (var, optional) - output variable which will
       retain the status code of the HTTP response. A 0 status
       code value means no HTTP reply arrived at all.

   Return Codes
     * 1 - Success
     * -1 - Connection Refused.
     * -2 - Connection Timeout (the connection_timeout was
       exceeded before a TCP connection could be established)
     * -3 - Transfer Timeout (the curl_timeout was exceeded before
       the last byte was received). The retcode_pv may be set to
       200 or 0, depending whether a 200 OK was received or not.
       If it was, the body_pv will contain partially downloaded
       data, use at your own risk! (we recommend you only use this
       data for logging / debugging purposes)
     * -4 - Already Connecting (another OpenSIPS worker is already
       connecting to this URL hostname. Consult
       no_concurrent_connects for more info).
     * -10 - Internal Error (out of memory, unexpected libcurl
       error, etc.)

   This function can be used from any route.

   Example 1.15. rest_put usage
...
# Creating/Updating a resource using a RESTful service with an HTTP PUT
request
$var(rc) = rest_put("https://myserver.org/users/$fU",
                    $var(userinfo), , $var(body), $var(ct), $var(rcode))
;
if ($var(rc) < 0) {
        xlog("rest_put() failed with $var(rc), user=$fU\n");
        send_reply(500, "Server Internal Error 3");
        exit;
}

if ($var(rcode) != 200) {
        xlog("rest_put() rcode=$var(rcode), user=$fU\n");
        send_reply(500, "Server Internal Error 4");
        exit;
}
...

1.5.4.  rest_append_hf(txt)

   Append txt to the HTTP headers of the subsequent request.
   Multiple headers can be appended by making multiple calls
   before executing a request.

   The contents of txt should adhere to the specification for HTTP
   headers (ex. Field: Value)

   Parameters
     * txt (string)

   This function can be used from any route.

   Example 1.16. rest_append_hf usage
...
# Example of querying a REST service requiring additional headers

rest_append_hf("Authorization: Bearer mF_9.B5f-4.1JqM");
$var(rc) = rest_get("http://getcredit.org/?account=$fU", $var(credit));
...

1.5.5.  rest_init_client_tls(tls_client_domain)

   Force a specific TLS domain to be used at most once, during the
   next GET/POST/PUT request. Refer to the tls_mgm module for
   additional info regarding TLS client domains.

   If using this function, you must also ensure that tls_mgm is
   loaded and properly configured.

   Parameters
     * tls_client_domain (string)

   This function can be used from any route.

   Example 1.17. rest_init_client_tls usage
...
rest_init_client_tls("dom1");
if (!rest_get("https://example.com"))
    xlog("query failed\n");
...

1.6. Exported Asynchronous Functions

1.6.1.  rest_get(url, body_pv[, [ctype_pv][, [retcode_pv]]])

   Perform an asynchronous HTTP GET. This function behaves exactly
   the same as rest_get() (in terms of input, output and
   processing), but in a non-blocking manner. Script execution is
   suspended until the entire content of the HTTP response is
   available.

   Example 1.18. async rest_get usage
route {
        ...
        async(rest_get("http://getcredit.org/?account=$fU",
                       $var(credit), , $var(rcode)), resume);
}

route [resume] {
        $var(rc) = $rc;
        if ($var(rc) < 0) {
                xlog("async rest_get() failed with $var(rc), acc=$fU\n")
;
                send_reply(500, "Server Internal Error");
                exit;
        }

        if ($var(rcode) != 200) {
                xlog("L_INFO", "async rest_get() rcode=$var(rcode), acc=
$fU\n");
                send_reply(403, "Forbidden");
                exit;
        }

        ...
}

1.6.2.  rest_post(url, send_body_pv, [send_ctype_pv], recv_body_pv[,
[recv_ctype_pv][, [retcode_pv]]])

   Perform an asynchronous HTTP POST. This function behaves
   exactly the same as rest_post() (in terms of input, output and
   processing), but in a non-blocking manner. Script execution is
   suspended until the entire content of the HTTP response is
   available.

   Example 1.19. async rest_post usage
route {
        ...
        async(rest_post("http://myserver.org/register_user",
                        $fU, , $var(body), $var(ct), $var(rcode)), resum
e);
}

route [resume] {
        $var(rc) = $rc;
        if ($var(rc) < 0) {
                xlog("async rest_post() failed with $var(rc), user=$fU\n
");
                send_reply(500, "Server Internal Error 1");
                exit;
        }
        if ($var(rcode) != 200) {
                xlog("async rest_post() rcode=$var(rcode), user=$fU\n");
                send_reply(500, "Server Internal Error 2");
                exit;
        }

        ...
}


1.6.3.  rest_put(url, send_body_pv, [send_ctype_pv], recv_body_pv[,
[recv_ctype_pv][, [retcode_pv]]])

   Perform an asynchronous HTTP PUT. This function behaves exactly
   the same as rest_put() (in terms of input, output and
   processing), but in a non-blocking manner. Script execution is
   suspended until the entire content of the HTTP response is
   available.

   Example 1.20. async rest_put usage
route {
        ...
        async(rest_put("http://myserver.org/users/$fU", $var(userinfo),
,
                       $var(body), $var(ct), $var(rcode)), resume);
}

route [resume] {
        $var(rc) = $rc;
        if ($var(rc) < 0) {
                xlog("async rest_put() failed with $var(rc), user=$fU\n"
);
                send_reply(500, "Server Internal Error 3");
                exit;
        }
        if ($var(rcode) != 200) {
                xlog("async rest_put() rcode=$var(rcode), user=$fU\n");
                send_reply(500, "Server Internal Error 4");
                exit;
        }

        ...
}

1.7. Exported script transformations

   The module also provides a way for encoding and decoding
   parameters contained in an arbitrary script variable, in
   accordance with RFC3986. This is done by applying a
   transformation to a script variable containing the data to be
   encoded. The value of the original variable is not altered and
   a corresponding string value is returned. The transformation is
   performed through libcurl API method curl_easy_escape (or
   curl_escape for libcurl < 7.15.4).

1.7.1.  {rest.escape}

   The result of this transformation is to produce percent encoded
   string value which can be safely used in URI construction.

   There are no parameters for this transformation.

   Example 1.21. rest.escape usage
...
# This example would produce log entry: "Output: call%40example.com%26sa
fe%3Dfalse"
$var(tmp) = "call@example.com&safe=false";
xlog("Output: $(var(tmp){rest.escape})\n");

# Encode call ID before transmission:
$var(rc) = rest_get("https://call-info.org/?id=$(ci{rest.escape})", $var
(body_pv));
...

1.7.2.  {rest.unescape}

   The result of this transformation is to decode percent encoded
   string values.

   There are no parameters for this transformation.

   Example 1.22. rest.unescape usage
...
# This example would produce log entry: "Output: 1+1=2!"
$var(tmp) = "1%2B1%3D2%21";
xlog("Output: $(var(tmp){rest.unescape})\n");

# This example would produce log entry: "OpenSIPs, tastes better with ev
ery SIP!"
$var(tmp) = "OpenSIPs%2C%20tastes%20better%20with%20every%20SIP%21";
xlog("$(var(tmp){rest.unescape})\n");
...

Chapter 2. Contributors

2.1. By Commit Statistics

   Table 2.1. Top contributors by DevScore^(1), authored
   commits^(2) and lines added/removed^(3)
     Name DevScore Commits Lines ++ Lines --
   1. Liviu Chircu (@liviuchircu) 150 86 4034 1785
   2. Ionut Ionita (@ionutrazvanionita) 23 12 663 262
   3. Vlad Patrascu (@rvlad-patrascu) 17 8 336 345
   4. Razvan Crainea (@razvancrainea) 15 13 41 17
   5. Bogdan-Andrei Iancu (@bogdan-iancu) 7 5 114 47
   6. Jarrod Baumann (@jarrodb) 6 3 131 32
   7. Agalya Ramachandran (@AgalyaR) 6 2 354 1
   8. Callum Guy (@spacetourist) 6 2 281 8
   9. Ryan Bullock (@rrb3942) 5 2 91 77
   10. Aron Podrigal (@ar45) 4 2 15 7

   All remaining contributors: Maksym Sobolyev (@sobomax), John
   Burke (@john08burke), Peter Lemenkov (@lemenkov), Andrey
   Vorobiev (@andrey-vorobiev).

   (1) DevScore = author_commits + author_lines_added /
   (project_lines_added / project_commits) + author_lines_deleted
   / (project_lines_deleted / project_commits)

   (2) including any documentation-related commits, excluding
   merge commits. Regarding imported patches/code, we do our best
   to count the work on behalf of the proper owner, as per the
   "fix_authors" and "mod_renames" arrays in
   opensips/doc/build-contrib.sh. If you identify any
   patches/commits which do not get properly attributed to you,
   please submit a pull request which extends "fix_authors" and/or
   "mod_renames".

   (3) ignoring whitespace edits, renamed files and auto-generated
   files

2.2. By Commit Activity

   Table 2.2. Most recently active contributors^(1) to this module
                      Name                   Commit Activity
   1.  Liviu Chircu (@liviuchircu)         Mar 2013 - Sep 2024
   2.  Aron Podrigal (@ar45)               Sep 2024 - Sep 2024
   3.  Maksym Sobolyev (@sobomax)          Oct 2020 - Feb 2023
   4.  Vlad Patrascu (@rvlad-patrascu)     May 2017 - May 2021
   5.  John Burke (@john08burke)           Apr 2021 - Apr 2021
   6.  Callum Guy (@spacetourist)          Jan 2020 - Jan 2020
   7.  Razvan Crainea (@razvancrainea)     Aug 2015 - Nov 2019
   8.  Bogdan-Andrei Iancu (@bogdan-iancu) Oct 2014 - Apr 2019
   9.  Peter Lemenkov (@lemenkov)          Jun 2018 - Jun 2018
   10. Ionut Ionita (@ionutrazvanionita)   Feb 2017 - Mar 2017

   All remaining contributors: Andrey Vorobiev (@andrey-vorobiev),
   Ryan Bullock (@rrb3942), Agalya Ramachandran (@AgalyaR), Jarrod
   Baumann (@jarrodb).

   (1) including any documentation-related commits, excluding
   merge commits

Chapter 3. Documentation

3.1. Contributors

   Last edited by: Liviu Chircu (@liviuchircu), Callum Guy
   (@spacetourist), Vlad Patrascu (@rvlad-patrascu), Peter
   Lemenkov (@lemenkov), Razvan Crainea (@razvancrainea), Agalya
   Ramachandran (@AgalyaR), Jarrod Baumann (@jarrodb),
   Bogdan-Andrei Iancu (@bogdan-iancu).

   Documentation Copyrights:

   Copyright © 2013 www.opensips-solutions.com
