aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2018-08-30 14:23:39 -0700
committerGarrett D'Amore <garrett@damore.org>2018-08-30 15:28:43 -0700
commit0cc96c69b3fce09a9c4a8d467d17cfbda76bc02f (patch)
tree855f6995749e56f4a9e69ce7d19437b780d63ba2
parentc96b7665469679563ee642a42d175aa24a957f26 (diff)
downloadnng-0cc96c69b3fce09a9c4a8d467d17cfbda76bc02f.tar.gz
nng-0cc96c69b3fce09a9c4a8d467d17cfbda76bc02f.tar.bz2
nng-0cc96c69b3fce09a9c4a8d467d17cfbda76bc02f.zip
fixes #681 HTTP convenience GET method desired...
This adds a couple of new methods, and related documentation and test cases.
-rw-r--r--docs/man/CMakeLists.txt4
-rw-r--r--docs/man/libnng.3.adoc16
-rw-r--r--docs/man/nng_http_client_transact.3http.adoc89
-rw-r--r--docs/man/nng_http_conn_read_res.3http.adoc8
-rw-r--r--docs/man/nng_http_conn_transact.3http.adoc98
-rw-r--r--docs/man/nng_http_conn_write_req.3http.adoc8
-rw-r--r--docs/man/nng_http_conn_write_res.3http.adoc8
-rw-r--r--docs/man/nng_http_req_get_data.3http.adoc50
-rw-r--r--docs/man/nng_http_res_get_data.3http.adoc50
-rw-r--r--src/supplemental/http/http.h22
-rw-r--r--src/supplemental/http/http_api.h17
-rw-r--r--src/supplemental/http/http_client.c254
-rw-r--r--src/supplemental/http/http_msg.c14
-rw-r--r--src/supplemental/http/http_public.c56
-rw-r--r--tests/httpclient.c107
15 files changed, 793 insertions, 8 deletions
diff --git a/docs/man/CMakeLists.txt b/docs/man/CMakeLists.txt
index af644a62..21651ba5 100644
--- a/docs/man/CMakeLists.txt
+++ b/docs/man/CMakeLists.txt
@@ -198,11 +198,13 @@ if (NNG_ENABLE_DOC)
nng_http_client_free
nng_http_client_get_tls
nng_http_client_set_tls
+ nng_http_client_transact
nng_http_conn_close
nng_http_conn_read
nng_http_conn_read_all
nng_http_conn_read_req
nng_http_conn_read_res
+ nng_http_conn_transact
nng_http_conn_write
nng_http_conn_write_all
nng_http_conn_write_req
@@ -220,6 +222,7 @@ if (NNG_ENABLE_DOC)
nng_http_req_copy_data
nng_http_req_del_header
nng_http_req_free
+ nng_http_req_get_data
nng_http_req_get_header
nng_http_req_get_method
nng_http_req_get_uri
@@ -235,6 +238,7 @@ if (NNG_ENABLE_DOC)
nng_http_res_copy_data
nng_http_res_del_header
nng_http_res_free
+ nng_http_res_get_data
nng_http_res_get_header
nng_http_res_get_reason
nng_http_res_get_status
diff --git a/docs/man/libnng.3.adoc b/docs/man/libnng.3.adoc
index 23306997..3e3037f9 100644
--- a/docs/man/libnng.3.adoc
+++ b/docs/man/libnng.3.adoc
@@ -287,6 +287,7 @@ and connections.
|<<nng_http_req_copy_data.3http#,nng_http_req_copy_data()>>|copy HTTP request body
|<<nng_http_req_del_header.3http#,nng_http_req_del_header()>>|delete HTTP request header
|<<nng_http_req_free.3http#,nng_http_req_free()>>|free HTTP request structure
+|<<nng_http_req_get_data.3http#,nng_http_req_get_data()>>|get HTTP request body
|<<nng_http_req_get_header.3http#,nng_http_req_get_header()>>|return HTTP request header
|<<nng_http_req_get_method.3http#,nng_http_req_get_method()>>|return HTTP request method
|<<nng_http_req_get_uri.3http#,nng_http_req_get_uri()>>|return HTTP request URI
@@ -302,11 +303,12 @@ and connections.
|<<nng_http_res_copy_data.3http#,nng_http_res_copy_data()>>|copy HTTP response body
|<<nng_http_res_del_header.3http#,nng_http_res_del_header()>>|delete HTTP response header
|<<nng_http_res_free.3http#,nng_http_res_free()>>|free HTTP response structure
-|<<nng_http_res_set_data.3http#,nng_http_res_set_data()>>|set HTTP response body
+|<<nng_http_res_get_data.3http#,nng_http_res_get_data()>>|get HTTP response body
|<<nng_http_res_get_header.3http#,nng_http_res_get_header()>>|return HTTP response header
|<<nng_http_res_get_reason.3http#,nng_http_res_get_reason()>>|return HTTP response reason
|<<nng_http_res_get_status.3http#,nng_http_res_get_status()>>|return HTTP response status
|<<nng_http_res_get_version.3http#,nng_http_res_get_version()>>|return HTTP response protocol version
+|<<nng_http_res_set_data.3http#,nng_http_res_set_data()>>|set HTTP response body
|<<nng_http_res_set_header.3http#,nng_http_res_set_header()>>|set HTTP response header
|<<nng_http_res_set_reason.3http#,nng_http_res_set_reason()>>|set HTTP response reason
|<<nng_http_res_set_status.3http#,nng_http_res_set_status()>>|set HTTP response status
@@ -318,11 +320,13 @@ and connections.
These functions are intended for use with HTTP client applications.
|===
-| <<nng_http_client_alloc.3http#,nng_http_client_alloc()>>|allocate HTTP client
-| <<nng_http_client_connect.3http#,nng_http_client_connect()>>|establish HTTP client connection
-| <<nng_http_client_free.3http#,nng_http_client_free()>>|free HTTP client
-| <<nng_http_client_get_tls.3http#,nng_http_client_get_tls()>>|get HTTP client TLS configuration
-| <<nng_http_client_set_tls.3http#,nng_http_client_set_tls()>>|set HTTP client TLS configuration
+|<<nng_http_client_alloc.3http#,nng_http_client_alloc()>>|allocate HTTP client
+|<<nng_http_client_connect.3http#,nng_http_client_connect()>>|establish HTTP client connection
+|<<nng_http_client_free.3http#,nng_http_client_free()>>|free HTTP client
+|<<nng_http_client_get_tls.3http#,nng_http_client_get_tls()>>|get HTTP client TLS configuration
+|<<nng_http_client_set_tls.3http#,nng_http_client_set_tls()>>|set HTTP client TLS configuration
+|<<nng_http_client_transact.3http#,nng_http_client_transact()>>|perform one HTTP transaction
+|<<nng_http_conn_transact.3http#,nng_http_conn_transact()>>|perform one HTTP transaction on connection
|===
==== HTTP Server Functions
diff --git a/docs/man/nng_http_client_transact.3http.adoc b/docs/man/nng_http_client_transact.3http.adoc
new file mode 100644
index 00000000..a469dc50
--- /dev/null
+++ b/docs/man/nng_http_client_transact.3http.adoc
@@ -0,0 +1,89 @@
+= nng_http_client_transact(3http)
+//
+// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2018 Capitar IT Group BV <info@capitar.com>
+//
+// This document is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+== NAME
+
+nng_http_client_transact - perform one HTTP transaction
+
+== SYNOPSIS
+
+[source, c]
+----
+#include <nng/nng.h>
+#include <nng/supplemental/http/http.h>
+
+void nng_http_client_transact(nng_http_client *client, nng_http_req *req,
+ nng_http_res *res, nng_aio *aio);
+----
+
+== DESCRIPTION
+
+The `nng_http_client_transact()` function is used to perform a complete
+HTTP exchange.
+It creates a new connection using _client_, performs the transaction by
+sending the request _req_
+(and attached body data) to the remote server, then reading the response
+_res_, and finally closes the connection that it created.
+The entire response is read, including any associated body, which can
+subsequently be obtained using
+`<<nng_http_res_get_data.3http#,nng_http_res_get_data()>>`.
+
+This function is intended to make creation of client applications easier,
+by performing multiple asynchronous operations required to complete an
+entire HTTP transaction.
+
+A similar function,
+`<<nng_http_conn_transact.3http#,nng_http_conn_transact()>>`,
+exists.
+That function behaves similarily, but uses an existing connection, which
+can be reused.
+
+NOTE: This function does not support reading data sent using chunked
+transfer encoding, and if the server attempts to do so, the underlying
+connection will be closed and an `NNG_ENOTSUP` error will be returned.
+This limitation is considered a bug, and a fix is planned for the future.
+
+WARNING: If the remote server tries to send an extremely large buffer,
+then a corresponding allocation will be made, which can lead to denial
+of service attacks.
+Client applications should take care to use this only with reasonably
+trust-worthy servers.
+
+This function returns immediately, with no return value.
+Completion of the operation is signaled via the _aio_, and the final result
+may be obtained via `<<nng_aio_result.3#,nng_aio_result()>>`.
+That result will either be zero or an error code.
+
+== RETURN VALUES
+
+None.
+
+== ERRORS
+
+[horizontal]
+`NNG_ECANCELED`:: The operation was canceled.
+`NNG_ECLOSED`:: The connection was closed.
+`NNG_ECONNRESET`:: The peer closed the connection.
+`NNG_ENOMEM`:: Insufficient free memory to perform the operation.
+`NNG_ENOTSUP`:: HTTP operations are not supported, or peer sent chunked encoding.
+`NNG_EPROTO`:: An HTTP protocol error occurred.
+`NNG_ETIMEDOUT`:: Timeout waiting for data from the connection.
+
+== SEE ALSO
+
+[.text-left]
+<<nng_aio_alloc.3#,nng_aio_alloc(3)>>,
+<<nng_aio_result.3#,nng_aio_result(3)>>,
+<<nng_strerror.3#,nng_strerror(3)>>,
+<<nng_http_client_connect.3http#,nng_http_client_connect(3http)>>,
+<<nng_http_conn_transact.3http#,nng_http_conn_transact(3http)>>,
+<<nng_http_res_get_data.3http#,nng_http_res_get_data(3http)>>,
+<<nng.7#,nng(7)>>
diff --git a/docs/man/nng_http_conn_read_res.3http.adoc b/docs/man/nng_http_conn_read_res.3http.adoc
index 4e2f2713..96e64ed6 100644
--- a/docs/man/nng_http_conn_read_res.3http.adoc
+++ b/docs/man/nng_http_conn_read_res.3http.adoc
@@ -42,6 +42,12 @@ the operation is signaled via the _aio_, and the final result may be
obtained via `<<nng_aio_result.3#,nng_aio_result()>>`.
That result will either be zero or an error code.
+NOTE: Consider using the
+`<<nng_http_client_transact.3http#,nng_http_client_transact()>>` or
+`<<nng_http_conn_transact.3http#,nng_http_conn_transact()>>` functions,
+which provide a simpler interface for performing a complete HTTP client
+transaction.
+
== RETURN VALUES
None.
@@ -63,5 +69,7 @@ None.
<<nng_aio_result.3#,nng_aio_result(3)>>,
<<nng_strerror.3#,nng_strerror(3)>>,
<<nng_http_client_connect.3http#,nng_http_client_connect(3http)>>,
+<<nng_http_client_transact.3http#,nng_http_client_transact(3http)>>,
+<<nng_http_conn_transact.3http#,nng_http_conn_transact(3http)>>,
<<nng_http_conn_read_all.3http#,nng_http_conn_read_all(3http)>>,
<<nng.7#,nng(7)>>
diff --git a/docs/man/nng_http_conn_transact.3http.adoc b/docs/man/nng_http_conn_transact.3http.adoc
new file mode 100644
index 00000000..bd41f659
--- /dev/null
+++ b/docs/man/nng_http_conn_transact.3http.adoc
@@ -0,0 +1,98 @@
+= nng_http_conn_transact(3http)
+//
+// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2018 Capitar IT Group BV <info@capitar.com>
+//
+// This document is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+== NAME
+
+nng_http_conn_transact - perform one HTTP transaction on connection
+
+== SYNOPSIS
+
+[source, c]
+----
+#include <nng/nng.h>
+#include <nng/supplemental/http/http.h>
+
+void nng_http_conn_transact(nng_http_conn *conn, nng_http_req *req,
+ nng_http_res *res, nng_aio *aio);
+----
+
+== DESCRIPTION
+
+The `nng_http_conn_transact()` function is used to perform a complete
+HTTP exchange over the connection _conn_, sending the request _req_
+(and attached body data) to the remote server, and reading the response
+_res_.
+The entire response is read, including any associated body, which can
+subsequently be obtained using
+`<<nng_http_res_get_data.3http#,nng_http_res_get_data()>>`.
+
+This function is intended to make creation of client applications easier,
+by performing multiple asynchronous operations required to complete an
+entire HTTP transaction.
+
+If an error occurs, the caller should close _conn_ with
+`<<nng_http_conn_close.3http#,nng_http_conn_close()>>`, as it may not
+necessarily be usable with other transactions.
+
+A similar function,
+`<<nng_http_client_transact.3http#,nng_http_client_transact()>>`,
+exists.
+That function behaves similarily, but creates a connection on demand
+for the transaction, and disposes of it when finished.
+
+NOTE: This function does not support reading data sent using chunked
+transfer encoding, and if the server attempts to do so, the underlying
+connection will be closed and an `NNG_ENOTSUP` error will be returned.
+This limitation is considered a bug, and a fix is planned for the future.
+
+WARNING: If the remote server tries to send an extremely large buffer,
+then a corresponding allocation will be made, which can lead to denial
+of service attacks.
+Client applications should take care to use this only with reasonably
+trust-worthy servers.
+
+WARNING: A given connection _conn_ should be used with only one
+operation or transaction at a time as HTTP/1.1 has no support for
+request interleaving.
+
+This function returns immediately, with no return value.
+Completion of the operation is signaled via the _aio_, and the final result
+may be obtained via `<<nng_aio_result.3#,nng_aio_result()>>`.
+That result will either be zero or an error code.
+
+== RETURN VALUES
+
+None.
+
+== ERRORS
+
+[horizontal]
+`NNG_ECANCELED`:: The operation was canceled.
+`NNG_ECLOSED`:: The connection was closed.
+`NNG_ECONNRESET`:: The peer closed the connection.
+`NNG_ENOMEM`:: Insufficient free memory to perform the operation.
+`NNG_ENOTSUP`:: HTTP operations are not supported, or peer sent chunked encoding.
+`NNG_EPROTO`:: An HTTP protocol error occurred.
+`NNG_ETIMEDOUT`:: Timeout waiting for data from the connection.
+
+== SEE ALSO
+
+[.text-left]
+<<nng_aio_alloc.3#,nng_aio_alloc(3)>>,
+<<nng_aio_result.3#,nng_aio_result(3)>>,
+<<nng_strerror.3#,nng_strerror(3)>>,
+<<nng_http_client_connect.3http#,nng_http_client_connect(3http)>>,
+<<nng_http_client_transact.3http#,nng_http_client_transact(3http)>>,
+<<nng_http_conn_read_res.3http#,nng_http_conn_read_res(3http)>>,
+<<nng_http_conn_read_all.3http#,nng_http_conn_read_all(3http)>>,
+<<nng_http_conn_write_req.3http#,nng_http_conn_write_req(3http)>>,
+<<nng_http_res_get_data.3http#,nng_http_res_get_data(3http)>>,
+<<nng.7#,nng(7)>>
diff --git a/docs/man/nng_http_conn_write_req.3http.adoc b/docs/man/nng_http_conn_write_req.3http.adoc
index 0384e7b1..338ab26d 100644
--- a/docs/man/nng_http_conn_write_req.3http.adoc
+++ b/docs/man/nng_http_conn_write_req.3http.adoc
@@ -39,6 +39,12 @@ Completion of the operation is signaled via the _aio_, and the final result
may be obtained via `<<nng_aio_result.3#,nng_aio_result()>>`.
That result will either be zero or an error code.
+NOTE: Consider using the
+`<<nng_http_client_transact.3http#,nng_http_client_transact()>>` or
+`<<nng_http_conn_transact.3http#,nng_http_conn_transact()>>` functions,
+which provide a simpler interface for performing a complete HTTP client
+transaction.
+
== RETURN VALUES
None.
@@ -59,6 +65,8 @@ None.
<<nng_aio_alloc.3#,nng_aio_alloc(3)>>,
<<nng_aio_result.3#,nng_aio_result(3)>>,
<<nng_http_client_connect.3http#,nng_http_client_connect(3http)>>,
+<<nng_http_client_transact.3http#,nng_http_client_transact(3http)>>,
<<nng_http_conn_read_all.3http#,nng_http_conn_read_all(3http)>>,
+<<nng_http_conn_transact.3http#,nng_http_conn_transact(3http)>>,
<<nng_strerror.3#,nng_strerror(3)>>,
<<nng.7#,nng(7)>>
diff --git a/docs/man/nng_http_conn_write_res.3http.adoc b/docs/man/nng_http_conn_write_res.3http.adoc
index 2bab9a31..62d7a3d1 100644
--- a/docs/man/nng_http_conn_write_res.3http.adoc
+++ b/docs/man/nng_http_conn_write_res.3http.adoc
@@ -48,6 +48,12 @@ If however the _res_ contains a header of `Connection:` with a value
of `Close` (case-insensitive) or the response corresponds to `HTTP/1.0`,
then the connection is immediately after sending the response.
+NOTE: Consider using the
+`<<nng_http_client_transact.3http#,nng_http_client_transact()>>` or
+`<<nng_http_conn_transact.3http#,nng_http_conn_transact()>>` functions,
+which provide a simpler interface for performing a complete HTTP client
+transaction.
+
== RETURN VALUES
None.
@@ -68,6 +74,8 @@ None.
<<nng_aio_alloc.3#,nng_aio_alloc(3)>>,
<<nng_aio_result.3#,nng_aio_result(3)>>,
<<nng_http_client_connect.3http#,nng_http_client_connect(3http)>>,
+<<nng_http_client_transact.3http#,nng_http_client_transact(3http)>>,
<<nng_http_conn_read_all.3http#,nng_http_conn_read_all(3http)>>,
+<<nng_http_conn_transact.3http#,nng_http_conn_transact(3http)>>,
<<nng_strerror.3#,nng_strerror(3)>>,
<<nng.7#,nng(7)>>
diff --git a/docs/man/nng_http_req_get_data.3http.adoc b/docs/man/nng_http_req_get_data.3http.adoc
new file mode 100644
index 00000000..cd5c076e
--- /dev/null
+++ b/docs/man/nng_http_req_get_data.3http.adoc
@@ -0,0 +1,50 @@
+= nng_http_req_get_data(3http)
+//
+// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2018 Capitar IT Group BV <info@capitar.com>
+//
+// This document is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+== NAME
+
+nng_http_req_get_data - get HTTP request body
+
+== SYNOPSIS
+
+[source, c]
+----
+#include <nng/nng.h>
+#include <nng/supplemental/http/http.h>
+
+void nng_http_req_get_data(nng_http_req *req, void **bodyp, size_t *sizep);
+----
+
+== DESCRIPTION
+
+The `nng_http_req_get_data()` gets the HTTP body associated with
+the request _req_, storing a pointer to the buffer at the location referenced
+by _bodyp_, and the length of the associated buffer at the location referenced
+by _sizep_.
+
+NOTE: The buffer returned is owned by _req_, and will automatically freed
+when the request is freed.
+
+== RETURN VALUES
+
+None.
+
+== ERRORS
+
+None.
+
+== SEE ALSO
+
+[.text-left]
+<<nng_http_req_alloc.3http#,nng_http_req_alloc(3http)>>,
+<<nng_http_req_set_data.3http#,nng_http_req_copy_data(3http)>>,
+<<nng_http_req_copy_data.3http#,nng_http_req_copy_data(3http)>>,
+<<nng.7#,nng(7)>>
diff --git a/docs/man/nng_http_res_get_data.3http.adoc b/docs/man/nng_http_res_get_data.3http.adoc
new file mode 100644
index 00000000..44f98c1f
--- /dev/null
+++ b/docs/man/nng_http_res_get_data.3http.adoc
@@ -0,0 +1,50 @@
+= nng_http_res_get_data(3http)
+//
+// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2018 Capitar IT Group BV <info@capitar.com>
+//
+// This document is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+== NAME
+
+nng_http_res_get_data - get HTTP response body
+
+== SYNOPSIS
+
+[source, c]
+----
+#include <nng/nng.h>
+#include <nng/supplemental/http/http.h>
+
+void nng_http_res_get_data(nng_http_res *res, void **bodyp, size_t *sizep);
+----
+
+== DESCRIPTION
+
+The `nng_http_res_get_data()` gets the HTTP body associated with
+the request _res_, storing a pointer to the buffer at the location referenced
+by _bodyp_, and the length of the associated buffer at the location referenced
+by _sizep_.
+
+NOTE: The buffer returned is owned by _res_, and will automatically freed
+when the request is freed.
+
+== RETURN VALUES
+
+None.
+
+== ERRORS
+
+None.
+
+== SEE ALSO
+
+[.text-left]
+<<nng_http_res_alloc.3http#,nng_http_req_alloc(3http)>>,
+<<nng_http_res_set_data.3http#,nng_http_req_copy_data(3http)>>,
+<<nng_http_res_copy_data.3http#,nng_http_req_copy_data(3http)>>,
+<<nng.7#,nng(7)>>
diff --git a/src/supplemental/http/http.h b/src/supplemental/http/http.h
index 1991da1b..f6656fce 100644
--- a/src/supplemental/http/http.h
+++ b/src/supplemental/http/http.h
@@ -154,6 +154,9 @@ NNG_DECL int nng_http_req_set_data(nng_http_req *, const void *, size_t);
// probably set the content-type header.
NNG_DECL int nng_http_req_copy_data(nng_http_req *, const void *, size_t);
+// nng_http_req_get_data gets the data for the response.
+NNG_DECL void nng_http_req_get_data(nng_http_req *, void **, size_t *);
+
// nng_http_res represents an HTTP response.
typedef struct nng_http_res nng_http_res;
@@ -208,6 +211,9 @@ NNG_DECL int nng_http_res_set_version(nng_http_res *, const char *);
// nng_http_res_get_version returns the version, usually HTTP/1.1.
NNG_DECL const char *nng_http_res_get_version(nng_http_res *);
+// nng_http_res_get_data gets the data for the response.
+NNG_DECL void nng_http_res_get_data(nng_http_res *, void **, size_t *);
+
// nng_http_res_set_data adds entity data to the response. The
// data object must persist (so only really useful for static data).
// The content-length header is updated as well, but the caller should
@@ -477,6 +483,22 @@ NNG_DECL int nng_http_client_get_tls(
// in the first (index 0) output for the aio.
NNG_DECL void nng_http_client_connect(nng_http_client *, nng_aio *);
+// nng_http_conn_transact is used to perform a round-trip exchange (i.e. a
+// single HTTP transaction). It will not automatically close the connection,
+// unless some kind of significant error occurs. The caller should close
+// the connection if the aio does not complete successfully.
+// Note that this will fail with NNG_ENOTSUP if the server attempts to reply
+// with a chunked transfer encoding.
+NNG_DECL void nng_http_conn_transact(
+ nng_http_conn *, nng_http_req *, nng_http_res *, nng_aio *);
+
+// nng_http_client_transact is used to execute a single transaction to a
+// server. The connection is opened, and will be closed when the transaction is
+// complete. Note that this will fail with NNG_ENOTSUP if the server attempts
+// to reply with a chunked transfer encoding.
+NNG_DECL void nng_http_client_transact(
+ nng_http_client *, nng_http_req *, nng_http_res *, nng_aio *);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/supplemental/http/http_api.h b/src/supplemental/http/http_api.h
index cf2c78bf..71b24f54 100644
--- a/src/supplemental/http/http_api.h
+++ b/src/supplemental/http/http_api.h
@@ -101,6 +101,7 @@ extern int nni_http_req_copy_data(nni_http_req *, const void *, size_t);
extern int nni_http_res_copy_data(nni_http_res *, const void *, size_t);
extern int nni_http_req_set_data(nni_http_req *, const void *, size_t);
extern int nni_http_res_set_data(nni_http_res *, const void *, size_t);
+extern int nni_http_res_alloc_data(nni_http_res *, size_t);
extern const char *nni_http_req_get_method(nni_http_req *);
extern const char *nni_http_req_get_version(nni_http_req *);
extern const char *nni_http_req_get_uri(nni_http_req *);
@@ -306,4 +307,20 @@ extern int nni_http_client_get_tls(
extern void nni_http_client_connect(nni_http_client *, nni_aio *);
+// nni_http_transact_conn is used to perform a round-trip exchange (i.e. a
+// single HTTP transaction). It will not automatically close the connection,
+// unless some kind of significant error occurs. The caller should dispose
+// of the connection if the aio does not complete successfully.
+// Note that this will fail with NNG_ENOTSUP if the server attempts to reply
+// with a chunked transfer encoding.
+extern void nni_http_transact_conn(
+ nni_http_conn *, nni_http_req *, nni_http_res *, nni_aio *);
+
+// nni_http_transact is used to execute a single transaction to a server.
+// The connection is opened, and will be closed when the transaction is
+// complete. Note that this will fail with NNG_ENOTSUP if the server attempts
+// to reply with a chunked transfer encoding.
+extern void nni_http_transact(
+ nni_http_client *, nni_http_req *, nni_http_res *, nni_aio *);
+
#endif // NNG_SUPPLEMENTAL_HTTP_HTTP_API_H
diff --git a/src/supplemental/http/http_client.c b/src/supplemental/http/http_client.c
index 1639b3ec..f8b1c8ab 100644
--- a/src/supplemental/http/http_client.c
+++ b/src/supplemental/http/http_client.c
@@ -19,6 +19,8 @@
#include "http_api.h"
+static nni_mtx http_txn_lk;
+
struct nng_http_client {
nni_list aios;
nni_mtx mtx;
@@ -265,3 +267,255 @@ nni_http_client_connect(nni_http_client *c, nni_aio *aio)
}
nni_mtx_unlock(&c->mtx);
}
+
+static int http_client_sys_init(void);
+static void http_client_sys_fini(void);
+
+static nni_initializer http_client_initializer = {
+ .i_init = http_client_sys_init,
+ .i_fini = http_client_sys_fini,
+ .i_once = 0,
+};
+
+typedef enum http_txn_state {
+ HTTP_CONNECTING,
+ HTTP_SENDING,
+ HTTP_RECVING,
+ HTTP_RECVING_BODY,
+} http_txn_state;
+
+typedef struct http_txn {
+ nni_aio * aio; // lower level aio
+ nni_list aios; // upper level aio(s) -- maximum one
+ nni_http_client *client;
+ nni_http_conn * conn;
+ nni_http_req * req;
+ nni_http_res * res;
+ http_txn_state state;
+ nni_reap_item reap;
+} http_txn;
+
+static void
+http_txn_reap(void *arg)
+{
+ http_txn *txn = arg;
+ if (txn->client != NULL) {
+ // We only close the connection if we created it.
+ if (txn->conn != NULL) {
+ nni_http_conn_fini(txn->conn);
+ }
+ }
+ nni_aio_fini(txn->aio);
+ NNI_FREE_STRUCT(txn);
+}
+
+static void
+http_txn_cb(void *arg)
+{
+ http_txn * txn = arg;
+ const char *str;
+ nni_aio * aio;
+ int rv;
+ uint64_t len;
+ nni_iov iov;
+
+ nni_mtx_lock(&http_txn_lk);
+ if ((rv = nni_aio_result(txn->aio)) != 0) {
+ while ((aio = nni_list_first(&txn->aios)) != NULL) {
+ nni_list_remove(&txn->aios, aio);
+ nni_aio_finish_error(aio, rv);
+ }
+ nni_mtx_unlock(&http_txn_lk);
+ nni_reap(&txn->reap, http_txn_reap, txn);
+ return;
+ }
+ switch (txn->state) {
+ case HTTP_CONNECTING:
+ txn->conn = nni_aio_get_output(txn->aio, 0);
+ txn->state = HTTP_SENDING;
+ nni_http_write_req(txn->conn, txn->req, txn->aio);
+ nni_mtx_unlock(&http_txn_lk);
+ return;
+
+ case HTTP_SENDING:
+ txn->state = HTTP_RECVING;
+ nni_http_read_res(txn->conn, txn->res, txn->aio);
+ nni_mtx_unlock(&http_txn_lk);
+ return;
+
+ case HTTP_RECVING:
+ if (((str = nni_http_res_get_header(
+ txn->res, "Transfer-Encoding")) != NULL) &&
+ (strstr(str, "chunked") != NULL)) {
+ // We refuse to receive chunked encoding data.
+ // This is an implementation limitation, but as HTTP/2
+ // has eliminated this encoding, maybe it's not that
+ // big of a deal. We forcibly close this.
+ while ((aio = nni_list_first(&txn->aios)) != NULL) {
+ nni_list_remove(&txn->aios, aio);
+ nni_aio_finish_error(aio, NNG_ENOTSUP);
+ }
+ nni_http_conn_close(txn->conn);
+ nni_mtx_unlock(&http_txn_lk);
+ nni_reap(&txn->reap, http_txn_reap, txn);
+ return;
+ }
+ str = nni_http_req_get_method(txn->req);
+ if ((nni_strcasecmp(str, "HEAD") == 0) ||
+ ((str = nni_http_res_get_header(
+ txn->res, "Content-Length")) == NULL) ||
+ (nni_strtou64(str, &len) != 0) || (len == 0)) {
+ // If no content-length, or HEAD (which per RFC
+ // never transfers data), then we are done.
+ while ((aio = nni_list_first(&txn->aios)) != NULL) {
+ nni_list_remove(&txn->aios, aio);
+ nni_aio_finish(aio, 0, 0);
+ }
+ nni_mtx_unlock(&http_txn_lk);
+ nni_reap(&txn->reap, http_txn_reap, txn);
+ return;
+ }
+
+ nni_http_res_alloc_data(txn->res, (size_t) len);
+ nni_http_res_get_data(txn->res, &iov.iov_buf, &iov.iov_len);
+ nni_aio_set_iov(txn->aio, 1, &iov);
+ txn->state = HTTP_RECVING_BODY;
+ nni_http_read_full(txn->conn, txn->aio);
+ nni_mtx_unlock(&http_txn_lk);
+ return;
+
+ case HTTP_RECVING_BODY:
+ // All done!
+ while ((aio = nni_list_first(&txn->aios)) != NULL) {
+ nni_list_remove(&txn->aios, aio);
+ nni_aio_finish(aio, 0, 0);
+ }
+ nni_mtx_unlock(&http_txn_lk);
+ nni_reap(&txn->reap, http_txn_reap, txn);
+ return;
+ }
+
+ NNI_ASSERT(0); // Unknown state!
+}
+
+static void
+http_txn_cancel(nni_aio *aio, void *arg, int rv)
+{
+ http_txn *txn = arg;
+ nni_mtx_lock(&http_txn_lk);
+ if (nni_aio_list_active(aio)) {
+ nni_aio_abort(txn->aio, rv);
+ }
+ nni_mtx_unlock(&http_txn_lk);
+}
+
+// nni_http_transact_conn sends a request to an HTTP server, and reads the
+// response. It also attempts to read any associated data. Note that
+// at present it can only read data that comes in normally, as support
+// for Chunked Transfer Encoding is missing. Note that cancelling the aio
+// is generally fatal to the connection.
+void
+nni_http_transact_conn(
+ nni_http_conn *conn, nni_http_req *req, nni_http_res *res, nni_aio *aio)
+{
+ http_txn *txn;
+ int rv;
+
+ nni_initialize(&http_client_initializer);
+
+ if (nni_aio_begin(aio) != 0) {
+ return;
+ }
+ if ((txn = NNI_ALLOC_STRUCT(txn)) == NULL) {
+ nni_aio_finish_error(aio, NNG_ENOMEM);
+ return;
+ }
+ if ((rv = nni_aio_init(&txn->aio, http_txn_cb, txn)) != 0) {
+ NNI_FREE_STRUCT(txn);
+ nni_aio_finish_error(aio, rv);
+ return;
+ }
+ nni_aio_list_init(&txn->aios);
+ txn->client = NULL;
+ txn->conn = conn;
+ txn->req = req;
+ txn->res = res;
+ txn->state = HTTP_SENDING;
+
+ nni_mtx_lock(&http_txn_lk);
+ if ((rv = nni_aio_schedule(aio, http_txn_cancel, txn)) != 0) {
+ nni_mtx_unlock(&http_txn_lk);
+ nni_aio_finish_error(aio, rv);
+ nni_reap(&txn->reap, http_txn_reap, txn);
+ return;
+ }
+ nni_http_res_reset(txn->res);
+ nni_list_append(&txn->aios, aio);
+ nni_http_write_req(conn, req, txn->aio);
+ nni_mtx_unlock(&http_txn_lk);
+}
+
+// nni_http_transact_simple does a single transaction, creating a connection
+// just for the purpose, and closing it when done. (No connection caching.)
+// The reason we require a client to be created first is to deal with TLS
+// settings. A single global client (per server) may be used.
+void
+nni_http_transact(nni_http_client *client, nni_http_req *req,
+ nni_http_res *res, nni_aio *aio)
+{
+ http_txn *txn;
+ int rv;
+
+ nni_initialize(&http_client_initializer);
+
+ if (nni_aio_begin(aio) != 0) {
+ return;
+ }
+ if ((txn = NNI_ALLOC_STRUCT(txn)) == NULL) {
+ nni_aio_finish_error(aio, NNG_ENOMEM);
+ return;
+ }
+ if ((rv = nni_aio_init(&txn->aio, http_txn_cb, txn)) != 0) {
+ NNI_FREE_STRUCT(txn);
+ nni_aio_finish_error(aio, rv);
+ return;
+ }
+
+ if ((rv = nni_http_req_set_header(req, "Connection", "close")) != 0) {
+ nni_aio_finish_error(aio, rv);
+ nni_reap(&txn->reap, http_txn_reap, txn);
+ return;
+ }
+
+ nni_aio_list_init(&txn->aios);
+ txn->client = NULL;
+ txn->conn = NULL;
+ txn->req = req;
+ txn->res = res;
+ txn->state = HTTP_CONNECTING;
+
+ nni_mtx_lock(&http_txn_lk);
+ if ((rv = nni_aio_schedule(aio, http_txn_cancel, txn)) != 0) {
+ nni_mtx_unlock(&http_txn_lk);
+ nni_aio_finish_error(aio, rv);
+ nni_reap(&txn->reap, http_txn_reap, txn);
+ return;
+ }
+ nni_http_res_reset(txn->res);
+ nni_list_append(&txn->aios, aio);
+ nni_http_client_connect(client, txn->aio);
+ nni_mtx_unlock(&http_txn_lk);
+}
+
+static int
+http_client_sys_init(void)
+{
+ nni_mtx_init(&http_txn_lk);
+ return (0);
+}
+
+static void
+http_client_sys_fini(void)
+{
+ nni_mtx_fini(&http_txn_lk);
+}
diff --git a/src/supplemental/http/http_msg.c b/src/supplemental/http/http_msg.c
index d6ab862e..dcd842c5 100644
--- a/src/supplemental/http/http_msg.c
+++ b/src/supplemental/http/http_msg.c
@@ -399,6 +399,20 @@ nni_http_res_copy_data(nni_http_res *res, const void *data, size_t size)
return (0);
}
+// nni_http_res_alloc_data allocates the data region, but does not update any
+// headers. The intended use is for client implementations that want to
+// allocate a buffer to receive the entity into.
+int
+nni_http_res_alloc_data(nni_http_res *res, size_t size)
+{
+ int rv;
+
+ if ((rv = http_entity_alloc_data(&res->data, size)) != 0) {
+ return (rv);
+ }
+ return (0);
+}
+
bool
nni_http_res_is_error(nni_http_res *res)
{
diff --git a/src/supplemental/http/http_public.c b/src/supplemental/http/http_public.c
index f275db28..84811e54 100644
--- a/src/supplemental/http/http_public.c
+++ b/src/supplemental/http/http_public.c
@@ -226,6 +226,30 @@ nng_http_res_set_data(nng_http_res *res, const void *data, size_t sz)
#endif
}
+void
+nng_http_req_get_data(nng_http_req *req, void **datap, size_t *lenp)
+{
+#ifdef NNG_SUPP_HTTP
+ nni_http_req_get_data(req, datap, lenp);
+#else
+ NNI_ARG_UNUSED(req);
+ *datap = NULL;
+ *lenp = 0;
+#endif
+}
+
+void
+nng_http_res_get_data(nng_http_res *res, void **datap, size_t *lenp)
+{
+#ifdef NNG_SUPP_HTTP
+ nni_http_res_get_data(res, datap, lenp);
+#else
+ NNI_ARG_UNUSED(res);
+ *datap = NULL;
+ *lenp = 0;
+#endif
+}
+
const char *
nng_http_req_get_method(nng_http_req *req)
{
@@ -811,3 +835,35 @@ nng_http_client_connect(nng_http_client *cli, nng_aio *aio)
}
#endif
}
+
+void
+nng_http_client_transact(
+ nng_http_client *cli, nng_http_req *req, nng_http_res *res, nng_aio *aio)
+{
+#ifdef NNG_SUPP_HTTP
+ nni_http_transact(cli, req, res, aio);
+#else
+ NNI_ARG_UNUSED(cli);
+ NNI_ARG_UNUSED(req);
+ NNI_ARG_UNUSED(res);
+ if (nni_aio_begin(aio) == 0) {
+ nni_aio_finish_error(aio, NNG_ENOTSUP);
+ }
+#endif
+}
+
+void
+nng_http_conn_transact(
+ nng_http_conn *conn, nng_http_req *req, nng_http_res *res, nng_aio *aio)
+{
+#ifdef NNG_SUPP_HTTP
+ nni_http_transact_conn(conn, req, res, aio);
+#else
+ NNI_ARG_UNUSED(conn);
+ NNI_ARG_UNUSED(req);
+ NNI_ARG_UNUSED(res);
+ if (nni_aio_begin(aio) == 0) {
+ nni_aio_finish_error(aio, NNG_ENOTSUP);
+ }
+#endif
+}
diff --git a/tests/httpclient.c b/tests/httpclient.c
index 96597d0c..6964bcc2 100644
--- a/tests/httpclient.c
+++ b/tests/httpclient.c
@@ -29,7 +29,7 @@ const uint8_t example_sum[20] = { 0x0e, 0x97, 0x3b, 0x59, 0xf4, 0x76, 0x00,
TestMain("HTTP Client", {
atexit(nng_fini);
- Convey("Given a TCP connection to httpbin.org", {
+ Convey("Given a TCP connection to example.com", {
nng_aio * aio;
nng_http_client *cli;
nng_http_conn * http;
@@ -37,8 +37,9 @@ TestMain("HTTP Client", {
So(nng_aio_alloc(&aio, NULL, NULL) == 0);
- So(nng_url_parse(&url, "http://example.org/") == 0);
+ So(nng_url_parse(&url, "http://example.com/") == 0);
+ nng_aio_set_timeout(aio, 10000);
So(nng_http_client_alloc(&cli, url) == 0);
nng_http_client_connect(cli, aio);
nng_aio_wait(aio);
@@ -105,4 +106,106 @@ TestMain("HTTP Client", {
});
});
});
+
+ Convey("Given a client", {
+ nng_aio * aio;
+ nng_http_client *cli;
+ nng_url * url;
+
+ So(nng_aio_alloc(&aio, NULL, NULL) == 0);
+
+ So(nng_url_parse(&url, "http://example.com/") == 0);
+
+ So(nng_http_client_alloc(&cli, url) == 0);
+ nng_aio_set_timeout(aio, 10000); // 10 sec timeout
+
+ Reset({
+ nng_http_client_free(cli);
+ nng_url_free(url);
+ nng_aio_free(aio);
+ });
+
+ Convey("One off exchange works", {
+ nng_http_req *req;
+ nng_http_res *res;
+ void * data;
+ size_t len;
+ uint8_t digest[20];
+
+ So(nng_http_req_alloc(&req, url) == 0);
+ So(nng_http_res_alloc(&res) == 0);
+ Reset({
+ nng_http_req_free(req);
+ nng_http_res_free(res);
+ });
+
+ nng_http_client_transact(cli, req, res, aio);
+ nng_aio_wait(aio);
+ So(nng_aio_result(aio) == 0);
+ So(nng_http_res_get_status(res) == 200);
+ nng_http_res_get_data(res, &data, &len);
+ nni_sha1(data, len, digest);
+ So(memcmp(digest, example_sum, 20) == 0);
+ });
+
+ Convey("Timeout works", {
+ nng_http_req *req;
+ nng_http_res *res;
+
+ So(nng_http_req_alloc(&req, url) == 0);
+ So(nng_http_res_alloc(&res) == 0);
+ Reset({
+ nng_http_req_free(req);
+ nng_http_res_free(res);
+ });
+
+ nng_aio_set_timeout(aio, 1); // 1 ms, should timeout!
+ nng_http_client_transact(cli, req, res, aio);
+ nng_aio_wait(aio);
+ So(nng_aio_result(aio) == NNG_ETIMEDOUT);
+ });
+
+ Convey("Connection reuse works", {
+ nng_http_req * req;
+ nng_http_res * res1;
+ nng_http_res * res2;
+ void * data;
+ size_t len;
+ uint8_t digest[20];
+ nng_http_conn *conn = NULL;
+
+ So(nng_http_req_alloc(&req, url) == 0);
+ So(nng_http_res_alloc(&res1) == 0);
+ So(nng_http_res_alloc(&res2) == 0);
+ Reset({
+ nng_http_req_free(req);
+ nng_http_res_free(res1);
+ nng_http_res_free(res2);
+ if (conn != NULL) {
+ nng_http_conn_close(conn);
+ }
+ });
+
+ nng_http_client_connect(cli, aio);
+ nng_aio_wait(aio);
+ So(nng_aio_result(aio) == 0);
+ conn = nng_aio_get_output(aio, 0);
+
+ nng_http_conn_transact(conn, req, res1, aio);
+ nng_aio_wait(aio);
+ So(nng_aio_result(aio) == 0);
+ So(nng_http_res_get_status(res1) == 200);
+ nng_http_res_get_data(res1, &data, &len);
+ nni_sha1(data, len, digest);
+ So(memcmp(digest, example_sum, 20) == 0);
+
+ nng_http_conn_transact(conn, req, res2, aio);
+ nng_aio_wait(aio);
+ So(nng_aio_result(aio) == 0);
+ So(nng_http_res_get_status(res2) == 200);
+ nng_http_res_get_data(res2, &data, &len);
+ nni_sha1(data, len, digest);
+ So(memcmp(digest, example_sum, 20) == 0);
+ });
+ });
})