summaryrefslogtreecommitdiffstats
path: root/src/lib/http
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 11:36:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 11:36:04 +0000
commit040eee1aa49b49df4698d83a05af57c220127fd1 (patch)
treef635435954e6ccde5eee9893889e24f30ca68346 /src/lib/http
parentInitial commit. (diff)
downloadisc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.tar.xz
isc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.zip
Adding upstream version 2.2.0.upstream/2.2.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/http')
-rw-r--r--src/lib/http/Makefile.am134
-rw-r--r--src/lib/http/Makefile.in1299
-rw-r--r--src/lib/http/auth_config.h102
-rw-r--r--src/lib/http/auth_log.cc20
-rw-r--r--src/lib/http/auth_log.h23
-rw-r--r--src/lib/http/auth_messages.cc31
-rw-r--r--src/lib/http/auth_messages.h19
-rw-r--r--src/lib/http/auth_messages.mes24
-rw-r--r--src/lib/http/basic_auth.cc45
-rw-r--r--src/lib/http/basic_auth.h87
-rw-r--r--src/lib/http/basic_auth_config.cc399
-rw-r--r--src/lib/http/basic_auth_config.h204
-rw-r--r--src/lib/http/client.cc2063
-rw-r--r--src/lib/http/client.h342
-rw-r--r--src/lib/http/connection.cc600
-rw-r--r--src/lib/http/connection.h432
-rw-r--r--src/lib/http/connection_pool.cc74
-rw-r--r--src/lib/http/connection_pool.h78
-rw-r--r--src/lib/http/date_time.cc158
-rw-r--r--src/lib/http/date_time.h161
-rw-r--r--src/lib/http/header_context.h49
-rw-r--r--src/lib/http/http.dox24
-rw-r--r--src/lib/http/http_acceptor.h39
-rw-r--r--src/lib/http/http_header.cc56
-rw-r--r--src/lib/http/http_header.h86
-rw-r--r--src/lib/http/http_log.cc21
-rw-r--r--src/lib/http/http_log.h23
-rw-r--r--src/lib/http/http_message.cc108
-rw-r--r--src/lib/http/http_message.h265
-rw-r--r--src/lib/http/http_message_parser_base.cc307
-rw-r--r--src/lib/http/http_message_parser_base.h316
-rw-r--r--src/lib/http/http_messages.cc77
-rw-r--r--src/lib/http/http_messages.h42
-rw-r--r--src/lib/http/http_messages.mes164
-rw-r--r--src/lib/http/http_thread_pool.cc279
-rw-r--r--src/lib/http/http_thread_pool.h265
-rw-r--r--src/lib/http/http_types.h76
-rw-r--r--src/lib/http/listener.cc56
-rw-r--r--src/lib/http/listener.h147
-rw-r--r--src/lib/http/listener_impl.cc125
-rw-r--r--src/lib/http/listener_impl.h136
-rw-r--r--src/lib/http/post_request.cc33
-rw-r--r--src/lib/http/post_request.h53
-rw-r--r--src/lib/http/post_request_json.cc102
-rw-r--r--src/lib/http/post_request_json.h110
-rw-r--r--src/lib/http/request.cc285
-rw-r--r--src/lib/http/request.h312
-rw-r--r--src/lib/http/request_context.h44
-rw-r--r--src/lib/http/request_parser.cc458
-rw-r--r--src/lib/http/request_parser.h246
-rw-r--r--src/lib/http/response.cc233
-rw-r--r--src/lib/http/response.h247
-rw-r--r--src/lib/http/response_context.h44
-rw-r--r--src/lib/http/response_creator.cc33
-rw-r--r--src/lib/http/response_creator.h113
-rw-r--r--src/lib/http/response_creator_factory.h59
-rw-r--r--src/lib/http/response_json.cc122
-rw-r--r--src/lib/http/response_json.h114
-rw-r--r--src/lib/http/response_parser.cc461
-rw-r--r--src/lib/http/response_parser.h243
-rw-r--r--src/lib/http/tests/Makefile.am78
-rw-r--r--src/lib/http/tests/Makefile.in1390
-rw-r--r--src/lib/http/tests/basic_auth_config_unittests.cc540
-rw-r--r--src/lib/http/tests/basic_auth_unittests.cc65
-rw-r--r--src/lib/http/tests/client_mt_unittests.cc1042
-rw-r--r--src/lib/http/tests/connection_pool_unittests.cc270
-rw-r--r--src/lib/http/tests/date_time_unittests.cc190
-rw-r--r--src/lib/http/tests/http_header_unittests.cc54
-rw-r--r--src/lib/http/tests/http_thread_pool_unittests.cc265
-rw-r--r--src/lib/http/tests/post_request_json_unittests.cc197
-rw-r--r--src/lib/http/tests/post_request_unittests.cc83
-rw-r--r--src/lib/http/tests/request_parser_unittests.cc387
-rw-r--r--src/lib/http/tests/request_test.h82
-rw-r--r--src/lib/http/tests/request_unittests.cc422
-rw-r--r--src/lib/http/tests/response_creator_unittests.cc340
-rw-r--r--src/lib/http/tests/response_json_unittests.cc151
-rw-r--r--src/lib/http/tests/response_parser_unittests.cc351
-rw-r--r--src/lib/http/tests/response_test.h62
-rw-r--r--src/lib/http/tests/response_unittests.cc169
-rw-r--r--src/lib/http/tests/run_unittests.cc21
-rw-r--r--src/lib/http/tests/server_client_unittests.cc2041
-rw-r--r--src/lib/http/tests/test_http_client.h273
-rw-r--r--src/lib/http/tests/testdata/empty0
-rw-r--r--src/lib/http/tests/testdata/hiddenp1
-rw-r--r--src/lib/http/tests/testdata/hiddens1
-rw-r--r--src/lib/http/tests/testdata/hiddenu1
-rw-r--r--src/lib/http/tests/tls_client_unittests.cc1398
-rw-r--r--src/lib/http/tests/tls_server_unittests.cc1253
-rw-r--r--src/lib/http/tests/url_unittests.cc115
-rw-r--r--src/lib/http/url.cc223
-rw-r--r--src/lib/http/url.h131
91 files changed, 23864 insertions, 0 deletions
diff --git a/src/lib/http/Makefile.am b/src/lib/http/Makefile.am
new file mode 100644
index 0000000..8ccaedf
--- /dev/null
+++ b/src/lib/http/Makefile.am
@@ -0,0 +1,134 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+EXTRA_DIST = http.dox
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST += auth_messages.mes http_messages.mes
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-http.la
+libkea_http_la_SOURCES = client.cc client.h
+libkea_http_la_SOURCES += connection.cc connection.h
+libkea_http_la_SOURCES += connection_pool.cc connection_pool.h
+libkea_http_la_SOURCES += date_time.cc date_time.h
+libkea_http_la_SOURCES += http_log.cc http_log.h
+libkea_http_la_SOURCES += header_context.h
+libkea_http_la_SOURCES += http_acceptor.h
+libkea_http_la_SOURCES += http_header.cc http_header.h
+libkea_http_la_SOURCES += http_message.cc http_message.h
+libkea_http_la_SOURCES += http_message_parser_base.cc http_message_parser_base.h
+libkea_http_la_SOURCES += http_messages.cc http_messages.h
+libkea_http_la_SOURCES += http_types.h
+libkea_http_la_SOURCES += listener.cc listener.h
+libkea_http_la_SOURCES += listener_impl.cc listener_impl.h
+libkea_http_la_SOURCES += post_request.cc post_request.h
+libkea_http_la_SOURCES += post_request_json.cc post_request_json.h
+libkea_http_la_SOURCES += request.cc request.h
+libkea_http_la_SOURCES += request_context.h
+libkea_http_la_SOURCES += request_parser.cc request_parser.h
+libkea_http_la_SOURCES += response.cc response.h
+libkea_http_la_SOURCES += response_parser.cc response_parser.h
+libkea_http_la_SOURCES += response_context.h
+libkea_http_la_SOURCES += response_creator.cc response_creator.h
+libkea_http_la_SOURCES += response_creator_factory.h
+libkea_http_la_SOURCES += response_json.cc response_json.h
+libkea_http_la_SOURCES += url.cc url.h
+libkea_http_la_SOURCES += auth_config.h
+libkea_http_la_SOURCES += auth_log.cc auth_log.h
+libkea_http_la_SOURCES += auth_messages.cc auth_messages.h
+libkea_http_la_SOURCES += basic_auth_config.cc basic_auth_config.h
+libkea_http_la_SOURCES += basic_auth.cc basic_auth.h
+libkea_http_la_SOURCES += http_thread_pool.cc http_thread_pool.h
+
+libkea_http_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_http_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_http_la_LDFLAGS = $(AM_LDFLAGS)
+libkea_http_la_LDFLAGS += -no-undefined -version-info 42:0:0
+
+libkea_http_la_LIBADD =
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_http_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(CRYPTO_LIBS)
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f auth_messages.cc auth_messages.h
+ rm -f http_messages.h http_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: auth_messages.cc auth_messages.h http_messages.h http_messages.cc
+ @echo Message files regenerated
+
+auth_messages.cc auth_messages.h: auth_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/http/auth_messages.mes
+
+http_messages.h http_messages.cc: http_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/http/http_messages.mes
+
+else
+
+messages http_messages.h http_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+# Specify the headers for copying into the installation directory tree.
+libkea_http_includedir = $(pkgincludedir)/http
+libkea_http_include_HEADERS = \
+ auth_config.h \
+ auth_log.h \
+ auth_messages.h \
+ basic_auth.h \
+ basic_auth_config.h \
+ client.h \
+ connection.h \
+ connection_pool.h \
+ date_time.h \
+ header_context.h \
+ http_acceptor.h \
+ http_header.h \
+ http_log.h \
+ http_message.h \
+ http_message_parser_base.h \
+ http_messages.h \
+ http_thread_pool.h \
+ http_types.h \
+ listener.h \
+ listener_impl.h \
+ post_request.h \
+ post_request_json.h \
+ request.h \
+ request_context.h \
+ request_parser.h \
+ response.h \
+ response_context.h \
+ response_creator.h \
+ response_creator_factory.h \
+ response_json.h \
+ response_parser.h \
+ url.h
+
diff --git a/src/lib/http/Makefile.in b/src/lib/http/Makefile.in
new file mode 100644
index 0000000..45d231e
--- /dev/null
+++ b/src/lib/http/Makefile.in
@@ -0,0 +1,1299 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib/http
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(libkea_http_include_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(libdir)" \
+ "$(DESTDIR)$(libkea_http_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_http_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+am_libkea_http_la_OBJECTS = libkea_http_la-client.lo \
+ libkea_http_la-connection.lo libkea_http_la-connection_pool.lo \
+ libkea_http_la-date_time.lo libkea_http_la-http_log.lo \
+ libkea_http_la-http_header.lo libkea_http_la-http_message.lo \
+ libkea_http_la-http_message_parser_base.lo \
+ libkea_http_la-http_messages.lo libkea_http_la-listener.lo \
+ libkea_http_la-listener_impl.lo libkea_http_la-post_request.lo \
+ libkea_http_la-post_request_json.lo libkea_http_la-request.lo \
+ libkea_http_la-request_parser.lo libkea_http_la-response.lo \
+ libkea_http_la-response_parser.lo \
+ libkea_http_la-response_creator.lo \
+ libkea_http_la-response_json.lo libkea_http_la-url.lo \
+ libkea_http_la-auth_log.lo libkea_http_la-auth_messages.lo \
+ libkea_http_la-basic_auth_config.lo \
+ libkea_http_la-basic_auth.lo \
+ libkea_http_la-http_thread_pool.lo
+libkea_http_la_OBJECTS = $(am_libkea_http_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libkea_http_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libkea_http_la_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/libkea_http_la-auth_log.Plo \
+ ./$(DEPDIR)/libkea_http_la-auth_messages.Plo \
+ ./$(DEPDIR)/libkea_http_la-basic_auth.Plo \
+ ./$(DEPDIR)/libkea_http_la-basic_auth_config.Plo \
+ ./$(DEPDIR)/libkea_http_la-client.Plo \
+ ./$(DEPDIR)/libkea_http_la-connection.Plo \
+ ./$(DEPDIR)/libkea_http_la-connection_pool.Plo \
+ ./$(DEPDIR)/libkea_http_la-date_time.Plo \
+ ./$(DEPDIR)/libkea_http_la-http_header.Plo \
+ ./$(DEPDIR)/libkea_http_la-http_log.Plo \
+ ./$(DEPDIR)/libkea_http_la-http_message.Plo \
+ ./$(DEPDIR)/libkea_http_la-http_message_parser_base.Plo \
+ ./$(DEPDIR)/libkea_http_la-http_messages.Plo \
+ ./$(DEPDIR)/libkea_http_la-http_thread_pool.Plo \
+ ./$(DEPDIR)/libkea_http_la-listener.Plo \
+ ./$(DEPDIR)/libkea_http_la-listener_impl.Plo \
+ ./$(DEPDIR)/libkea_http_la-post_request.Plo \
+ ./$(DEPDIR)/libkea_http_la-post_request_json.Plo \
+ ./$(DEPDIR)/libkea_http_la-request.Plo \
+ ./$(DEPDIR)/libkea_http_la-request_parser.Plo \
+ ./$(DEPDIR)/libkea_http_la-response.Plo \
+ ./$(DEPDIR)/libkea_http_la-response_creator.Plo \
+ ./$(DEPDIR)/libkea_http_la-response_json.Plo \
+ ./$(DEPDIR)/libkea_http_la-response_parser.Plo \
+ ./$(DEPDIR)/libkea_http_la-url.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libkea_http_la_SOURCES)
+DIST_SOURCES = $(libkea_http_la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(libkea_http_include_HEADERS)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = . tests
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = http.dox auth_messages.mes http_messages.mes
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-http.la
+libkea_http_la_SOURCES = client.cc client.h connection.cc connection.h \
+ connection_pool.cc connection_pool.h date_time.cc date_time.h \
+ http_log.cc http_log.h header_context.h http_acceptor.h \
+ http_header.cc http_header.h http_message.cc http_message.h \
+ http_message_parser_base.cc http_message_parser_base.h \
+ http_messages.cc http_messages.h http_types.h listener.cc \
+ listener.h listener_impl.cc listener_impl.h post_request.cc \
+ post_request.h post_request_json.cc post_request_json.h \
+ request.cc request.h request_context.h request_parser.cc \
+ request_parser.h response.cc response.h response_parser.cc \
+ response_parser.h response_context.h response_creator.cc \
+ response_creator.h response_creator_factory.h response_json.cc \
+ response_json.h url.cc url.h auth_config.h auth_log.cc \
+ auth_log.h auth_messages.cc auth_messages.h \
+ basic_auth_config.cc basic_auth_config.h basic_auth.cc \
+ basic_auth.h http_thread_pool.cc http_thread_pool.h
+libkea_http_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_http_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_http_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info \
+ 42:0:0
+libkea_http_la_LIBADD = $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(CRYPTO_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_http_includedir = $(pkgincludedir)/http
+libkea_http_include_HEADERS = \
+ auth_config.h \
+ auth_log.h \
+ auth_messages.h \
+ basic_auth.h \
+ basic_auth_config.h \
+ client.h \
+ connection.h \
+ connection_pool.h \
+ date_time.h \
+ header_context.h \
+ http_acceptor.h \
+ http_header.h \
+ http_log.h \
+ http_message.h \
+ http_message_parser_base.h \
+ http_messages.h \
+ http_thread_pool.h \
+ http_types.h \
+ listener.h \
+ listener_impl.h \
+ post_request.h \
+ post_request_json.h \
+ request.h \
+ request_context.h \
+ request_parser.h \
+ response.h \
+ response_context.h \
+ response_creator.h \
+ response_creator_factory.h \
+ response_json.h \
+ response_parser.h \
+ url.h
+
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/http/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/http/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libkea-http.la: $(libkea_http_la_OBJECTS) $(libkea_http_la_DEPENDENCIES) $(EXTRA_libkea_http_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_http_la_LINK) -rpath $(libdir) $(libkea_http_la_OBJECTS) $(libkea_http_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-auth_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-auth_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-basic_auth.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-basic_auth_config.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-client.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-connection_pool.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-date_time.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-http_header.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-http_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-http_message.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-http_message_parser_base.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-http_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-http_thread_pool.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-listener.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-listener_impl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-post_request.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-post_request_json.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-request.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-request_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-response.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-response_creator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-response_json.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-response_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-url.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+libkea_http_la-client.lo: client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-client.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-client.Tpo -c -o libkea_http_la-client.lo `test -f 'client.cc' || echo '$(srcdir)/'`client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-client.Tpo $(DEPDIR)/libkea_http_la-client.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client.cc' object='libkea_http_la-client.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-client.lo `test -f 'client.cc' || echo '$(srcdir)/'`client.cc
+
+libkea_http_la-connection.lo: connection.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-connection.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-connection.Tpo -c -o libkea_http_la-connection.lo `test -f 'connection.cc' || echo '$(srcdir)/'`connection.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-connection.Tpo $(DEPDIR)/libkea_http_la-connection.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='connection.cc' object='libkea_http_la-connection.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-connection.lo `test -f 'connection.cc' || echo '$(srcdir)/'`connection.cc
+
+libkea_http_la-connection_pool.lo: connection_pool.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-connection_pool.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-connection_pool.Tpo -c -o libkea_http_la-connection_pool.lo `test -f 'connection_pool.cc' || echo '$(srcdir)/'`connection_pool.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-connection_pool.Tpo $(DEPDIR)/libkea_http_la-connection_pool.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='connection_pool.cc' object='libkea_http_la-connection_pool.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-connection_pool.lo `test -f 'connection_pool.cc' || echo '$(srcdir)/'`connection_pool.cc
+
+libkea_http_la-date_time.lo: date_time.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-date_time.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-date_time.Tpo -c -o libkea_http_la-date_time.lo `test -f 'date_time.cc' || echo '$(srcdir)/'`date_time.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-date_time.Tpo $(DEPDIR)/libkea_http_la-date_time.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='date_time.cc' object='libkea_http_la-date_time.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-date_time.lo `test -f 'date_time.cc' || echo '$(srcdir)/'`date_time.cc
+
+libkea_http_la-http_log.lo: http_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-http_log.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-http_log.Tpo -c -o libkea_http_la-http_log.lo `test -f 'http_log.cc' || echo '$(srcdir)/'`http_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-http_log.Tpo $(DEPDIR)/libkea_http_la-http_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_log.cc' object='libkea_http_la-http_log.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-http_log.lo `test -f 'http_log.cc' || echo '$(srcdir)/'`http_log.cc
+
+libkea_http_la-http_header.lo: http_header.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-http_header.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-http_header.Tpo -c -o libkea_http_la-http_header.lo `test -f 'http_header.cc' || echo '$(srcdir)/'`http_header.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-http_header.Tpo $(DEPDIR)/libkea_http_la-http_header.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_header.cc' object='libkea_http_la-http_header.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-http_header.lo `test -f 'http_header.cc' || echo '$(srcdir)/'`http_header.cc
+
+libkea_http_la-http_message.lo: http_message.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-http_message.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-http_message.Tpo -c -o libkea_http_la-http_message.lo `test -f 'http_message.cc' || echo '$(srcdir)/'`http_message.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-http_message.Tpo $(DEPDIR)/libkea_http_la-http_message.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_message.cc' object='libkea_http_la-http_message.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-http_message.lo `test -f 'http_message.cc' || echo '$(srcdir)/'`http_message.cc
+
+libkea_http_la-http_message_parser_base.lo: http_message_parser_base.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-http_message_parser_base.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-http_message_parser_base.Tpo -c -o libkea_http_la-http_message_parser_base.lo `test -f 'http_message_parser_base.cc' || echo '$(srcdir)/'`http_message_parser_base.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-http_message_parser_base.Tpo $(DEPDIR)/libkea_http_la-http_message_parser_base.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_message_parser_base.cc' object='libkea_http_la-http_message_parser_base.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-http_message_parser_base.lo `test -f 'http_message_parser_base.cc' || echo '$(srcdir)/'`http_message_parser_base.cc
+
+libkea_http_la-http_messages.lo: http_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-http_messages.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-http_messages.Tpo -c -o libkea_http_la-http_messages.lo `test -f 'http_messages.cc' || echo '$(srcdir)/'`http_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-http_messages.Tpo $(DEPDIR)/libkea_http_la-http_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_messages.cc' object='libkea_http_la-http_messages.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-http_messages.lo `test -f 'http_messages.cc' || echo '$(srcdir)/'`http_messages.cc
+
+libkea_http_la-listener.lo: listener.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-listener.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-listener.Tpo -c -o libkea_http_la-listener.lo `test -f 'listener.cc' || echo '$(srcdir)/'`listener.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-listener.Tpo $(DEPDIR)/libkea_http_la-listener.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='listener.cc' object='libkea_http_la-listener.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-listener.lo `test -f 'listener.cc' || echo '$(srcdir)/'`listener.cc
+
+libkea_http_la-listener_impl.lo: listener_impl.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-listener_impl.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-listener_impl.Tpo -c -o libkea_http_la-listener_impl.lo `test -f 'listener_impl.cc' || echo '$(srcdir)/'`listener_impl.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-listener_impl.Tpo $(DEPDIR)/libkea_http_la-listener_impl.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='listener_impl.cc' object='libkea_http_la-listener_impl.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-listener_impl.lo `test -f 'listener_impl.cc' || echo '$(srcdir)/'`listener_impl.cc
+
+libkea_http_la-post_request.lo: post_request.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-post_request.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-post_request.Tpo -c -o libkea_http_la-post_request.lo `test -f 'post_request.cc' || echo '$(srcdir)/'`post_request.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-post_request.Tpo $(DEPDIR)/libkea_http_la-post_request.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='post_request.cc' object='libkea_http_la-post_request.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-post_request.lo `test -f 'post_request.cc' || echo '$(srcdir)/'`post_request.cc
+
+libkea_http_la-post_request_json.lo: post_request_json.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-post_request_json.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-post_request_json.Tpo -c -o libkea_http_la-post_request_json.lo `test -f 'post_request_json.cc' || echo '$(srcdir)/'`post_request_json.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-post_request_json.Tpo $(DEPDIR)/libkea_http_la-post_request_json.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='post_request_json.cc' object='libkea_http_la-post_request_json.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-post_request_json.lo `test -f 'post_request_json.cc' || echo '$(srcdir)/'`post_request_json.cc
+
+libkea_http_la-request.lo: request.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-request.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-request.Tpo -c -o libkea_http_la-request.lo `test -f 'request.cc' || echo '$(srcdir)/'`request.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-request.Tpo $(DEPDIR)/libkea_http_la-request.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='request.cc' object='libkea_http_la-request.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-request.lo `test -f 'request.cc' || echo '$(srcdir)/'`request.cc
+
+libkea_http_la-request_parser.lo: request_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-request_parser.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-request_parser.Tpo -c -o libkea_http_la-request_parser.lo `test -f 'request_parser.cc' || echo '$(srcdir)/'`request_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-request_parser.Tpo $(DEPDIR)/libkea_http_la-request_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='request_parser.cc' object='libkea_http_la-request_parser.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-request_parser.lo `test -f 'request_parser.cc' || echo '$(srcdir)/'`request_parser.cc
+
+libkea_http_la-response.lo: response.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-response.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-response.Tpo -c -o libkea_http_la-response.lo `test -f 'response.cc' || echo '$(srcdir)/'`response.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-response.Tpo $(DEPDIR)/libkea_http_la-response.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response.cc' object='libkea_http_la-response.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-response.lo `test -f 'response.cc' || echo '$(srcdir)/'`response.cc
+
+libkea_http_la-response_parser.lo: response_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-response_parser.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-response_parser.Tpo -c -o libkea_http_la-response_parser.lo `test -f 'response_parser.cc' || echo '$(srcdir)/'`response_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-response_parser.Tpo $(DEPDIR)/libkea_http_la-response_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_parser.cc' object='libkea_http_la-response_parser.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-response_parser.lo `test -f 'response_parser.cc' || echo '$(srcdir)/'`response_parser.cc
+
+libkea_http_la-response_creator.lo: response_creator.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-response_creator.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-response_creator.Tpo -c -o libkea_http_la-response_creator.lo `test -f 'response_creator.cc' || echo '$(srcdir)/'`response_creator.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-response_creator.Tpo $(DEPDIR)/libkea_http_la-response_creator.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_creator.cc' object='libkea_http_la-response_creator.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-response_creator.lo `test -f 'response_creator.cc' || echo '$(srcdir)/'`response_creator.cc
+
+libkea_http_la-response_json.lo: response_json.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-response_json.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-response_json.Tpo -c -o libkea_http_la-response_json.lo `test -f 'response_json.cc' || echo '$(srcdir)/'`response_json.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-response_json.Tpo $(DEPDIR)/libkea_http_la-response_json.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_json.cc' object='libkea_http_la-response_json.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-response_json.lo `test -f 'response_json.cc' || echo '$(srcdir)/'`response_json.cc
+
+libkea_http_la-url.lo: url.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-url.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-url.Tpo -c -o libkea_http_la-url.lo `test -f 'url.cc' || echo '$(srcdir)/'`url.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-url.Tpo $(DEPDIR)/libkea_http_la-url.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='url.cc' object='libkea_http_la-url.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-url.lo `test -f 'url.cc' || echo '$(srcdir)/'`url.cc
+
+libkea_http_la-auth_log.lo: auth_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-auth_log.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-auth_log.Tpo -c -o libkea_http_la-auth_log.lo `test -f 'auth_log.cc' || echo '$(srcdir)/'`auth_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-auth_log.Tpo $(DEPDIR)/libkea_http_la-auth_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='auth_log.cc' object='libkea_http_la-auth_log.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-auth_log.lo `test -f 'auth_log.cc' || echo '$(srcdir)/'`auth_log.cc
+
+libkea_http_la-auth_messages.lo: auth_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-auth_messages.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-auth_messages.Tpo -c -o libkea_http_la-auth_messages.lo `test -f 'auth_messages.cc' || echo '$(srcdir)/'`auth_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-auth_messages.Tpo $(DEPDIR)/libkea_http_la-auth_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='auth_messages.cc' object='libkea_http_la-auth_messages.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-auth_messages.lo `test -f 'auth_messages.cc' || echo '$(srcdir)/'`auth_messages.cc
+
+libkea_http_la-basic_auth_config.lo: basic_auth_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-basic_auth_config.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-basic_auth_config.Tpo -c -o libkea_http_la-basic_auth_config.lo `test -f 'basic_auth_config.cc' || echo '$(srcdir)/'`basic_auth_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-basic_auth_config.Tpo $(DEPDIR)/libkea_http_la-basic_auth_config.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_auth_config.cc' object='libkea_http_la-basic_auth_config.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-basic_auth_config.lo `test -f 'basic_auth_config.cc' || echo '$(srcdir)/'`basic_auth_config.cc
+
+libkea_http_la-basic_auth.lo: basic_auth.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-basic_auth.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-basic_auth.Tpo -c -o libkea_http_la-basic_auth.lo `test -f 'basic_auth.cc' || echo '$(srcdir)/'`basic_auth.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-basic_auth.Tpo $(DEPDIR)/libkea_http_la-basic_auth.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_auth.cc' object='libkea_http_la-basic_auth.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-basic_auth.lo `test -f 'basic_auth.cc' || echo '$(srcdir)/'`basic_auth.cc
+
+libkea_http_la-http_thread_pool.lo: http_thread_pool.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-http_thread_pool.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-http_thread_pool.Tpo -c -o libkea_http_la-http_thread_pool.lo `test -f 'http_thread_pool.cc' || echo '$(srcdir)/'`http_thread_pool.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-http_thread_pool.Tpo $(DEPDIR)/libkea_http_la-http_thread_pool.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_thread_pool.cc' object='libkea_http_la-http_thread_pool.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-http_thread_pool.lo `test -f 'http_thread_pool.cc' || echo '$(srcdir)/'`http_thread_pool.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_http_includeHEADERS: $(libkea_http_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_http_include_HEADERS)'; test -n "$(libkea_http_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_http_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_http_includedir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libkea_http_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_http_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_http_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_http_include_HEADERS)'; test -n "$(libkea_http_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_http_includedir)'; $(am__uninstall_files_from_dir)
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_http_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_http_la-auth_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-auth_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-basic_auth.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-basic_auth_config.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-client.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-connection.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-connection_pool.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-date_time.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_header.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_message.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_message_parser_base.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_thread_pool.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-listener.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-listener_impl.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-post_request.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-post_request_json.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-request.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-request_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response_creator.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response_json.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-url.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-libkea_http_includeHEADERS
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/libkea_http_la-auth_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-auth_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-basic_auth.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-basic_auth_config.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-client.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-connection.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-connection_pool.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-date_time.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_header.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_message.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_message_parser_base.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_thread_pool.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-listener.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-listener_impl.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-post_request.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-post_request_json.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-request.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-request_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response_creator.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response_json.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-url.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-libLTLIBRARIES \
+ uninstall-libkea_http_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic \
+ clean-libLTLIBRARIES clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-libLTLIBRARIES \
+ install-libkea_http_includeHEADERS install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-libkea_http_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f auth_messages.cc auth_messages.h
+ rm -f http_messages.h http_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: auth_messages.cc auth_messages.h http_messages.h http_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@auth_messages.cc auth_messages.h: auth_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/http/auth_messages.mes
+
+@GENERATE_MESSAGES_TRUE@http_messages.h http_messages.cc: http_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/http/http_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages http_messages.h http_messages.cc:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/http/auth_config.h b/src/lib/http/auth_config.h
new file mode 100644
index 0000000..d8f74eb
--- /dev/null
+++ b/src/lib/http/auth_config.h
@@ -0,0 +1,102 @@
+// Copyright (C) 2020-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_AUTH_CONFIG_H
+#define HTTP_AUTH_CONFIG_H
+
+#include <cc/cfg_to_element.h>
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <cc/user_context.h>
+#include <http/request.h>
+#include <http/response_creator.h>
+#include <http/response_json.h>
+
+namespace isc {
+namespace http {
+
+/// @brief Base type of HTTP authentication configuration.
+class HttpAuthConfig : public isc::data::UserContext,
+ public isc::data::CfgToElement {
+public:
+
+ /// @brief Destructor.
+ virtual ~HttpAuthConfig() { }
+
+ /// @brief Set the realm.
+ ///
+ /// @param realm New realm.
+ void setRealm(const std::string& realm) {
+ realm_ = realm;
+ }
+
+ /// @brief Returns the realm.
+ ///
+ /// @return The HTTP authentication realm.
+ const std::string& getRealm() const {
+ return (realm_);
+ }
+
+ /// @brief Set the common part for file paths (usually a directory).
+ ///
+ /// @param directory New directory.
+ void setDirectory(const std::string& directory) {
+ directory_ = directory;
+ }
+
+ /// @brief Returns the common part for file paths (usually a directory).
+ ///
+ /// @return The common part for file paths (usually a directory).
+ const std::string& getDirectory() const {
+ return (directory_);
+ }
+
+ /// @brief Empty predicate.
+ ///
+ /// @return true if the configuration is empty so authentication
+ /// is not required.
+ virtual bool empty() const = 0;
+
+ /// @brief Clear configuration.
+ virtual void clear() = 0;
+
+ /// @brief Parses HTTP authentication configuration.
+ ///
+ /// @param config Element holding the basic HTTP authentication
+ /// configuration to be parsed.
+ /// @throw DhcpConfigError when the configuration is invalid.
+ virtual void parse(const isc::data::ConstElementPtr& config) = 0;
+
+ /// @brief Unparses HTTP authentication configuration.
+ ///
+ /// @return A pointer to unparsed HTTP authentication configuration.
+ virtual isc::data::ElementPtr toElement() const = 0;
+
+ /// @brief Validate HTTP request.
+ ///
+ /// @param creator The HTTP response creator.
+ /// @param request The HTTP request to validate.
+ /// @return Error HTTP response if validation failed, null otherwise.
+ virtual isc::http::HttpResponseJsonPtr
+ checkAuth(const isc::http::HttpResponseCreator& creator,
+ const isc::http::HttpRequestPtr& request) const = 0;
+
+private:
+
+ /// @brief The realm.
+ std::string realm_;
+
+ /// @brief Common part for file paths (usually a directory).
+ std::string directory_;
+};
+
+/// @brief Type of shared pointers to HTTP authentication configuration.
+typedef boost::shared_ptr<HttpAuthConfig> HttpAuthConfigPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // endif HTTP_AUTH_CONFIG_H
diff --git a/src/lib/http/auth_log.cc b/src/lib/http/auth_log.cc
new file mode 100644
index 0000000..d2c805c
--- /dev/null
+++ b/src/lib/http/auth_log.cc
@@ -0,0 +1,20 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// Defines the logger used by the HTTP authentication.
+
+#include <config.h>
+
+#include <http/auth_log.h>
+
+namespace isc {
+namespace http {
+
+/// @brief Defines the logger used by the HTTP authentication.
+isc::log::Logger auth_logger("auth");
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/auth_log.h b/src/lib/http/auth_log.h
new file mode 100644
index 0000000..8ebf5c3
--- /dev/null
+++ b/src/lib/http/auth_log.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef AUTH_LOG_H
+#define AUTH_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <http/auth_messages.h>
+
+namespace isc {
+namespace http {
+
+/// Define the HTTP authentication logger.
+extern isc::log::Logger auth_logger;
+
+} // namespace http
+} // namespace isc
+
+#endif // AUTH_LOG_H
diff --git a/src/lib/http/auth_messages.cc b/src/lib/http/auth_messages.cc
new file mode 100644
index 0000000..ebf9da5
--- /dev/null
+++ b/src/lib/http/auth_messages.cc
@@ -0,0 +1,31 @@
+// File created from ../../../src/lib/http/auth_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace http {
+
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_AUTHORIZED = "HTTP_CLIENT_REQUEST_AUTHORIZED";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER = "HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_NOT_AUTHORIZED = "HTTP_CLIENT_REQUEST_NOT_AUTHORIZED";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_NO_AUTH_HEADER = "HTTP_CLIENT_REQUEST_NO_AUTH_HEADER";
+
+} // namespace http
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "HTTP_CLIENT_REQUEST_AUTHORIZED", "received HTTP request authorized for '%1'",
+ "HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER", "received HTTP request with malformed authentication header: %1",
+ "HTTP_CLIENT_REQUEST_NOT_AUTHORIZED", "received HTTP request with not matching authentication header",
+ "HTTP_CLIENT_REQUEST_NO_AUTH_HEADER", "received HTTP request without required authentication header",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/http/auth_messages.h b/src/lib/http/auth_messages.h
new file mode 100644
index 0000000..ff02ef5
--- /dev/null
+++ b/src/lib/http/auth_messages.h
@@ -0,0 +1,19 @@
+// File created from ../../../src/lib/http/auth_messages.mes
+
+#ifndef AUTH_MESSAGES_H
+#define AUTH_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace http {
+
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_AUTHORIZED;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_NOT_AUTHORIZED;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_NO_AUTH_HEADER;
+
+} // namespace http
+} // namespace isc
+
+#endif // AUTH_MESSAGES_H
diff --git a/src/lib/http/auth_messages.mes b/src/lib/http/auth_messages.mes
new file mode 100644
index 0000000..685bdb3
--- /dev/null
+++ b/src/lib/http/auth_messages.mes
@@ -0,0 +1,24 @@
+# Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$NAMESPACE isc::http
+
+% HTTP_CLIENT_REQUEST_AUTHORIZED received HTTP request authorized for '%1'
+This information message is issued when the server receives with a matching
+authentication header. The argument provides the user id.
+
+% HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER received HTTP request with malformed authentication header: %1
+This information message is issued when the server receives a request with
+a malformed authentication header. The argument explains the problem.
+
+% HTTP_CLIENT_REQUEST_NOT_AUTHORIZED received HTTP request with not matching authentication header
+This information message is issued when the server receives a request with
+authentication header carrying not recognized credential: the user
+provided incorrect user id and/or password.
+
+% HTTP_CLIENT_REQUEST_NO_AUTH_HEADER received HTTP request without required authentication header
+This information message is issued when the server receives a request without
+a required authentication header.
diff --git a/src/lib/http/basic_auth.cc b/src/lib/http/basic_auth.cc
new file mode 100644
index 0000000..f3a1d87
--- /dev/null
+++ b/src/lib/http/basic_auth.cc
@@ -0,0 +1,45 @@
+// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/basic_auth.h>
+#include <util/encode/base64.h>
+#include <util/encode/utf8.h>
+
+using namespace isc::util::encode;
+using namespace std;
+
+namespace isc {
+namespace http {
+
+BasicHttpAuth::BasicHttpAuth(const std::string& user,
+ const std::string& password)
+ : user_(user), password_(password) {
+ if (user.find(':') != string::npos) {
+ isc_throw(BadValue, "user '" << user << "' must not contain a ':'");
+ }
+ buildSecret();
+ buildCredential();
+}
+
+BasicHttpAuth::BasicHttpAuth(const std::string& secret) : secret_(secret) {
+ if (secret.find(':') == string::npos) {
+ isc_throw(BadValue, "secret '" << secret << "' must contain a ':");
+ }
+ buildCredential();
+}
+
+void BasicHttpAuth::buildSecret() {
+ secret_ = user_ + ":" + password_;
+}
+
+void BasicHttpAuth::buildCredential() {
+ credential_ = encodeBase64(encodeUtf8(secret_));
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/basic_auth.h b/src/lib/http/basic_auth.h
new file mode 100644
index 0000000..45301af
--- /dev/null
+++ b/src/lib/http/basic_auth.h
@@ -0,0 +1,87 @@
+// Copyright (C) 2020-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef BASIC_HTTP_AUTH_H
+#define BASIC_HTTP_AUTH_H
+
+#include <http/header_context.h>
+#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
+#include <unordered_set>
+
+namespace isc {
+namespace http {
+
+/// @brief Represents a basic HTTP authentication.
+///
+/// It computes the credential from user and password.
+class BasicHttpAuth {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param user User id
+ /// @param password Password
+ /// @throw BadValue if user contains the ':' character.
+ BasicHttpAuth(const std::string& user, const std::string& password);
+
+ /// @brief Constructor.
+ ///
+ /// @param secret user:password string
+ /// @throw BadValue if secret does not contain the ':' character.
+ BasicHttpAuth(const std::string& secret);
+
+ /// @brief Returns the secret.
+ const std::string& getSecret() const {
+ return (secret_);
+ }
+
+ /// @brief Returns the credential (base64 of the UTF-8 secret).
+ const std::string& getCredential() const {
+ return (credential_);
+ }
+
+private:
+
+ /// @brief Build the secret from user and password.
+ void buildSecret();
+
+ /// @brief Build the credential from the secret.
+ void buildCredential();
+
+ /// @brief User id e.g. johndoe.
+ std::string user_;
+
+ /// @brief Password e.g. secret1.
+ std::string password_;
+
+ /// @brief Secret e.g. johndoe:secret1.
+ std::string secret_;
+
+ /// @brief Credential: base64 encoding of UTF-8 secret,
+ /// e.g. am9obmRvZTpzZWNyZXQx.
+ std::string credential_;
+};
+
+/// @brief Type of pointers to basic HTTP authentication objects.
+typedef boost::shared_ptr<BasicHttpAuth> BasicHttpAuthPtr;
+
+/// @brief Represents basic HTTP authentication header.
+struct BasicAuthHttpHeaderContext : public HttpHeaderContext {
+
+ /// @brief Constructor.
+ ///
+ /// @param basic_auth Basic HTTP authentication object.
+ explicit BasicAuthHttpHeaderContext(const BasicHttpAuth& basic_auth)
+ : HttpHeaderContext("Authorization",
+ "Basic " + basic_auth.getCredential()) {
+ }
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // endif BASIC_HTTP_AUTH_H
diff --git a/src/lib/http/basic_auth_config.cc b/src/lib/http/basic_auth_config.cc
new file mode 100644
index 0000000..0c98a4a
--- /dev/null
+++ b/src/lib/http/basic_auth_config.cc
@@ -0,0 +1,399 @@
+// Copyright (C) 2020-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/auth_log.h>
+#include <http/basic_auth_config.h>
+#include <util/file_utilities.h>
+#include <util/strutil.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace std;
+
+namespace isc {
+namespace http {
+
+BasicHttpAuthClient::BasicHttpAuthClient(const std::string& user,
+ const std::string& password,
+ const isc::data::ConstElementPtr& user_context)
+ : user_(user), user_file_(""), password_(password),
+ password_file_(""), password_file_only_(false) {
+ if (user_context) {
+ setContext(user_context);
+ }
+}
+
+BasicHttpAuthClient::BasicHttpAuthClient(const std::string& user,
+ const std::string& user_file,
+ const std::string& password,
+ const std::string& password_file,
+ bool password_file_only,
+ const isc::data::ConstElementPtr& user_context)
+ : user_(user), user_file_(user_file), password_(password),
+ password_file_(password_file), password_file_only_(password_file_only) {
+ if (user_context) {
+ setContext(user_context);
+ }
+}
+
+ElementPtr
+BasicHttpAuthClient::toElement() const {
+ ElementPtr result = Element::createMap();
+
+ // Set user-context
+ contextToElement(result);
+
+ // Set password file or password.
+ if (!password_file_.empty()) {
+ result->set("password-file", Element::create(password_file_));
+ } else {
+ result->set("password", Element::create(password_));
+ }
+
+ // Set user-file or user.
+ if (!password_file_only_) {
+ if (!user_file_.empty()) {
+ result->set("user-file", Element::create(user_file_));
+ } else {
+ result->set("user", Element::create(user_));
+ }
+ }
+
+ return (result);
+}
+
+void
+BasicHttpAuthConfig::add(const std::string& user,
+ const std::string& user_file,
+ const std::string& password,
+ const std::string& password_file,
+ bool password_file_only,
+ const ConstElementPtr& user_context) {
+ BasicHttpAuth basic_auth(user, password);
+ list_.push_back(BasicHttpAuthClient(user, user_file, password,
+ password_file, password_file_only,
+ user_context));
+ map_[basic_auth.getCredential()] = user;
+}
+
+void
+BasicHttpAuthConfig::clear() {
+ list_.clear();
+ map_.clear();
+}
+
+bool
+BasicHttpAuthConfig::empty() const {
+ return (map_.empty());
+}
+
+string
+BasicHttpAuthConfig::getFileContent(const std::string& file_name) const {
+ // Build path.
+ string path = getDirectory();
+ // Add a trailing '/' if the last character is not already a '/'.
+ if (path.empty() || (path[path.size() - 1] != '/')) {
+ path += "/";
+ }
+ // Don't add a second '/'.
+ if (file_name.empty() || (file_name[0] != '/')) {
+ path += file_name;
+ } else {
+ path += file_name.substr(1);
+ }
+
+ try {
+ return (file::getContent(path));
+ } catch (const isc::BadValue& ex) {
+ isc_throw(DhcpConfigError, ex.what());
+ }
+}
+
+ElementPtr
+BasicHttpAuthConfig::toElement() const {
+ ElementPtr result = Element::createMap();
+
+ // Set user-context
+ contextToElement(result);
+
+ // Set type
+ result->set("type", Element::create(string("basic")));
+
+ // Set realm
+ result->set("realm", Element::create(getRealm()));
+
+ // Set directory.
+ result->set("directory", Element::create(getDirectory()));
+
+ // Set clients
+ ElementPtr clients = Element::createList();
+ for (auto client : list_) {
+ clients->add(client.toElement());
+ }
+ result->set("clients", clients);
+
+ return (result);
+}
+
+void
+BasicHttpAuthConfig::parse(const ConstElementPtr& config) {
+ if (!config) {
+ return;
+ }
+ if (config->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "authentication must be a map ("
+ << config->getPosition() << ")");
+ }
+
+ // Get and verify the type.
+ ConstElementPtr type = config->get("type");
+ if (!type) {
+ isc_throw(DhcpConfigError, "type is required in authentication ("
+ << config->getPosition() << ")");
+ }
+ if (type->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "type must be a string ("
+ << type->getPosition() << ")");
+ }
+ if (type->stringValue() != "basic") {
+ isc_throw(DhcpConfigError, "only basic HTTP authentication is "
+ << "supported: type is '" << type->stringValue()
+ << "' not 'basic' (" << type->getPosition() << ")");
+ }
+
+ // Get the realm.
+ ConstElementPtr realm = config->get("realm");
+ if (realm) {
+ if (realm->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "realm must be a string ("
+ << realm->getPosition() << ")");
+ }
+ setRealm(realm->stringValue());
+ }
+
+ // Get the directory.
+ ConstElementPtr directory = config->get("directory");
+ if (directory) {
+ if (directory->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "directory must be a string ("
+ << directory->getPosition() << ")");
+ }
+ setDirectory(directory->stringValue());
+ }
+
+ // Get user context.
+ ConstElementPtr user_context_cfg = config->get("user-context");
+ if (user_context_cfg) {
+ if (user_context_cfg->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "user-context must be a map ("
+ << user_context_cfg->getPosition() << ")");
+ }
+ setContext(user_context_cfg);
+ }
+
+ // Get clients.
+ ConstElementPtr clients = config->get("clients");
+ if (!clients) {
+ return;
+ }
+ if (clients->getType() != Element::list) {
+ isc_throw(DhcpConfigError, "clients must be a list ("
+ << clients->getPosition() << ")");
+ }
+
+ // Iterate on clients.
+ for (auto client : clients->listValue()) {
+ if (client->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "clients items must be maps ("
+ << client->getPosition() << ")");
+ }
+
+ // password.
+ string password;
+ ConstElementPtr password_cfg = client->get("password");
+ if (password_cfg) {
+ if (password_cfg->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "password must be a string ("
+ << password_cfg->getPosition() << ")");
+ }
+ password = password_cfg->stringValue();
+ }
+
+ // password file.
+ string password_file;
+ ConstElementPtr password_file_cfg = client->get("password-file");
+ if (password_file_cfg) {
+ if (password_cfg) {
+ isc_throw(DhcpConfigError, "password ("
+ << password_cfg->getPosition()
+ << ") and password-file ("
+ << password_file_cfg->getPosition()
+ << ") are mutually exclusive");
+ }
+ if (password_file_cfg->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "password-file must be a string ("
+ << password_file_cfg->getPosition() << ")");
+ }
+ password_file = password_file_cfg->stringValue();
+ }
+
+ ConstElementPtr user_cfg = client->get("user");
+ ConstElementPtr user_file_cfg = client->get("user-file");
+ bool password_file_only = false;
+ if (!user_cfg && !user_file_cfg) {
+ if (password_file_cfg) {
+ password_file_only = true;
+ } else {
+ isc_throw(DhcpConfigError, "user is required in clients "
+ << "items (" << client->getPosition() << ")");
+ }
+ }
+
+ // user.
+ string user;
+ if (user_cfg) {
+ if (user_file_cfg) {
+ isc_throw(DhcpConfigError, "user (" << user_cfg->getPosition()
+ << ") and user-file ("
+ << user_file_cfg->getPosition()
+ << ") are mutually exclusive");
+ }
+ if (user_cfg->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "user must be a string ("
+ << user_cfg->getPosition() << ")");
+ }
+ user = user_cfg->stringValue();
+ if (user.empty()) {
+ isc_throw(DhcpConfigError, "user must not be empty ("
+ << user_cfg->getPosition() << ")");
+ }
+ if (user.find(':') != string::npos) {
+ isc_throw(DhcpConfigError, "user must not contain a ':': '"
+ << user << "' (" << user_cfg->getPosition() << ")");
+ }
+ }
+
+ // user file.
+ string user_file;
+ if (user_file_cfg) {
+ if (user_file_cfg->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "user-file must be a string ("
+ << user_file_cfg->getPosition() << ")");
+ }
+ user_file = user_file_cfg->stringValue();
+ user = getFileContent(user_file);
+ if (user.empty()) {
+ isc_throw(DhcpConfigError, "user must not be empty "
+ << "from user-file '" << user_file << "' ("
+ << user_file_cfg->getPosition() << ")");
+ }
+ if (user.find(':') != string::npos) {
+ isc_throw(DhcpConfigError, "user must not contain a ':' "
+ << "from user-file '" << user_file << "' ("
+ << user_file_cfg->getPosition() << ")");
+ }
+ }
+
+ // Solve password file.
+ if (password_file_cfg) {
+ if (password_file_only) {
+ string content = getFileContent(password_file);
+ auto pos = content.find(':');
+ if (pos == string::npos) {
+ isc_throw(DhcpConfigError, "can't find the user id part "
+ << "in password-file '" << password_file << "' ("
+ << password_file_cfg->getPosition() << ")");
+ }
+ user = content.substr(0, pos);
+ password = content.substr(pos + 1);
+ } else {
+ password = getFileContent(password_file);
+ }
+ }
+
+ // user context.
+ ConstElementPtr user_context = client->get("user-context");
+ if (user_context) {
+ if (user_context->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "user-context must be a map ("
+ << user_context->getPosition() << ")");
+ }
+ }
+
+ // add it.
+ try {
+ add(user, user_file, password, password_file, password_file_only,
+ user_context);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << client->getPosition() << ")");
+ }
+ }
+}
+
+HttpResponseJsonPtr
+BasicHttpAuthConfig::checkAuth(const HttpResponseCreator& creator,
+ const HttpRequestPtr& request) const {
+ const BasicHttpAuthMap& credentials = getCredentialMap();
+ bool authentic = false;
+ if (credentials.empty()) {
+ authentic = true;
+ } else try {
+ string value = request->getHeaderValue("Authorization");
+ // Trim space characters.
+ value = str::trim(value);
+ if (value.size() < 8) {
+ isc_throw(BadValue, "header content is too short");
+ }
+ // Get the authentication scheme which must be "basic".
+ string scheme = value.substr(0, 5);
+ str::lowercase(scheme);
+ if (scheme != "basic") {
+ isc_throw(BadValue, "not basic authentication");
+ }
+ // Skip the authentication scheme name and space characters.
+ value = value.substr(5);
+ value = str::trim(value);
+ // Verify the credential is in the list.
+ const auto it = credentials.find(value);
+ if (it != credentials.end()) {
+ LOG_INFO(auth_logger, HTTP_CLIENT_REQUEST_AUTHORIZED)
+ .arg(it->second);
+ if (HttpRequest::recordBasicAuth_) {
+ request->setBasicAuth(it->second);
+ }
+ authentic = true;
+ } else {
+ LOG_INFO(auth_logger, HTTP_CLIENT_REQUEST_NOT_AUTHORIZED);
+ authentic = false;
+ }
+ } catch (const HttpMessageNonExistingHeader&) {
+ LOG_INFO(auth_logger, HTTP_CLIENT_REQUEST_NO_AUTH_HEADER);
+ } catch (const BadValue& ex) {
+ LOG_INFO(auth_logger, HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER)
+ .arg(ex.what());
+ }
+ if (authentic) {
+ return (HttpResponseJsonPtr());
+ }
+ const string& realm = getRealm();
+ const string& scheme = "Basic";
+ HttpResponsePtr response =
+ creator.createStockHttpResponse(request, HttpStatusCode::UNAUTHORIZED);
+ response->reset();
+ response->context()->headers_.push_back(
+ HttpHeaderContext("WWW-Authenticate",
+ scheme + " realm=\"" + realm + "\""));
+ response->finalize();
+ return (boost::dynamic_pointer_cast<HttpResponseJson>(response));
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/basic_auth_config.h b/src/lib/http/basic_auth_config.h
new file mode 100644
index 0000000..413dd22
--- /dev/null
+++ b/src/lib/http/basic_auth_config.h
@@ -0,0 +1,204 @@
+// Copyright (C) 2020-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_BASIC_AUTH_CONFIG_H
+#define HTTP_BASIC_AUTH_CONFIG_H
+
+#include <http/auth_config.h>
+#include <http/basic_auth.h>
+#include <list>
+#include <unordered_map>
+
+namespace isc {
+namespace http {
+
+/// @brief Type of basic HTTP authentication credential and user id map,
+/// e.g. map["am9obmRvZTpzZWNyZXQx"] = "johndoe".
+///
+/// The map is used to verify a received credential: if it is not in it
+/// the authentication fails, if it is in it the user id is logged.
+typedef std::unordered_map<std::string, std::string> BasicHttpAuthMap;
+
+/// @brief Basic HTTP authentication client configuration.
+class BasicHttpAuthClient : public isc::data::UserContext,
+ public isc::data::CfgToElement {
+public:
+
+ /// @brief Constructor (legacy).
+ ///
+ /// @param user User id.
+ /// @param password Password.
+ /// @param user_context Optional user context.
+ BasicHttpAuthClient(const std::string& user,
+ const std::string& password,
+ const isc::data::ConstElementPtr& user_context);
+
+ /// @brief Constructor.
+ ///
+ /// @param user User id.
+ /// @param user_file File with the user id.
+ /// @param password Password.
+ /// @param password_file File with the password.
+ /// @param password_file_only Flag true if the password file includes
+ /// the user id too.
+ /// @param user_context Optional user context.
+ BasicHttpAuthClient(const std::string& user,
+ const std::string& user_file,
+ const std::string& password,
+ const std::string& password_file,
+ bool password_file_only,
+ const isc::data::ConstElementPtr& user_context);
+
+ /// @brief Returns the user id.
+ ///
+ /// @return The user id.
+ const std::string& getUser() const {
+ return (user_);
+ }
+
+ /// @brief Returns the user id file.
+ ///
+ /// @return The user id file.
+ const std::string& getUserFile() const {
+ return (user_file_);
+ }
+
+ /// @brief Returns the password.
+ ///
+ /// @return The password.
+ const std::string& getPassword() const {
+ return (password_);
+ }
+
+ /// @brief Returns the password file.
+ ///
+ /// @return The password file.
+ const std::string& getPasswordFile() const {
+ return (password_file_);
+ }
+
+ /// @brief Returns the password file only flag.
+ ///
+ /// @return The password file only flag.
+ bool getPasswordFileOnly() const {
+ return (password_file_only_);
+ }
+
+ /// @brief Unparses basic HTTP authentication client configuration.
+ ///
+ /// @return A pointer to unparsed client configuration.
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+
+ /// @brief The user id e.g. johndoe.
+ std::string user_;
+
+ /// @brief The user id file.
+ std::string user_file_;
+
+ /// @brief The password e.g. secret1.
+ std::string password_;
+
+ /// @brief The password file.
+ std::string password_file_;
+
+ /// @brief The password file only flag.
+ bool password_file_only_;
+};
+
+/// @brief Type of basic HTTP authentication client configuration list.
+typedef std::list<BasicHttpAuthClient> BasicHttpAuthClientList;
+
+/// @brief Basic HTTP authentication configuration.
+class BasicHttpAuthConfig : public HttpAuthConfig {
+public:
+ /// @brief Destructor.
+ virtual ~BasicHttpAuthConfig() { }
+
+ /// @brief Add a client configuration.
+ ///
+ /// @param user User id.
+ /// @param user_file File with the user id.
+ /// @param password Password.
+ /// @param password_file File with the password.
+ /// @param password_file_only Flag true if the password file includes
+ /// the user id too.
+ /// @param user_context Optional user context.
+ /// @throw BadValue if the user id contains the ':' character.
+ void add(const std::string& user,
+ const std::string& user_file,
+ const std::string& password,
+ const std::string& password_file,
+ bool password_file_only = false,
+ const isc::data::ConstElementPtr& user_context = isc::data::ConstElementPtr());
+
+ /// @brief Empty predicate.
+ ///
+ /// @return true if the configuration is empty so authentication
+ /// is not required.
+ virtual bool empty() const;
+
+ /// @brief Clear configuration.
+ virtual void clear();
+
+ /// @brief Get the content of {directory}/{file-name} regular file.
+ ///
+ /// @param file_name The file name.
+ /// @return The content of the {directory}/{file-name} regular file.
+ std::string getFileContent(const std::string& file_name) const;
+
+ /// @brief Returns the list of client configuration.
+ ///
+ /// @return List of basic HTTP authentication client configuration.
+ const BasicHttpAuthClientList& getClientList() const {
+ return (list_);
+ }
+
+ /// @brief Returns the credential and user id map.
+ ///
+ /// @return The basic HTTP authentication credential and user id map.
+ const BasicHttpAuthMap& getCredentialMap() const {
+ return (map_);
+ }
+
+ /// @brief Parses basic HTTP authentication configuration.
+ ///
+ /// @param config Element holding the basic HTTP authentication
+ /// configuration to be parsed.
+ /// @throw DhcpConfigError when the configuration is invalid.
+ void parse(const isc::data::ConstElementPtr& config);
+
+ /// @brief Unparses basic HTTP authentication configuration.
+ ///
+ /// @return A pointer to unparsed basic HTTP authentication configuration.
+ virtual isc::data::ElementPtr toElement() const;
+
+ /// @brief Validate HTTP request.
+ ///
+ /// @param creator The HTTP response creator.
+ /// @param request The HTTP request to validate.
+ /// @return Error HTTP response if validation failed, null otherwise.
+ virtual isc::http::HttpResponseJsonPtr
+ checkAuth(const isc::http::HttpResponseCreator& creator,
+ const isc::http::HttpRequestPtr& request) const;
+
+private:
+
+ /// @brief The list of basic HTTP authentication client configuration.
+ BasicHttpAuthClientList list_;
+
+ /// @brief The basic HTTP authentication credential and user id map.
+ BasicHttpAuthMap map_;
+};
+
+/// @brief Type of shared pointers to basic HTTP authentication configuration.
+typedef boost::shared_ptr<BasicHttpAuthConfig> BasicHttpAuthConfigPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // endif HTTP_BASIC_AUTH_CONFIG_H
diff --git a/src/lib/http/client.cc b/src/lib/http/client.cc
new file mode 100644
index 0000000..570dab1
--- /dev/null
+++ b/src/lib/http/client.cc
@@ -0,0 +1,2063 @@
+// Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/tls_socket.h>
+#include <http/client.h>
+#include <http/http_log.h>
+#include <http/http_messages.h>
+#include <http/response_json.h>
+#include <http/response_parser.h>
+#include <util/boost_time_utils.h>
+#include <util/multi_threading_mgr.h>
+#include <util/unlock_guard.h>
+
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/weak_ptr.hpp>
+
+#include <atomic>
+#include <array>
+#include <functional>
+#include <iostream>
+#include <map>
+#include <mutex>
+#include <queue>
+#include <thread>
+
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::http;
+using namespace isc::util;
+using namespace boost::posix_time;
+
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Maximum size of the HTTP message that can be logged.
+///
+/// The part of the HTTP message beyond this value is truncated.
+constexpr size_t MAX_LOGGED_MESSAGE_SIZE = 1024;
+
+/// @brief TCP / TLS socket callback function type.
+typedef std::function<void(boost::system::error_code ec, size_t length)>
+SocketCallbackFunction;
+
+/// @brief Socket callback class required by the TCPSocket and TLSSocket APIs.
+///
+/// Its function call operator ignores callbacks invoked with "operation aborted"
+/// error codes. Such status codes are generated when the posted IO operations
+/// are canceled.
+class SocketCallback {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Stores pointer to a callback function provided by a caller.
+ ///
+ /// @param socket_callback Pointer to a callback function.
+ SocketCallback(SocketCallbackFunction socket_callback)
+ : callback_(socket_callback) {
+ }
+
+ /// @brief Function call operator.
+ ///
+ /// Invokes the callback for all error codes except "operation aborted".
+ ///
+ /// @param ec Error code.
+ /// @param length Length of the data transmitted over the socket.
+ void operator()(boost::system::error_code ec, size_t length = 0) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+ }
+ callback_(ec, length);
+ }
+
+private:
+
+ /// @brief Holds pointer to a supplied callback.
+ SocketCallbackFunction callback_;
+
+};
+
+class ConnectionPool;
+
+/// @brief Shared pointer to a connection pool.
+typedef boost::shared_ptr<ConnectionPool> ConnectionPoolPtr;
+
+/// @brief Client side HTTP connection to the server.
+///
+/// Each connection is established with a unique destination identified by the
+/// specified URL and TLS context. Multiple requests to the same destination
+/// can be sent over the same connection, if the connection is persistent.
+/// If the server closes the TCP connection (e.g. after sending a response),
+/// the connection is closed.
+///
+/// If new request is created while the previous request is still in progress,
+/// the new request is stored in the FIFO queue. The queued requests to the
+/// particular URL are sent to the server when the current transaction ends.
+///
+/// The communication over the transport socket is asynchronous. The caller is
+/// notified about the completion of the transaction via a callback that the
+/// caller supplies when initiating the transaction.
+class Connection : public boost::enable_shared_from_this<Connection> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used for the connection.
+ /// @param tls_context TLS context to be used for the connection.
+ /// @param conn_pool Back pointer to the connection pool to which this
+ /// connection belongs.
+ /// @param url URL associated with this connection.
+ explicit Connection(IOService& io_service,
+ const TlsContextPtr& tls_context,
+ const ConnectionPoolPtr& conn_pool,
+ const Url& url);
+
+ /// @brief Destructor.
+ ~Connection();
+
+ /// @brief Starts new asynchronous transaction (HTTP request and response).
+ ///
+ /// This method expects that all pointers provided as argument are non-null.
+ ///
+ /// @param request Pointer to the request to be sent to the server.
+ /// @param response Pointer to the object into which the response is stored.
+ /// The caller should create a response object of the type which matches the
+ /// content type expected by the caller, e.g. HttpResponseJson when JSON
+ /// content type is expected to be received.
+ /// @param request_timeout Request timeout in milliseconds.
+ /// @param callback Pointer to the callback function to be invoked when the
+ /// transaction completes.
+ /// @param connect_callback Pointer to the callback function to be invoked
+ /// when the client connects to the server.
+ /// @param handshake_callback Optional callback invoked when the client
+ /// performs the TLS handshake with the server.
+ /// @param close_callback Pointer to the callback function to be invoked
+ /// when the client closes the socket to the server.
+ void doTransaction(const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const long request_timeout,
+ const HttpClient::RequestHandler& callback,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback);
+
+ /// @brief Closes the socket and cancels the request timer.
+ void close();
+
+ /// @brief Checks if a transaction has been initiated over this connection.
+ ///
+ /// @return true if transaction has been initiated, false otherwise.
+ bool isTransactionOngoing() const {
+ return (started_);
+ }
+
+ /// @brief Checks if the socket has been closed.
+ ///
+ /// @return true if the socket has been closed.
+ bool isClosed() const {
+ return (closed_);
+ }
+
+ /// @brief Checks if the peer has closed the idle socket at its side.
+ ///
+ /// If the socket is open but is not usable the peer has closed
+ /// the socket at its side so we close it.
+ void isClosedByPeer();
+
+ /// @brief Checks if a socket descriptor belongs to this connection.
+ ///
+ /// @param socket_fd socket descriptor to check
+ ///
+ /// @return True if the socket fd belongs to this connection.
+ bool isMySocket(int socket_fd) const;
+
+ /// @brief Checks and logs if premature transaction timeout is suspected.
+ ///
+ /// There are cases when the premature timeout occurs, e.g. as a result of
+ /// moving system clock, during the transaction. In such case, the
+ /// @c terminate function is called which resets the transaction state but
+ /// the transaction handlers may be already waiting for the execution.
+ /// Each such handler should call this function to check if the transaction
+ /// it is participating in is still alive. If it is not, it should simply
+ /// return. This method also logs such situation.
+ ///
+ /// @param transid identifier of the transaction for which the handler
+ /// is being invoked. It is compared against the current transaction
+ /// id for this connection.
+ ///
+ /// @return true if the premature timeout is suspected, false otherwise.
+ bool checkPrematureTimeout(const uint64_t transid);
+
+private:
+
+ /// @brief Starts new asynchronous transaction (HTTP request and response).
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// This method expects that all pointers provided as argument are non-null.
+ ///
+ /// @param request Pointer to the request to be sent to the server.
+ /// @param response Pointer to the object into which the response is stored.
+ /// The caller should create a response object of the type which matches the
+ /// content type expected by the caller, e.g. HttpResponseJson when JSON
+ /// content type is expected to be received.
+ /// @param request_timeout Request timeout in milliseconds.
+ /// @param callback Pointer to the callback function to be invoked when the
+ /// transaction completes.
+ /// @param connect_callback Pointer to the callback function to be invoked
+ /// when the client connects to the server.
+ /// @param handshake_callback Optional callback invoked when the client
+ /// performs the TLS handshake with the server.
+ /// @param close_callback Pointer to the callback function to be invoked
+ /// when the client closes the socket to the server.
+ void doTransactionInternal(const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const long request_timeout,
+ const HttpClient::RequestHandler& callback,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback);
+
+ /// @brief Closes the socket and cancels the request timer.
+ ///
+ /// Should be called in a thread safe context.
+ void closeInternal();
+
+ /// @brief Checks if the peer has closed the socket at its side.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// If the socket is open but is not usable the peer has closed
+ /// the socket at its side so we close it.
+ void isClosedByPeerInternal();
+
+ /// @brief Checks and logs if premature transaction timeout is suspected.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// There are cases when the premature timeout occurs, e.g. as a result of
+ /// moving system clock, during the transaction. In such case, the
+ /// @c terminate function is called which resets the transaction state but
+ /// the transaction handlers may be already waiting for the execution.
+ /// Each such handler should call this function to check if the transaction
+ /// it is participating in is still alive. If it is not, it should simply
+ /// return. This method also logs such situation.
+ ///
+ /// @param transid identifier of the transaction for which the handler
+ /// is being invoked. It is compared against the current transaction
+ /// id for this connection.
+ ///
+ /// @return true if the premature timeout is suspected, false otherwise.
+ bool checkPrematureTimeoutInternal(const uint64_t transid);
+
+ /// @brief Resets the state of the object.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// In particular, it removes instances of objects provided for the previous
+ /// transaction by a caller. It doesn't close the socket, though.
+ void resetState();
+
+ /// @brief Performs tasks required after receiving a response or after an
+ /// error.
+ ///
+ /// This method triggers user's callback, resets the state of the connection
+ /// and initiates next transaction if there is any transaction queued for the
+ /// URL associated with this connection.
+ ///
+ /// @param ec Error code received as a result of the IO operation.
+ /// @param parsing_error Message parsing error.
+ void terminate(const boost::system::error_code& ec,
+ const std::string& parsing_error = "");
+
+ /// @brief Performs tasks required after receiving a response or after an
+ /// error.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// This method triggers user's callback, resets the state of the connection
+ /// and initiates next transaction if there is any transaction queued for the
+ /// URL associated with this connection.
+ ///
+ /// @param ec Error code received as a result of the IO operation.
+ /// @param parsing_error Message parsing error.
+ void terminateInternal(const boost::system::error_code& ec,
+ const std::string& parsing_error = "");
+
+ /// @brief Run parser and check if more data is needed.
+ ///
+ /// @param ec Error code received as a result of the IO operation.
+ /// @param length Number of bytes received.
+ ///
+ /// @return true if more data is needed, false otherwise.
+ bool runParser(const boost::system::error_code& ec, size_t length);
+
+ /// @brief Run parser and check if more data is needed.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @param ec Error code received as a result of the IO operation.
+ /// @param length Number of bytes received.
+ ///
+ /// @return true if more data is needed, false otherwise.
+ bool runParserInternal(const boost::system::error_code& ec, size_t length);
+
+ /// @brief This method schedules timer or reschedules existing timer.
+ ///
+ /// @param request_timeout New timer interval in milliseconds.
+ void scheduleTimer(const long request_timeout);
+
+ /// @brief Asynchronously performs the TLS handshake.
+ ///
+ /// The TLS handshake is performed once on TLS sockets.
+ ///
+ /// @param transid Current transaction id.
+ void doHandshake(const uint64_t transid);
+
+ /// @brief Asynchronously sends data over the socket.
+ ///
+ /// The data sent over the socket are stored in the @c buf_.
+ ///
+ /// @param transid Current transaction id.
+ void doSend(const uint64_t transid);
+
+ /// @brief Asynchronously receives data over the socket.
+ ///
+ /// The data received over the socket are store into the @c input_buf_.
+ ///
+ /// @param transid Current transaction id.
+ void doReceive(const uint64_t transid);
+
+ /// @brief Local callback invoked when the connection is established.
+ ///
+ /// If the connection is successfully established, this callback will start
+ /// to asynchronously send the request over the socket or perform the
+ /// TLS handshake with the server.
+ ///
+ /// @param Pointer to the callback to be invoked when client connects to
+ /// the server.
+ /// @param transid Current transaction id.
+ /// @param ec Error code being a result of the connection attempt.
+ void connectCallback(HttpClient::ConnectHandler connect_callback,
+ const uint64_t transid,
+ const boost::system::error_code& ec);
+
+ /// @brief Local callback invoked when the handshake is performed.
+ ///
+ /// If the handshake is successfully performed, this callback will start
+ /// to asynchronously send the request over the socket.
+ ///
+ /// @param Pointer to the callback to be invoked when client performs
+ /// the TLS handshake with the server.
+ /// @param transid Current transaction id.
+ /// @param ec Error code being a result of the connection attempt.
+ void handshakeCallback(HttpClient::HandshakeHandler handshake_callback,
+ const uint64_t transid,
+ const boost::system::error_code& ec);
+
+ /// @brief Local callback invoked when an attempt to send a portion of data
+ /// over the socket has ended.
+ ///
+ /// The portion of data that has been sent is removed from the buffer. If all
+ /// data from the buffer were sent, the callback will start to asynchronously
+ /// receive a response from the server.
+ ///
+ /// @param transid Current transaction id.
+ /// @param ec Error code being a result of sending the data.
+ /// @param length Number of bytes sent.
+ void sendCallback(const uint64_t transid, const boost::system::error_code& ec,
+ size_t length);
+
+ /// @brief Local callback invoked when an attempt to receive a portion of data
+ /// over the socket has ended.
+ ///
+ /// @param transid Current transaction id.
+ /// @param ec Error code being a result of receiving the data.
+ /// @param length Number of bytes received.
+ void receiveCallback(const uint64_t transid, const boost::system::error_code& ec,
+ size_t length);
+
+ /// @brief Local callback invoked when request timeout occurs.
+ void timerCallback();
+
+ /// @brief Local callback invoked when the connection is closed.
+ ///
+ /// Invokes the close callback (if one), passing in the socket's
+ /// descriptor, when the connection's socket about to be closed.
+ /// The callback invocation is wrapped in a try-catch to ensure
+ /// exception safety.
+ ///
+ /// @param clear dictates whether or not the callback is discarded
+ /// after invocation. Defaults to false.
+ void closeCallback(const bool clear = false);
+
+ /// @brief Pointer to the connection pool owning this connection.
+ ///
+ /// This is a weak pointer to avoid circular dependency between the
+ /// Connection and ConnectionPool.
+ boost::weak_ptr<ConnectionPool> conn_pool_;
+
+ /// @brief URL for this connection.
+ Url url_;
+
+ /// @brief TLS context for this connection.
+ TlsContextPtr tls_context_;
+
+ /// @brief TCP socket to be used for this connection.
+ std::unique_ptr<TCPSocket<SocketCallback> > tcp_socket_;
+
+ /// @brief TLS socket to be used for this connection.
+ std::unique_ptr<TLSSocket<SocketCallback> > tls_socket_;
+
+ /// @brief Interval timer used for detecting request timeouts.
+ IntervalTimer timer_;
+
+ /// @brief Holds currently sent request.
+ HttpRequestPtr current_request_;
+
+ /// @brief Holds pointer to an object where response is to be stored.
+ HttpResponsePtr current_response_;
+
+ /// @brief Pointer to the HTTP response parser.
+ HttpResponseParserPtr parser_;
+
+ /// @brief User supplied callback.
+ HttpClient::RequestHandler current_callback_;
+
+ /// @brief Output buffer.
+ std::string buf_;
+
+ /// @brief Input buffer.
+ std::array<char, 32768> input_buf_;
+
+ /// @brief Identifier of the current transaction.
+ uint64_t current_transid_;
+
+ /// @brief User supplied handshake callback.
+ HttpClient::HandshakeHandler handshake_callback_;
+
+ /// @brief User supplied close callback.
+ HttpClient::CloseHandler close_callback_;
+
+ /// @brief Flag to indicate that a transaction is running.
+ std::atomic<bool> started_;
+
+ /// @brief Flag to indicate that the TLS handshake has to be performed.
+ std::atomic<bool> need_handshake_;
+
+ /// @brief Flag to indicate that the socket was closed.
+ std::atomic<bool> closed_;
+
+ /// @brief Mutex to protect the internal state.
+ std::mutex mutex_;
+};
+
+/// @brief Shared pointer to the connection.
+typedef boost::shared_ptr<Connection> ConnectionPtr;
+
+/// @brief Connection pool for managing multiple connections.
+///
+/// Connection pool creates and destroys URL destinations. It manages
+/// connections to and requests for URLs. Each time a request is
+/// submitted for a URL, it assigns it to an available idle connection,
+/// or if no idle connections are available, pushes the request on the queue
+/// for that URL.
+class ConnectionPool : public boost::enable_shared_from_this<ConnectionPool> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service to be used by the
+ /// connections.
+ /// @param max_url_connections maximum number of concurrent
+ /// connections allowed per URL.
+ explicit ConnectionPool(IOService& io_service, size_t max_url_connections)
+ : io_service_(io_service), destinations_(), pool_mutex_(),
+ max_url_connections_(max_url_connections) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes all connections.
+ ~ConnectionPool() {
+ closeAll();
+ }
+
+ /// @brief Process next queued request for the given URL and TLS context.
+ ///
+ /// @param url URL for which next queued request should be processed.
+ /// @param tls_context TLS context for which next queued request
+ /// should be processed.
+ void processNextRequest(const Url& url, const TlsContextPtr& tls_context) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(pool_mutex_);
+ return (processNextRequestInternal(url, tls_context));
+ } else {
+ return (processNextRequestInternal(url, tls_context));
+ }
+ }
+
+ /// @brief Schedule processing of next queued request.
+ ///
+ /// @param url URL for which next queued request should be processed.
+ /// @param tls_context TLS context for which next queued request
+ /// should be processed.
+ void postProcessNextRequest(const Url& url,
+ const TlsContextPtr& tls_context) {
+ io_service_.post(std::bind(&ConnectionPool::processNextRequest,
+ shared_from_this(), url, tls_context));
+ }
+
+ /// @brief Queue next request for sending to the server.
+ ///
+ /// A new transaction is started immediately, if there is no other request
+ /// in progress for the given URL. Otherwise, the request is queued.
+ ///
+ /// @param url Destination where the request should be sent.
+ /// @param tls_context TLS context to be used for the connection.
+ /// @param request Pointer to the request to be sent to the server.
+ /// @param response Pointer to the object into which the response should be
+ /// stored.
+ /// @param request_timeout Requested timeout for the transaction in
+ /// milliseconds.
+ /// @param request_callback Pointer to the user callback to be invoked when the
+ /// transaction ends.
+ /// @param connect_callback Pointer to the user callback to be invoked when the
+ /// client connects to the server.
+ /// @param handshake_callback Optional callback invoked when the client
+ /// performs the TLS handshake with the server.
+ /// @param close_callback Pointer to the user callback to be invoked when the
+ /// client closes the connection to the server.
+ void queueRequest(const Url& url,
+ const TlsContextPtr& tls_context,
+ const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const long request_timeout,
+ const HttpClient::RequestHandler& request_callback,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(pool_mutex_);
+ return (queueRequestInternal(url, tls_context, request, response,
+ request_timeout, request_callback,
+ connect_callback, handshake_callback,
+ close_callback));
+ } else {
+ return (queueRequestInternal(url, tls_context, request, response,
+ request_timeout, request_callback,
+ connect_callback, handshake_callback,
+ close_callback));
+ }
+ }
+
+ /// @brief Closes all URLs and removes associated information from
+ /// the connection pool.
+ void closeAll() {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(pool_mutex_);
+ closeAllInternal();
+ } else {
+ closeAllInternal();
+ }
+ }
+
+ /// @brief Closes a connection if it has an out-of-band socket event
+ ///
+ /// If the pool contains a connection using the given socket and that
+ /// connection is currently in a transaction the method returns as this
+ /// indicates a normal ready event. If the connection is not in an
+ /// ongoing transaction, then the connection is closed.
+ ///
+ /// This is method is intended to be used to detect and clean up then
+ /// sockets that are marked ready outside of transactions. The most common
+ /// case is the other end of the socket being closed.
+ ///
+ /// @param socket_fd socket descriptor to check
+ void closeIfOutOfBand(int socket_fd) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(pool_mutex_);
+ closeIfOutOfBandInternal(socket_fd);
+ } else {
+ closeIfOutOfBandInternal(socket_fd);
+ }
+ }
+
+private:
+
+ /// @brief Process next queued request for the given URL and TLS context.
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param url URL for which next queued request should be retrieved.
+ /// @param tls_context TLS context for which next queued request
+ /// should be processed.
+ void processNextRequestInternal(const Url& url,
+ const TlsContextPtr& tls_context) {
+ // Check if there is a queue for this URL. If there is no queue, there
+ // is no request queued either.
+ DestinationPtr destination = findDestination(url, tls_context);
+ if (destination) {
+ // Remove closed connections.
+ destination->garbageCollectConnections();
+ if (!destination->queueEmpty()) {
+ // We have at least one queued request. Do we have an
+ // idle connection?
+ ConnectionPtr connection = destination->getIdleConnection();
+ if (!connection) {
+ // No idle connections.
+ if (destination->connectionsFull()) {
+ return;
+ }
+ // Room to make another connection with this destination,
+ // so make one.
+ connection.reset(new Connection(io_service_, tls_context,
+ shared_from_this(), url));
+ destination->addConnection(connection);
+ }
+
+ // Dequeue the oldest request and start a transaction for it using
+ // the idle connection.
+ RequestDescriptor desc = destination->popNextRequest();
+ connection->doTransaction(desc.request_, desc.response_,
+ desc.request_timeout_, desc.callback_,
+ desc.connect_callback_,
+ desc.handshake_callback_,
+ desc.close_callback_);
+ }
+ }
+ }
+
+ /// @brief Queue next request for sending to the server.
+ ///
+ /// A new transaction is started immediately, if there is no other request
+ /// in progress for the given URL. Otherwise, the request is queued.
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param url Destination where the request should be sent.
+ /// @param tls_context TLS context to be used for the connection.
+ /// @param request Pointer to the request to be sent to the server.
+ /// @param response Pointer to the object into which the response should be
+ /// stored.
+ /// @param request_timeout Requested timeout for the transaction in
+ /// milliseconds.
+ /// @param request_callback Pointer to the user callback to be invoked when the
+ /// transaction ends.
+ /// @param connect_callback Pointer to the user callback to be invoked when the
+ /// client connects to the server.
+ /// @param handshake_callback Optional callback invoked when the client
+ /// performs the TLS handshake with the server.
+ /// @param close_callback Pointer to the user callback to be invoked when the
+ /// client closes the connection to the server.
+ void queueRequestInternal(const Url& url,
+ const TlsContextPtr& tls_context,
+ const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const long request_timeout,
+ const HttpClient::RequestHandler& request_callback,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback) {
+ ConnectionPtr connection;
+ // Find the destination for the requested URL.
+ DestinationPtr destination = findDestination(url, tls_context);
+ if (destination) {
+ // Remove closed connections.
+ destination->garbageCollectConnections();
+ // Found it, look for an idle connection.
+ connection = destination->getIdleConnection();
+ } else {
+ // Doesn't exist yet so it's a new destination.
+ destination = addDestination(url, tls_context);
+ }
+
+ if (!connection) {
+ if (destination->connectionsFull()) {
+ // All connections busy, queue it.
+ destination->pushRequest(RequestDescriptor(request, response,
+ request_timeout,
+ request_callback,
+ connect_callback,
+ handshake_callback,
+ close_callback));
+ return;
+ }
+
+ // Room to make another connection with this destination, so make one.
+ connection.reset(new Connection(io_service_, tls_context,
+ shared_from_this(), url));
+ destination->addConnection(connection);
+ }
+
+ // Use the connection to start the transaction.
+ connection->doTransaction(request, response, request_timeout, request_callback,
+ connect_callback, handshake_callback, close_callback);
+ }
+
+ /// @brief Closes all connections for all URLs and removes associated
+ /// information from the connection pool.
+ ///
+ /// This method should be called in a thread safe context.
+ void closeAllInternal() {
+ for (auto const& destination : destinations_) {
+ destination.second->closeAllConnections();
+ }
+
+ destinations_.clear();
+ }
+
+ /// @brief Closes a connection if it has an out-of-band socket event
+ ///
+ /// If the pool contains a connection using the given socket and that
+ /// connection is currently in a transaction the method returns as this
+ /// indicates a normal ready event. If the connection is not in an
+ /// ongoing transaction, then the connection is closed.
+ ///
+ /// This is method is intended to be used to detect and clean up then
+ /// sockets that are marked ready outside of transactions. The most common
+ /// case is the other end of the socket being closed.
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param socket_fd socket descriptor to check
+ void closeIfOutOfBandInternal(int socket_fd) {
+ for (auto const& destination : destinations_) {
+ // First we look for a connection with the socket.
+ ConnectionPtr connection = destination.second->findBySocketFd(socket_fd);
+ if (connection) {
+ if (!connection->isTransactionOngoing()) {
+ // Socket has no transaction, so any ready event is
+ // out-of-band (other end probably closed), so
+ // let's close it. Note we do not remove any queued
+ // requests, as this might somehow be occurring in
+ // between them.
+ destination.second->closeConnection(connection);
+ }
+
+ return;
+ }
+ }
+ }
+
+ /// @brief Request descriptor holds parameters associated with the
+ /// particular request.
+ struct RequestDescriptor {
+ /// @brief Constructor.
+ ///
+ /// @param request Pointer to the request to be sent.
+ /// @param response Pointer to the object into which the response will
+ /// be stored.
+ /// @param request_timeout Requested timeout for the transaction.
+ /// @param callback Pointer to the user callback.
+ /// @param connect_callback pointer to the user callback to be invoked
+ /// when the client connects to the server.
+ /// @param handshake_callback Optional callback invoked when the client
+ /// performs the TLS handshake with the server.
+ /// @param close_callback pointer to the user callback to be invoked
+ /// when the client closes the connection to the server.
+ RequestDescriptor(const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const long& request_timeout,
+ const HttpClient::RequestHandler& callback,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback)
+ : request_(request), response_(response),
+ request_timeout_(request_timeout), callback_(callback),
+ connect_callback_(connect_callback),
+ handshake_callback_(handshake_callback),
+ close_callback_(close_callback) {
+ }
+
+ /// @brief Holds pointer to the request.
+ HttpRequestPtr request_;
+
+ /// @brief Holds pointer to the response.
+ HttpResponsePtr response_;
+
+ /// @brief Holds requested timeout value.
+ long request_timeout_;
+
+ /// @brief Holds pointer to the user callback.
+ HttpClient::RequestHandler callback_;
+
+ /// @brief Holds pointer to the user callback for connect.
+ HttpClient::ConnectHandler connect_callback_;
+
+ /// @brief Holds pointer to the user callback for handshake.
+ HttpClient::HandshakeHandler handshake_callback_;
+
+ /// @brief Holds pointer to the user callback for close.
+ HttpClient::CloseHandler close_callback_;
+ };
+
+ /// @brief Type of URL and TLS context pairs.
+ typedef std::pair<Url, TlsContextPtr> DestinationDescriptor;
+
+ /// @brief Encapsulates connections and requests for a given URL
+ class Destination {
+ public:
+ /// @brief Number of queued requests allowed without warnings being emitted.
+ const size_t QUEUE_SIZE_THRESHOLD = 2048;
+ /// @brief Interval between queue size warnings.
+ const int QUEUE_WARN_SECS = 5;
+
+ /// @brief Constructor
+ ///
+ /// @param url server URL of this destination
+ /// @param tls_context server TLS context of this destination
+ /// @param max_connections maximum number of concurrent connections
+ /// allowed for in the list URL
+ Destination(Url url, TlsContextPtr tls_context, size_t max_connections)
+ : url_(url), tls_context_(tls_context),
+ max_connections_(max_connections), connections_(), queue_(),
+ last_queue_warn_time_(min_date_time), last_queue_size_(0) {
+ }
+
+ /// @brief Destructor
+ ~Destination() {
+ closeAllConnections();
+ }
+
+ /// @brief Adds a new connection
+ ///
+ /// @param connection the connection to add
+ ///
+ /// @throw BadValue if the maximum number of connections already
+ /// exist.
+ /// @note This should be called in a thread safe context.
+ void addConnection(ConnectionPtr connection) {
+ if (connectionsFull()) {
+ isc_throw(BadValue, "URL: " << url_.toText()
+ << ", already at maximum connections: "
+ << max_connections_);
+ }
+
+ connections_.push_back(connection);
+ }
+
+ /// @brief Closes a connection and removes it from the list.
+ ///
+ /// @param connection the connection to remove
+ /// @note This should be called in a thread safe context.
+ void closeConnection(ConnectionPtr connection) {
+ for (auto it = connections_.begin(); it != connections_.end(); ++it) {
+ if (*it == connection) {
+ (*it)->close();
+ connections_.erase(it);
+ break;
+ }
+ }
+ }
+
+ /// @brief Closes all connections and clears the list.
+ /// @note This should be called in a thread safe context.
+ void closeAllConnections() {
+ // Flush the queue.
+ while (!queue_.empty()) {
+ queue_.pop();
+ }
+
+ for (auto const& connection : connections_) {
+ connection->close();
+ }
+
+ connections_.clear();
+ }
+
+ /// @brief Removes closed connections.
+ ///
+ /// This method should be called before @ref getIdleConnection.
+ ///
+ /// In a first step it closes not usable idle connections
+ /// (idle means no current transaction and not closed,
+ /// usable means the peer side did not close it at that time).
+ /// In a second step it removes (collects) closed connections.
+ ///
+ /// @note a connection is closed when the transaction is finished
+ /// and the connection is persistent, or when the connection was
+ /// idle and the first step of the garbage collector detects that
+ /// it was closed by peer, so is not usable.
+ ///
+ /// @note there are two races here:
+ /// - the peer side closes the connection after the first step
+ /// - a not persistent connection finishes its transaction and
+ /// closes
+ /// The second race is avoided by setting the closed flag before
+ /// the started flag and by unconditionally posting a process next
+ /// request action.
+ ///
+ /// @note This should be called in a thread safe context.
+ void garbageCollectConnections() {
+ for (auto it = connections_.begin(); it != connections_.end();) {
+ (*it)->isClosedByPeer();
+ if (!(*it)->isClosed()) {
+ ++it;
+ } else {
+ it = connections_.erase(it);
+ }
+ }
+ }
+
+ /// @brief Finds the first idle connection.
+ ///
+ /// Iterates over the existing connections and returns the
+ /// first connection which is not currently in a transaction and
+ /// is not closed.
+ ///
+ /// @note @ref garbageCollectConnections should be called before.
+ /// This removes connections which were closed at that time.
+ ///
+ /// @return The first idle connection or an empty pointer if
+ /// all connections are busy or closed.
+ ConnectionPtr getIdleConnection() {
+ for (auto const& connection : connections_) {
+ if (!connection->isTransactionOngoing() &&
+ !connection->isClosed()) {
+ return (connection);
+ }
+ }
+
+ return (ConnectionPtr());
+ }
+
+ /// @brief Find a connection by its socket descriptor.
+ ///
+ /// @param socket_fd socket descriptor to find
+ ///
+ /// @return The connection or an empty pointer if no matching
+ /// connection exists.
+ ConnectionPtr findBySocketFd(int socket_fd) {
+ for (auto const& connection : connections_) {
+ if (connection->isMySocket(socket_fd)) {
+ return (connection);
+ }
+ }
+
+ return (ConnectionPtr());
+ }
+
+ /// @brief Indicates if there are no connections in the list.
+ ///
+ /// @return true if the list is empty.
+ bool connectionsEmpty() {
+ return (connections_.empty());
+ }
+
+ /// @brief Indicates if list contains the maximum number.
+ ///
+ /// @return true if the list is full.
+ bool connectionsFull() {
+ return (connections_.size() >= max_connections_);
+ }
+
+ /// @brief Fetches the number of connections in the list.
+ ///
+ /// @return the number of connections in the list.
+ size_t connectionCount() {
+ return (connections_.size());
+ }
+
+ /// @brief Fetches the maximum number of connections.
+ ///
+ /// @return the maxim number of connections.
+ size_t getMaxConnections() const {
+ return (max_connections_);
+ }
+
+ /// @brief Indicates if request queue is empty.
+ ///
+ /// @return true if there are no requests queued.
+ bool queueEmpty() const {
+ return (queue_.empty());
+ }
+
+ /// @brief Adds a request to the end of the request queue.
+ ///
+ /// If the size of the queue exceeds a threshold and appears
+ /// to be growing it will emit a warning log.
+ ///
+ /// @param desc RequestDescriptor to queue.
+ void pushRequest(RequestDescriptor desc) {
+ queue_.push(desc);
+ size_t size = queue_.size();
+ // If the queue size is larger than the threshold and growing, issue a
+ // periodic warning.
+ if ((size > QUEUE_SIZE_THRESHOLD) && (size > last_queue_size_)) {
+ ptime now = microsec_clock::universal_time();
+ if ((now - last_queue_warn_time_) > seconds(QUEUE_WARN_SECS)) {
+ LOG_WARN(http_logger, HTTP_CLIENT_QUEUE_SIZE_GROWING)
+ .arg(url_.toText())
+ .arg(size);
+ // Remember the last time we warned.
+ last_queue_warn_time_ = now;
+ }
+ }
+
+ // Remember the previous size.
+ last_queue_size_ = size;
+ }
+
+ /// @brief Removes a request from the front of the request queue.
+ ///
+ /// @return desc RequestDescriptor of the removed request.
+ RequestDescriptor popNextRequest() {
+ if (queue_.empty()) {
+ isc_throw(InvalidOperation, "cannot pop, queue is empty");
+ }
+
+ RequestDescriptor desc = queue_.front();
+ queue_.pop();
+ return (desc);
+ }
+
+ private:
+ /// @brief URL supported by this destination.
+ Url url_;
+
+ /// @brief TLS context to use with this destination.
+ TlsContextPtr tls_context_;
+
+ /// @brief Maximum number of concurrent connections for this destination.
+ size_t max_connections_;
+
+ /// @brief List of concurrent connections.
+ std::list<ConnectionPtr> connections_;
+
+ /// @brief Holds the queue of request for this destination.
+ std::queue<RequestDescriptor> queue_;
+
+ /// @brief Time the last queue size warning was issued.
+ ptime last_queue_warn_time_;
+
+ /// @brief Size of the queue after last push.
+ size_t last_queue_size_;
+ };
+
+ /// @brief Pointer to a Destination.
+ typedef boost::shared_ptr<Destination> DestinationPtr;
+
+ /// @brief Creates a new destination for the given URL and TLS context.
+ ///
+ /// @param url URL of the new destination.
+ /// @param tls_context TLS context for the new destination.
+ ///
+ /// @return Pointer to the newly created destination.
+ /// @note Must be called from within a thread-safe context.
+ DestinationPtr addDestination(const Url& url,
+ const TlsContextPtr& tls_context) {
+ const DestinationDescriptor& desc = std::make_pair(url, tls_context);
+ DestinationPtr destination(new Destination(url, tls_context,
+ max_url_connections_));
+ destinations_[desc] = destination;
+ return (destination);
+ }
+
+ /// @brief Fetches a destination by URL and TLS context.
+ ///
+ /// @param url URL of the destination desired.
+ /// @param tls_context TLS context for the destination desired.
+ ///
+ /// @return pointer the desired destination, empty pointer
+ /// if the destination does not exist.
+ /// @note Must be called from within a thread-safe context.
+ DestinationPtr findDestination(const Url& url,
+ const TlsContextPtr& tls_context) const {
+ const DestinationDescriptor& desc = std::make_pair(url, tls_context);
+ auto it = destinations_.find(desc);
+ if (it != destinations_.end()) {
+ return (it->second);
+ }
+
+ return (DestinationPtr());
+ }
+
+ /// @brief Removes a destination by URL and TLS context.
+ ///
+ /// Closes all of the destination's connections and
+ /// discards all of its queued requests while removing
+ /// the destination from the list of known destinations.
+ ///
+ /// @note not used yet.
+ ///
+ /// @param url URL of the destination to be removed.
+ /// @param tls_context TLS context for the destination to be removed.
+ /// @note Must be called from within a thread-safe context.
+ void removeDestination(const Url& url,
+ const TlsContextPtr& tls_context) {
+ const DestinationDescriptor& desc = std::make_pair(url, tls_context);
+ auto it = destinations_.find(desc);
+ if (it != destinations_.end()) {
+ it->second->closeAllConnections();
+ destinations_.erase(it);
+ }
+ }
+
+ /// @brief A reference to the IOService that drives socket IO.
+ IOService& io_service_;
+
+ /// @brief Map of Destinations by URL and TLS context.
+ std::map<DestinationDescriptor, DestinationPtr> destinations_;
+
+ /// @brief Mutex to protect the internal state.
+ std::mutex pool_mutex_;
+
+ /// @brief Maximum number of connections per URL and TLS context.
+ size_t max_url_connections_;
+};
+
+Connection::Connection(IOService& io_service,
+ const TlsContextPtr& tls_context,
+ const ConnectionPoolPtr& conn_pool,
+ const Url& url)
+ : conn_pool_(conn_pool), url_(url), tls_context_(tls_context),
+ tcp_socket_(), tls_socket_(), timer_(io_service),
+ current_request_(), current_response_(), parser_(),
+ current_callback_(), buf_(), input_buf_(), current_transid_(0),
+ close_callback_(), started_(false), need_handshake_(false),
+ closed_(false) {
+ if (!tls_context) {
+ tcp_socket_.reset(new asiolink::TCPSocket<SocketCallback>(io_service));
+ } else {
+ tls_socket_.reset(new asiolink::TLSSocket<SocketCallback>(io_service,
+ tls_context));
+ need_handshake_ = true;
+ }
+}
+
+Connection::~Connection() {
+ close();
+}
+
+void
+Connection::resetState() {
+ started_ = false;
+ current_request_.reset();
+ current_response_.reset();
+ parser_.reset();
+ current_callback_ = HttpClient::RequestHandler();
+}
+
+void
+Connection::closeCallback(const bool clear) {
+ if (close_callback_) {
+ try {
+ if (tcp_socket_) {
+ close_callback_(tcp_socket_->getNative());
+ } else if (tls_socket_) {
+ close_callback_(tls_socket_->getNative());
+ } else {
+ isc_throw(Unexpected,
+ "internal error: can't find a socket to close");
+ }
+ } catch (...) {
+ LOG_ERROR(http_logger, HTTP_CONNECTION_CLOSE_CALLBACK_FAILED);
+ }
+ }
+
+ if (clear) {
+ close_callback_ = HttpClient::CloseHandler();
+ }
+}
+
+void
+Connection::isClosedByPeer() {
+ // This method applies only to idle connections.
+ if (started_ || closed_) {
+ return;
+ }
+ // This code was guarded by a lock so keep this.
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ isClosedByPeerInternal();
+ } else {
+ isClosedByPeerInternal();
+ }
+}
+
+void
+Connection::isClosedByPeerInternal() {
+ // If the socket is open we check if it is possible to transmit
+ // the data over this socket by reading from it with message
+ // peeking. If the socket is not usable, we close it and then
+ // re-open it. There is a narrow window of time between checking
+ // the socket usability and actually transmitting the data over
+ // this socket, when the peer may close the connection. In this
+ // case we'll need to re-transmit but we don't handle it here.
+ if (tcp_socket_) {
+ if (tcp_socket_->getASIOSocket().is_open() &&
+ !tcp_socket_->isUsable()) {
+ closeCallback();
+ closed_ = true;
+ tcp_socket_->close();
+ }
+ } else if (tls_socket_) {
+ if (tls_socket_->getASIOSocket().is_open() &&
+ !tls_socket_->isUsable()) {
+ closeCallback();
+ closed_ = true;
+ tls_socket_->close();
+ }
+ } else {
+ isc_throw(Unexpected, "internal error: can't find the sending socket");
+ }
+}
+
+void
+Connection::doTransaction(const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const long request_timeout,
+ const HttpClient::RequestHandler& callback,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ doTransactionInternal(request, response, request_timeout,
+ callback, connect_callback, handshake_callback,
+ close_callback);
+ } else {
+ doTransactionInternal(request, response, request_timeout,
+ callback, connect_callback, handshake_callback,
+ close_callback);
+ }
+}
+
+void
+Connection::doTransactionInternal(const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const long request_timeout,
+ const HttpClient::RequestHandler& callback,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback) {
+ try {
+ started_ = true;
+ current_request_ = request;
+ current_response_ = response;
+ parser_.reset(new HttpResponseParser(*current_response_));
+ parser_->initModel();
+ current_callback_ = callback;
+ handshake_callback_ = handshake_callback;
+ close_callback_ = close_callback;
+
+ // Starting new transaction. Generate new transaction id.
+ ++current_transid_;
+
+ buf_ = request->toString();
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTP_CLIENT_REQUEST_SEND)
+ .arg(request->toBriefString())
+ .arg(url_.toText());
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ HTTP_CLIENT_REQUEST_SEND_DETAILS)
+ .arg(url_.toText())
+ .arg(HttpMessageParserBase::logFormatHttpMessage(request->toString(),
+ MAX_LOGGED_MESSAGE_SIZE));
+
+ // Setup request timer.
+ scheduleTimer(request_timeout);
+
+ /// @todo We're getting a hostname but in fact it is expected to be an IP address.
+ /// We should extend the TCPEndpoint to also accept names. Currently, it will fall
+ /// over for names.
+ TCPEndpoint endpoint(url_.getStrippedHostname(),
+ static_cast<unsigned short>(url_.getPort()));
+ SocketCallback socket_cb(std::bind(&Connection::connectCallback, shared_from_this(),
+ connect_callback, current_transid_,
+ ph::_1));
+
+ // Establish new connection or use existing connection.
+ if (tcp_socket_) {
+ tcp_socket_->open(&endpoint, socket_cb);
+ return;
+ }
+ if (tls_socket_) {
+ tls_socket_->open(&endpoint, socket_cb);
+ return;
+ }
+
+ // Should never reach this point.
+ isc_throw(Unexpected, "internal error: can't find a socket to open");
+
+ } catch (const std::exception& ex) {
+ // Re-throw with the expected exception type.
+ isc_throw(HttpClientError, ex.what());
+ }
+}
+
+void
+Connection::close() {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ return (closeInternal());
+ } else {
+ return (closeInternal());
+ }
+}
+
+void
+Connection::closeInternal() {
+ // Pass in true to discard the callback.
+ closeCallback(true);
+
+ closed_ = true;
+ timer_.cancel();
+ if (tcp_socket_) {
+ tcp_socket_->close();
+ }
+ if (tls_socket_) {
+ tls_socket_->close();
+ }
+
+ resetState();
+}
+
+bool
+Connection::isMySocket(int socket_fd) const {
+ if (tcp_socket_) {
+ return (tcp_socket_->getNative() == socket_fd);
+ } else if (tls_socket_) {
+ return (tls_socket_->getNative() == socket_fd);
+ }
+ // Should never reach this point.
+ std::cerr << "internal error: can't find my socket\n";
+ return (false);
+}
+
+bool
+Connection::checkPrematureTimeout(const uint64_t transid) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ return (checkPrematureTimeoutInternal(transid));
+ } else {
+ return (checkPrematureTimeoutInternal(transid));
+ }
+}
+
+bool
+Connection::checkPrematureTimeoutInternal(const uint64_t transid) {
+ // If there is no transaction but the handlers are invoked it means
+ // that the last transaction in the queue timed out prematurely.
+ // Also, if there is a transaction in progress but the ID of that
+ // transaction doesn't match the one associated with the handler it,
+ // also means that the transaction timed out prematurely.
+ if (!isTransactionOngoing() || (transid != current_transid_)) {
+ LOG_WARN(http_logger, HTTP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED)
+ .arg(isTransactionOngoing())
+ .arg(transid)
+ .arg(current_transid_);
+ return (true);
+ }
+
+ return (false);
+}
+
+void
+Connection::terminate(const boost::system::error_code& ec,
+ const std::string& parsing_error) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ terminateInternal(ec, parsing_error);
+ } else {
+ terminateInternal(ec, parsing_error);
+ }
+}
+
+void
+Connection::terminateInternal(const boost::system::error_code& ec,
+ const std::string& parsing_error) {
+ HttpResponsePtr response;
+ if (isTransactionOngoing()) {
+
+ timer_.cancel();
+ if (tcp_socket_) {
+ tcp_socket_->cancel();
+ }
+ if (tls_socket_) {
+ tls_socket_->cancel();
+ }
+
+ if (!ec && current_response_->isFinalized()) {
+ response = current_response_;
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
+ HTTP_SERVER_RESPONSE_RECEIVED)
+ .arg(url_.toText());
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
+ HTTP_SERVER_RESPONSE_RECEIVED_DETAILS)
+ .arg(url_.toText())
+ .arg(parser_ ?
+ parser_->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE) :
+ "[HttpResponseParser is null]");
+
+ } else {
+ std::string err = parsing_error.empty() ? ec.message() :
+ parsing_error;
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
+ HTTP_BAD_SERVER_RESPONSE_RECEIVED)
+ .arg(url_.toText())
+ .arg(err);
+
+ // Only log the details if we have received anything and tried
+ // to parse it.
+ if (!parsing_error.empty()) {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
+ HTTP_BAD_SERVER_RESPONSE_RECEIVED_DETAILS)
+ .arg(url_.toText())
+ .arg(parser_ ?
+ parser_->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE) :
+ "[HttpResponseParser is null]");
+ }
+ }
+
+ try {
+ // The callback should take care of its own exceptions but one
+ // never knows.
+ if (MultiThreadingMgr::instance().getMode()) {
+ UnlockGuard<std::mutex> lock(mutex_);
+ current_callback_(ec, response, parsing_error);
+ } else {
+ current_callback_(ec, response, parsing_error);
+ }
+ } catch (...) {
+ }
+
+ // If we're not requesting connection persistence or the
+ // connection has timed out, we should close the socket.
+ if (!closed_ &&
+ (!current_request_->isPersistent() ||
+ (ec == boost::asio::error::timed_out))) {
+ closeInternal();
+ }
+
+ resetState();
+ }
+
+ // Check if there are any requests queued for this destination and start
+ // another transaction if there is at least one.
+ ConnectionPoolPtr conn_pool = conn_pool_.lock();
+ if (conn_pool) {
+ conn_pool->postProcessNextRequest(url_, tls_context_);
+ }
+}
+
+void
+Connection::scheduleTimer(const long request_timeout) {
+ if (request_timeout > 0) {
+ timer_.setup(std::bind(&Connection::timerCallback, this), request_timeout,
+ IntervalTimer::ONE_SHOT);
+ }
+}
+
+void
+Connection::doHandshake(const uint64_t transid) {
+ // Skip the handshake if it is not needed.
+ if (!need_handshake_) {
+ doSend(transid);
+ return;
+ }
+
+ SocketCallback socket_cb(std::bind(&Connection::handshakeCallback,
+ shared_from_this(),
+ handshake_callback_,
+ transid,
+ ph::_1));
+ try {
+ tls_socket_->handshake(socket_cb);
+
+ } catch (...) {
+ terminate(boost::asio::error::not_connected);
+ }
+}
+
+void
+Connection::doSend(const uint64_t transid) {
+ SocketCallback socket_cb(std::bind(&Connection::sendCallback,
+ shared_from_this(),
+ transid,
+ ph::_1,
+ ph::_2));
+ try {
+ if (tcp_socket_) {
+ tcp_socket_->asyncSend(&buf_[0], buf_.size(), socket_cb);
+ return;
+ }
+
+ if (tls_socket_) {
+ tls_socket_->asyncSend(&buf_[0], buf_.size(), socket_cb);
+ return;
+ }
+
+ // Should never reach this point.
+ std::cerr << "internal error: can't find a socket to send to\n";
+ isc_throw(Unexpected,
+ "internal error: can't find a socket to send to");
+ } catch (...) {
+ terminate(boost::asio::error::not_connected);
+ }
+}
+
+void
+Connection::doReceive(const uint64_t transid) {
+ TCPEndpoint endpoint;
+ SocketCallback socket_cb(std::bind(&Connection::receiveCallback,
+ shared_from_this(),
+ transid,
+ ph::_1,
+ ph::_2));
+ try {
+ if (tcp_socket_) {
+ tcp_socket_->asyncReceive(static_cast<void*>(input_buf_.data()),
+ input_buf_.size(), 0,
+ &endpoint, socket_cb);
+ return;
+ }
+ if (tls_socket_) {
+ tls_socket_->asyncReceive(static_cast<void*>(input_buf_.data()),
+ input_buf_.size(), 0,
+ &endpoint, socket_cb);
+ return;
+ }
+ // Should never reach this point.
+ std::cerr << "internal error: can't find a socket to receive from\n";
+ isc_throw(Unexpected,
+ "internal error: can't find a socket to receive from");
+
+ } catch (...) {
+ terminate(boost::asio::error::not_connected);
+ }
+}
+
+void
+Connection::connectCallback(HttpClient::ConnectHandler connect_callback,
+ const uint64_t transid,
+ const boost::system::error_code& ec) {
+ if (checkPrematureTimeout(transid)) {
+ return;
+ }
+
+ // Run user defined connect callback if specified.
+ if (connect_callback) {
+ // If the user defined callback indicates that the connection
+ // should not be continued.
+ if (tcp_socket_) {
+ if (!connect_callback(ec, tcp_socket_->getNative())) {
+ return;
+ }
+ } else if (tls_socket_) {
+ if (!connect_callback(ec, tls_socket_->getNative())) {
+ return;
+ }
+ } else {
+ // Should never reach this point.
+ std::cerr << "internal error: can't find a socket to connect\n";
+ }
+ }
+
+ if (ec && (ec.value() == boost::asio::error::operation_aborted)) {
+ return;
+
+ // In some cases the "in progress" status code may be returned. It doesn't
+ // indicate an error. Sending the request over the socket is expected to
+ // be successful. Getting such status appears to be highly dependent on
+ // the operating system.
+ } else if (ec &&
+ (ec.value() != boost::asio::error::in_progress) &&
+ (ec.value() != boost::asio::error::already_connected)) {
+ terminate(ec);
+
+ } else {
+ // Start the TLS handshake asynchronously.
+ doHandshake(transid);
+ }
+}
+
+void
+Connection::handshakeCallback(HttpClient::ConnectHandler handshake_callback,
+ const uint64_t transid,
+ const boost::system::error_code& ec) {
+ need_handshake_ = false;
+ if (checkPrematureTimeout(transid)) {
+ return;
+ }
+
+ // Run user defined handshake callback if specified.
+ if (handshake_callback) {
+ // If the user defined callback indicates that the connection
+ // should not be continued.
+ if (tls_socket_) {
+ if (!handshake_callback(ec, tls_socket_->getNative())) {
+ return;
+ }
+ } else {
+ // Should never reach this point.
+ std::cerr << "internal error: can't find TLS socket\n";
+ }
+ }
+
+ if (ec && (ec.value() == boost::asio::error::operation_aborted)) {
+ return;
+ } else if (ec) {
+ terminate(ec);
+
+ } else {
+ // Start sending the request asynchronously.
+ doSend(transid);
+ }
+}
+
+void
+Connection::sendCallback(const uint64_t transid,
+ const boost::system::error_code& ec,
+ size_t length) {
+ if (checkPrematureTimeout(transid)) {
+ return;
+ }
+
+ if (ec) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ // EAGAIN and EWOULDBLOCK don't really indicate an error. The length
+ // should be 0 in this case but let's be sure.
+ } else if ((ec.value() == boost::asio::error::would_block) ||
+ (ec.value() == boost::asio::error::try_again)) {
+ length = 0;
+
+ } else {
+ // Any other error should cause the transaction to terminate.
+ terminate(ec);
+ return;
+ }
+ }
+
+ // Sending is in progress, so push back the timeout.
+ scheduleTimer(timer_.getInterval());
+
+ // If any data have been sent, remove it from the buffer and only leave the
+ // portion that still has to be sent.
+ if (length > 0) {
+ buf_.erase(0, length);
+ }
+
+ // If there is no more data to be sent, start receiving a response. Otherwise,
+ // continue sending.
+ if (buf_.empty()) {
+ doReceive(transid);
+
+ } else {
+ doSend(transid);
+ }
+}
+
+void
+Connection::receiveCallback(const uint64_t transid,
+ const boost::system::error_code& ec,
+ size_t length) {
+ if (checkPrematureTimeout(transid)) {
+ return;
+ }
+
+ if (ec) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ // EAGAIN and EWOULDBLOCK don't indicate an error in this case. All
+ // other errors should terminate the transaction.
+ }
+ if ((ec.value() != boost::asio::error::try_again) &&
+ (ec.value() != boost::asio::error::would_block)) {
+ terminate(ec);
+ return;
+
+ } else {
+ // For EAGAIN and EWOULDBLOCK the length should be 0 anyway, but let's
+ // make sure.
+ length = 0;
+ }
+ }
+
+ // Receiving is in progress, so push back the timeout.
+ scheduleTimer(timer_.getInterval());
+
+ if (runParser(ec, length)) {
+ doReceive(transid);
+ }
+}
+
+bool
+Connection::runParser(const boost::system::error_code& ec, size_t length) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ return (runParserInternal(ec, length));
+ } else {
+ return (runParserInternal(ec, length));
+ }
+}
+
+bool
+Connection::runParserInternal(const boost::system::error_code& ec,
+ size_t length) {
+ // If we have received any data, let's feed the parser with it.
+ if (length != 0) {
+ parser_->postBuffer(static_cast<void*>(input_buf_.data()), length);
+ parser_->poll();
+ }
+
+ // If the parser still needs data, let's schedule another receive.
+ if (parser_->needData()) {
+ return (true);
+
+ } else if (parser_->httpParseOk()) {
+ // No more data needed and parsing has been successful so far. Let's
+ // try to finalize the response parsing.
+ try {
+ current_response_->finalize();
+ terminateInternal(ec);
+
+ } catch (const std::exception& ex) {
+ // If there is an error here, we need to return the error message.
+ terminateInternal(ec, ex.what());
+ }
+
+ } else {
+ // Parsing was unsuccessful. Let's pass the error message held in the
+ // parser.
+ terminateInternal(ec, parser_->getErrorMessage());
+ }
+
+ return (false);
+}
+
+void
+Connection::timerCallback() {
+ // Request timeout occurred.
+ terminate(boost::asio::error::timed_out);
+}
+
+}
+
+namespace isc {
+namespace http {
+
+/// @brief HttpClient implementation.
+class HttpClientImpl {
+public:
+ /// @brief Constructor.
+ ///
+ /// If single-threading:
+ /// - Creates the connection pool passing in the caller's IOService
+ /// and a maximum number of connections per URL value of 1.
+ /// If multi-threading:
+ /// - Creates a private IOService
+ /// - Creates a thread pool with the thread_pool_size threads
+ /// - Creates the connection pool passing the private IOService
+ /// and the thread_pool_size as the maximum number of connections
+ /// per URL.
+ ///
+ /// @param io_service IOService that will drive connection IO in single
+ /// threaded mode. (Currently ignored in multi-threaded mode)
+ /// @param thread_pool_size maximum number of concurrent threads
+ /// Internally this also sets the maximum number of concurrent connections
+ /// per URL.
+ /// @param defer_thread_start When true, starting of the pool threads is
+ /// deferred until a subsequent call to @ref start(). In this case the
+ /// pool's operational state after construction is STOPPED. Otherwise,
+ /// the thread pool threads will be created and started, with the
+ /// operational state being RUNNING. Applicable only when thread-pool size
+ /// is greater than zero.
+ HttpClientImpl(IOService& io_service, size_t thread_pool_size = 0,
+ bool defer_thread_start = false)
+ : thread_pool_size_(thread_pool_size), thread_pool_() {
+ if (thread_pool_size_ > 0) {
+ // Create our own private IOService.
+ thread_io_service_.reset(new IOService());
+
+ // Create the thread pool.
+ thread_pool_.reset(new HttpThreadPool(thread_io_service_, thread_pool_size_,
+ defer_thread_start));
+
+ // Create the connection pool. Note that we use the thread_pool_size
+ // as the maximum connections per URL value.
+ conn_pool_.reset(new ConnectionPool(*thread_io_service_, thread_pool_size_));
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC, HTTP_CLIENT_MT_STARTED)
+ .arg(thread_pool_size_);
+ } else {
+ // Single-threaded mode: use the caller's IOService,
+ // one connection per URL.
+ conn_pool_.reset(new ConnectionPool(io_service, 1));
+ }
+ }
+
+ /// @brief Destructor
+ ///
+ /// Calls stop().
+ ~HttpClientImpl() {
+ stop();
+ }
+
+ /// @brief Check if the current thread can perform thread pool state
+ /// transition.
+ ///
+ /// @throw MultiThreadingInvalidOperation if the state transition is done on
+ /// any of the worker threads.
+ void checkPermissions() {
+ if (thread_pool_) {
+ thread_pool_->checkPausePermissions();
+ }
+ }
+
+ /// @brief Starts running the client's thread pool, if multi-threaded.
+ void start() {
+ if (thread_pool_) {
+ thread_pool_->run();
+ }
+ }
+
+ /// @brief Close all connections, and if multi-threaded, stops the client's
+ /// thread pool.
+ void stop() {
+ // Close all the connections.
+ conn_pool_->closeAll();
+
+ // Stop the thread pool.
+ if (thread_pool_) {
+ thread_pool_->stop();
+ }
+ }
+
+ /// @brief Pauses the client's thread pool.
+ ///
+ /// Suspends thread pool event processing.
+ /// @throw InvalidOperation if the thread pool does not exist.
+ void pause() {
+ if (!thread_pool_) {
+ isc_throw(InvalidOperation, "HttpClient::pause - no thread pool");
+ }
+
+ // Pause the thread pool.
+ thread_pool_->pause();
+ }
+
+ /// @brief Resumes running the client's thread pool.
+ ///
+ /// Resumes thread pool event processing.
+ /// @throw InvalidOperation if the thread pool does not exist.
+ void resume() {
+ if (!thread_pool_) {
+ isc_throw(InvalidOperation, "HttpClient::resume - no thread pool");
+ }
+
+ // Resume running the thread pool.
+ thread_pool_->run();
+ }
+
+ /// @brief Indicates if the thread pool is running.
+ ///
+ /// @return True if the thread pool exists and it is in the RUNNING state,
+ /// false otherwise.
+ bool isRunning() {
+ if (thread_pool_) {
+ return (thread_pool_->isRunning());
+ }
+
+ return (false);
+ }
+
+ /// @brief Indicates if the thread pool is stopped.
+ ///
+ /// @return True if the thread pool exists and it is in the STOPPED state,
+ /// false otherwise.
+ bool isStopped() {
+ if (thread_pool_) {
+ return (thread_pool_->isStopped());
+ }
+
+ return (false);
+ }
+
+ /// @brief Indicates if the thread pool is paused.
+ ///
+ /// @return True if the thread pool exists and it is in the PAUSED state,
+ /// false otherwise.
+ bool isPaused() {
+ if (thread_pool_) {
+ return (thread_pool_->isPaused());
+ }
+
+ return (false);
+ }
+
+ /// @brief Fetches the internal IOService used in multi-threaded mode.
+ ///
+ /// @return A pointer to the IOService, or an empty pointer when
+ /// in single-threaded mode.
+ asiolink::IOServicePtr getThreadIOService() {
+ return (thread_io_service_);
+ };
+
+ /// @brief Fetches the maximum size of the thread pool.
+ ///
+ /// @return the maximum size of the thread pool.
+ uint16_t getThreadPoolSize() {
+ return (thread_pool_size_);
+ }
+
+ /// @brief Fetches the number of threads in the pool.
+ ///
+ /// @return the number of running threads.
+ uint16_t getThreadCount() {
+ if (!thread_pool_) {
+ return (0);
+ }
+ return (thread_pool_->getThreadCount());
+ }
+
+ /// @brief Holds a pointer to the connection pool.
+ ConnectionPoolPtr conn_pool_;
+
+private:
+
+ /// @brief Maxim number of threads in the thread pool.
+ size_t thread_pool_size_;
+
+ /// @brief Pointer to private IOService used in multi-threaded mode.
+ asiolink::IOServicePtr thread_io_service_;
+
+ /// @brief Pool of threads used to service connections in multi-threaded
+ /// mode.
+ HttpThreadPoolPtr thread_pool_;
+};
+
+HttpClient::HttpClient(IOService& io_service, size_t thread_pool_size,
+ bool defer_thread_start /* = false */) {
+ if (thread_pool_size > 0) {
+ if (!MultiThreadingMgr::instance().getMode()) {
+ isc_throw(InvalidOperation,
+ "HttpClient thread_pool_size must be zero"
+ "when Kea core multi-threading is disabled");
+ }
+ }
+
+ impl_.reset(new HttpClientImpl(io_service, thread_pool_size,
+ defer_thread_start));
+}
+
+HttpClient::~HttpClient() {
+}
+
+void
+HttpClient::asyncSendRequest(const Url& url,
+ const TlsContextPtr& tls_context,
+ const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const HttpClient::RequestHandler& request_callback,
+ const HttpClient::RequestTimeout& request_timeout,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback) {
+ if (!url.isValid()) {
+ isc_throw(HttpClientError, "invalid URL specified for the HTTP client");
+ }
+
+ if ((url.getScheme() == Url::Scheme::HTTPS) && !tls_context) {
+ isc_throw(HttpClientError, "HTTPS URL scheme but no TLS context");
+ }
+
+ if (!request) {
+ isc_throw(HttpClientError, "HTTP request must not be null");
+ }
+
+ if (!response) {
+ isc_throw(HttpClientError, "HTTP response must not be null");
+ }
+
+ if (!request_callback) {
+ isc_throw(HttpClientError, "callback for HTTP transaction must not be null");
+ }
+
+ impl_->conn_pool_->queueRequest(url, tls_context, request, response,
+ request_timeout.value_,
+ request_callback, connect_callback,
+ handshake_callback, close_callback);
+}
+
+void
+HttpClient::closeIfOutOfBand(int socket_fd) {
+ return (impl_->conn_pool_->closeIfOutOfBand(socket_fd));
+}
+
+void
+HttpClient::start() {
+ impl_->start();
+}
+
+void
+HttpClient::checkPermissions() {
+ impl_->checkPermissions();
+}
+
+void
+HttpClient::pause() {
+ impl_->pause();
+}
+
+void
+HttpClient::resume() {
+ impl_->resume();
+}
+
+void
+HttpClient::stop() {
+ impl_->stop();
+}
+
+const IOServicePtr
+HttpClient::getThreadIOService() const {
+ return (impl_->getThreadIOService());
+}
+
+uint16_t
+HttpClient::getThreadPoolSize() const {
+ return (impl_->getThreadPoolSize());
+}
+
+uint16_t
+HttpClient::getThreadCount() const {
+ return (impl_->getThreadCount());
+}
+
+bool
+HttpClient::isRunning() {
+ return (impl_->isRunning());
+}
+
+bool
+HttpClient::isStopped() {
+ return (impl_->isStopped());
+}
+
+bool
+HttpClient::isPaused() {
+ return (impl_->isPaused());
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/client.h b/src/lib/http/client.h
new file mode 100644
index 0000000..69c441c
--- /dev/null
+++ b/src/lib/http/client.h
@@ -0,0 +1,342 @@
+// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_CLIENT_H
+#define HTTP_CLIENT_H
+
+#include <asiolink/io_service.h>
+#include <asiolink/tls_socket.h>
+#include <exceptions/exceptions.h>
+#include <http/url.h>
+#include <http/request.h>
+#include <http/response.h>
+#include <http/http_thread_pool.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <string>
+#include <thread>
+#include <vector>
+
+namespace isc {
+namespace http {
+
+/// @brief A generic error raised by the @ref HttpClient class.
+class HttpClientError : public Exception {
+public:
+ HttpClientError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+class HttpClientImpl;
+
+/// @brief HTTP client class.
+///
+/// This class implements an asynchronous HTTP client. The caller can schedule
+/// transmission of the HTTP request using @ref HttpClient::asyncSendRequest
+/// method. The caller specifies target URL for each request. The caller also
+/// specifies a pointer to the @ref HttpRequest or derived class, holding a
+/// request that should be transmitted to the destination. Such request must
+/// be finalized, i.e. @ref HttpRequest::finalize method must be called prior
+/// to sending it. The caller must also provide a pointer to the
+/// @ref HttpResponse object or an object derived from it. The type of the
+/// response object must match the expected content type to be returned in the
+/// server's response. The last argument specified in this call is the pointer
+/// to the callback function, which should be launched when the response is
+/// received, an error occurs or when a timeout in the transmission is
+/// signaled.
+///
+/// The HTTP client supports multiple simultaneous and persistent connections
+/// with different destinations. The client determines if the connection is
+/// persistent by looking into the Connection header and HTTP version of the
+/// request. If the connection should be persistent the client doesn't
+/// close the connection after sending a request and receiving a response from
+/// the server. If the client is provided with the request to be sent to the
+/// particular destination, but there is an ongoing communication with this
+/// destination, e.g. as a result of sending previous request, the new
+/// request is queued in the FIFO queue. When the previous request completes,
+/// the next request in the queue for the particular URL will be initiated.
+///
+/// Furthermore, the class supports two modes of operation: single-threaded
+/// and multi-threaded mode. In single-threaded mode, all IO is driven by
+/// an external IOService passed into the class constructor, and ultimately
+/// only a single connection per URL can be open at any given time.
+///
+/// In multi-threaded mode an internal thread pool driven by a private
+/// IOService instance is used to support multiple concurrent connections
+/// per URL. Currently, the number of connections per URL is set to the
+/// number of threads in the thread pool.
+///
+/// The client tests the persistent connection for usability before sending
+/// a request by trying to read from the socket (with message peeking). If
+/// the socket is usable the client uses it to transmit the request.
+///
+/// This classes exposes the underlying transport socket's descriptor for
+/// each connection via connect, handshake and close callbacks.
+/// This is done to permit the sockets to be monitored for IO readiness
+/// by external code that's something other than boost::asio
+/// (e.g.select() or epoll()), and would thus otherwise starve the
+/// client's IOService and cause a backlog of ready event handlers.
+///
+/// All errors are reported to the caller via the callback function supplied
+/// to the @ref HttpClient::asyncSendRequest. The IO errors are communicated
+/// via the @c boost::system::error code value. The response parsing errors
+/// are returned via the 3rd parameter of the callback.
+class HttpClient {
+public:
+ /// @brief HTTP request/response timeout value.
+ struct RequestTimeout {
+ /// @brief Constructor.
+ ///
+ /// @param value Request/response timeout value in milliseconds.
+ explicit RequestTimeout(long value)
+ : value_(value) {
+ }
+ long value_; ///< Timeout value specified.
+ };
+
+ /// @brief Callback type used in call to @ref HttpClient::asyncSendRequest.
+ typedef std::function<void(const boost::system::error_code&,
+ const HttpResponsePtr&,
+ const std::string&)> RequestHandler;
+
+ /// @brief Optional handler invoked when client connects to the server.
+ ///
+ /// Returned boolean value indicates whether the client should continue
+ /// connecting to the server (if true) or not (false).
+ /// It is passed the IO error code along with the native socket handle of
+ /// the connection's TCP socket. The passed socket descriptor may be used
+ /// to monitor the readiness of the events via select() or epoll().
+ ///
+ /// @note Beware that the IO error code can be set to "in progress"
+ /// so a not null error code does not always mean the connect failed.
+ typedef std::function<bool(const boost::system::error_code&, const int)> ConnectHandler;
+
+ /// @brief Optional handler invoked when client performs the TLS handshake
+ /// with the server.
+ ///
+ /// It is called when the TLS handshake completes:
+ /// - if the handshake succeeds it is called with error code 0
+ /// - if the handshake fails it is called with error code != 0
+ /// - if TLS is not enabled, it is not called at all
+ ///
+ /// Returned boolean value indicates whether the client should continue
+ /// connecting to the server (if true) or not (false).
+ /// @note The second argument is not used.
+ typedef std::function<bool(const boost::system::error_code&, const int)> HandshakeHandler;
+
+ /// @brief Optional handler invoked when client closes the connection to the server.
+ ///
+ /// It is passed the native socket handler of the connection's TCP socket.
+ typedef std::function<void(const int)> CloseHandler;
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the HTTP client.
+ /// @param thread_pool_size maximum number of threads in the thread pool.
+ /// A value greater than zero enables multi-threaded mode and sets the
+ /// maximum number of concurrent connections per URL. A value of zero
+ /// (default) enables single-threaded mode with one connection per URL.
+ /// @param defer_thread_start When true, starting of the pool threads is
+ /// deferred until a subsequent call to @ref start(). In this case the
+ /// pool's operational state after construction is STOPPED. Otherwise,
+ /// the thread pool threads will be created and started, with the
+ /// operational state being RUNNING. Applicable only when thread-pool size
+ /// is greater than zero.
+ explicit HttpClient(asiolink::IOService& io_service, size_t thread_pool_size = 0,
+ bool defer_thread_start = false);
+
+ /// @brief Destructor.
+ ~HttpClient();
+
+ /// @brief Queues new asynchronous HTTP request for a given URL.
+ ///
+ /// The client maintains an internal connection pool which manages lists
+ /// of connections per URL. In single-threaded mode, each URL is limited
+ /// to a single connection. In multi-threaded mode, each URL may have
+ /// more than one open connection per URL, enabling the client to carry
+ /// on multiple concurrent requests per URL.
+ ///
+ /// The client will search the pool for an open, idle connection for the
+ /// given URL. If there are no idle connections, the client will open
+ /// a new connection up to the maximum number of connections allowed by the
+ /// thread mode. If all possible connections are busy, the request is
+ /// pushed on to back of a URL-specific FIFO queue of pending requests.
+ ///
+ /// If however, there is an idle connection available than a new transaction
+ /// for the request will be initiated immediately upon that connection.
+ ///
+ /// Note that when a connection completes a transaction, and its URL
+ /// queue is not empty, it will pop a pending request from the front of
+ /// the queue and begin a new transaction for that request. The net effect
+ /// is that requests are always pulled from the front of the queue unless
+ /// the queue is empty.
+ ///
+ /// The existing connection is tested before it is used for the new
+ /// transaction by attempting to read (with message peeking) from
+ /// the open transport socket. If the read attempt is successful,
+ /// the client will transmit the HTTP request to the server using
+ /// this connection. It is possible that the server closes the
+ /// connection between the connection test and sending the request.
+ /// In such case, an error will be returned and the caller will
+ /// need to try re-sending the request.
+ ///
+ /// If the connection test fails, the client will close the socket and
+ /// reconnect to the server prior to sending the request.
+ ///
+ /// Pointers to the request and response objects are provided as arguments
+ /// of this method. These pointers should have appropriate types derived
+ /// from the @ref HttpRequest and @ref HttpResponse classes. For example,
+ /// if the request has content type "application/json", a pointer to the
+ /// @ref HttpResponseJson should be passed. In this case, the response type
+ /// should be @ref HttpResponseJson. These types are used to validate both
+ /// the request provided by the caller and the response received from the
+ /// server.
+ ///
+ /// The callback function provided by the caller is invoked when the
+ /// transaction terminates, i.e. when the server has responded or when an
+ /// error occurred. The callback is expected to be exception safe, but the
+ /// client internally guards against exceptions thrown by the callback.
+ ///
+ /// The first argument of the callback indicates an IO error during
+ /// communication with the server. If the communication is successful the
+ /// error code of 0 is returned. However, in this case it is still possible
+ /// that the transaction is unsuccessful due to HTTP response parsing error,
+ /// e.g. invalid content type, malformed response etc. Such errors are
+ /// indicated via third argument.
+ ///
+ /// If message parsing was successful the second argument of the callback
+ /// contains a pointer to the parsed response (the same pointer as provided
+ /// by the caller as the argument). If parsing was unsuccessful, the null
+ /// pointer is returned.
+ ///
+ /// The default timeout for the transaction is set to 10 seconds
+ /// (10 000 ms). If the timeout occurs, the callback is invoked with the
+ /// error code of @c boost::asio::error::timed_out.
+ /// The timeout covers both the connect and the transaction phases
+ /// so when connecting to the server takes too long (e.g. with a
+ /// misconfigured firewall) the timeout is triggered. The connect
+ /// callback can be used to recognize this condition.
+ ///
+ /// @param url URL where the request should be send.
+ /// @param tls_context TLS context.
+ /// @param request Pointer to the object holding a request.
+ /// @param response Pointer to the object where response should be stored.
+ /// @param request_callback Pointer to the user callback function invoked
+ /// when transaction ends.
+ /// @param request_timeout Timeout for the transaction in milliseconds.
+ /// @param connect_callback Optional callback invoked when the client
+ /// connects to the server.
+ /// @param handshake_callback Optional callback invoked when the client
+ /// performs the TLS handshake with the server.
+ /// @param close_callback Optional callback invoked when the client
+ /// closes the connection to the server.
+ ///
+ /// @throw HttpClientError If invalid arguments were provided.
+ void asyncSendRequest(const Url& url,
+ const asiolink::TlsContextPtr& tls_context,
+ const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const RequestHandler& request_callback,
+ const RequestTimeout& request_timeout =
+ RequestTimeout(10000),
+ const ConnectHandler& connect_callback =
+ ConnectHandler(),
+ const HandshakeHandler& handshake_callback =
+ HandshakeHandler(),
+ const CloseHandler& close_callback =
+ CloseHandler());
+
+ /// @brief Check if the current thread can perform thread pool state
+ /// transition.
+ ///
+ /// @throw MultiThreadingInvalidOperation if the state transition is done on
+ /// any of the worker threads.
+ void checkPermissions();
+
+ /// @brief Starts running the client's thread pool, if multi-threaded.
+ void start();
+
+ /// @brief Pauses the client's thread pool.
+ ///
+ /// Suspends thread pool event processing.
+ /// @throw InvalidOperation if the thread pool does not exist.
+ void pause();
+
+ /// @brief Resumes running the client's thread pool.
+ ///
+ /// Resumes thread pool event processing.
+ /// @throw InvalidOperation if the thread pool does not exist.
+ void resume();
+
+ /// @brief Halts client-side IO activity.
+ ///
+ /// Closes all connections, discards any queued requests, and in
+ /// multi-threaded mode discards the thread-pool and the internal
+ /// IOService.
+ void stop();
+
+ /// @brief Closes a connection if it has an out-of-band socket event
+ ///
+ /// If the client owns a connection using the given socket and that
+ /// connection is currently in a transaction the method returns as this
+ /// indicates a normal ready event. If the connection is not in an
+ /// ongoing transaction, then the connection is closed.
+ ///
+ /// This is method is intended to be used to detect and clean up then
+ /// sockets that are marked ready outside of transactions. The most common
+ /// case is the other end of the socket being closed.
+ ///
+ /// @param socket_fd socket descriptor to check
+ void closeIfOutOfBand(int socket_fd);
+
+ /// @brief Fetches a pointer to the internal IOService used to
+ /// drive the thread-pool in multi-threaded mode.
+ ///
+ /// @return pointer to the IOService instance, or an empty pointer
+ /// in single-threaded mode.
+ const asiolink::IOServicePtr getThreadIOService() const;
+
+ /// @brief Fetches the maximum size of the thread pool.
+ ///
+ /// @return the maximum size of the thread pool.
+ uint16_t getThreadPoolSize() const;
+
+ /// @brief Fetches the number of threads in the pool.
+ ///
+ /// @return the number of running threads.
+ uint16_t getThreadCount() const;
+
+ /// @brief Indicates if the thread pool is running.
+ ///
+ /// @return True if the thread pool exists and it is in the RUNNING state,
+ /// false otherwise.
+ bool isRunning();
+
+ /// @brief Indicates if the thread pool is stopped.
+ ///
+ /// @return True if the thread pool exists and it is in the STOPPED state,
+ /// false otherwise.
+ bool isStopped();
+
+ /// @brief Indicates if the thread pool is paused.
+ ///
+ /// @return True if the thread pool exists and it is in the PAUSED state,
+ /// false otherwise.
+ bool isPaused();
+
+private:
+
+ /// @brief Pointer to the HTTP client implementation.
+ boost::shared_ptr<HttpClientImpl> impl_;
+};
+
+/// @brief Defines a pointer to an HttpClient instance.
+typedef boost::shared_ptr<HttpClient> HttpClientPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/connection.cc b/src/lib/http/connection.cc
new file mode 100644
index 0000000..b1e57bd
--- /dev/null
+++ b/src/lib/http/connection.cc
@@ -0,0 +1,600 @@
+// Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <http/connection.h>
+#include <http/connection_pool.h>
+#include <http/http_log.h>
+#include <http/http_messages.h>
+#include <boost/make_shared.hpp>
+#include <functional>
+
+using namespace isc::asiolink;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Maximum size of the HTTP message that can be logged.
+///
+/// The part of the HTTP message beyond this value is truncated.
+constexpr size_t MAX_LOGGED_MESSAGE_SIZE = 1024;
+
+}
+
+namespace isc {
+namespace http {
+
+HttpConnection::Transaction::Transaction(const HttpResponseCreatorPtr& response_creator,
+ const HttpRequestPtr& request)
+ : request_(request ? request : response_creator->createNewHttpRequest()),
+ parser_(new HttpRequestParser(*request_)),
+ input_buf_(),
+ output_buf_() {
+ parser_->initModel();
+}
+
+HttpConnection::TransactionPtr
+HttpConnection::Transaction::create(const HttpResponseCreatorPtr& response_creator) {
+ return (boost::make_shared<Transaction>(response_creator));
+}
+
+HttpConnection::TransactionPtr
+HttpConnection::Transaction::spawn(const HttpResponseCreatorPtr& response_creator,
+ const TransactionPtr& transaction) {
+ if (transaction) {
+ return (boost::make_shared<Transaction>(response_creator,
+ transaction->getRequest()));
+ }
+ return (create(response_creator));
+}
+
+void
+HttpConnection::
+SocketCallback::operator()(boost::system::error_code ec, size_t length) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+ }
+ callback_(ec, length);
+}
+
+HttpConnection::HttpConnection(asiolink::IOService& io_service,
+ const HttpAcceptorPtr& acceptor,
+ const TlsContextPtr& tls_context,
+ HttpConnectionPool& connection_pool,
+ const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback,
+ const long request_timeout,
+ const long idle_timeout)
+ : request_timer_(io_service),
+ request_timeout_(request_timeout),
+ tls_context_(tls_context),
+ idle_timeout_(idle_timeout),
+ tcp_socket_(),
+ tls_socket_(),
+ acceptor_(acceptor),
+ connection_pool_(connection_pool),
+ response_creator_(response_creator),
+ acceptor_callback_(callback) {
+ if (!tls_context) {
+ tcp_socket_.reset(new asiolink::TCPSocket<SocketCallback>(io_service));
+ } else {
+ tls_socket_.reset(new asiolink::TLSSocket<SocketCallback>(io_service,
+ tls_context));
+ }
+}
+
+HttpConnection::~HttpConnection() {
+ close();
+}
+
+void
+HttpConnection::recordParameters(const HttpRequestPtr& request) const {
+ if (!request) {
+ // Should never happen.
+ return;
+ }
+
+ // Record the remote address.
+ request->setRemote(getRemoteEndpointAddressAsText());
+
+ // Record TLS parameters.
+ if (!tls_socket_) {
+ return;
+ }
+
+ // The connection uses HTTPS aka HTTP over TLS.
+ request->setTls(true);
+
+ // Record the first commonName of the subjectName of the client
+ // certificate when wanted.
+ if (HttpRequest::recordSubject_) {
+ request->setSubject(tls_socket_->getTlsStream().getSubject());
+ }
+
+ // Record the first commonName of the issuerName of the client
+ // certificate when wanted.
+ if (HttpRequest::recordIssuer_) {
+ request->setIssuer(tls_socket_->getTlsStream().getIssuer());
+ }
+}
+
+void
+HttpConnection::shutdownCallback(const boost::system::error_code&) {
+ tls_socket_->close();
+}
+
+void
+HttpConnection::shutdown() {
+ request_timer_.cancel();
+ if (tcp_socket_) {
+ tcp_socket_->close();
+ return;
+ }
+ if (tls_socket_) {
+ // Create instance of the callback to close the socket.
+ SocketCallback cb(std::bind(&HttpConnection::shutdownCallback,
+ shared_from_this(),
+ ph::_1)); // error_code
+ tls_socket_->shutdown(cb);
+ return;
+ }
+ // Not reachable?
+ isc_throw(Unexpected, "internal error: unable to shutdown the socket");
+}
+
+void
+HttpConnection::close() {
+ request_timer_.cancel();
+ if (tcp_socket_) {
+ tcp_socket_->close();
+ return;
+ }
+ if (tls_socket_) {
+ tls_socket_->close();
+ return;
+ }
+ // Not reachable?
+ isc_throw(Unexpected, "internal error: unable to close the socket");
+}
+
+void
+HttpConnection::shutdownConnection() {
+ try {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
+ HTTP_CONNECTION_SHUTDOWN)
+ .arg(getRemoteEndpointAddressAsText());
+ connection_pool_.shutdown(shared_from_this());
+ } catch (...) {
+ LOG_ERROR(http_logger, HTTP_CONNECTION_SHUTDOWN_FAILED);
+ }
+}
+
+void
+HttpConnection::stopThisConnection() {
+ try {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
+ HTTP_CONNECTION_STOP)
+ .arg(getRemoteEndpointAddressAsText());
+ connection_pool_.stop(shared_from_this());
+ } catch (...) {
+ LOG_ERROR(http_logger, HTTP_CONNECTION_STOP_FAILED);
+ }
+}
+
+void
+HttpConnection::asyncAccept() {
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying boost functions make copies
+ // as needed.
+ HttpAcceptorCallback cb = std::bind(&HttpConnection::acceptorCallback,
+ shared_from_this(),
+ ph::_1); // error
+ try {
+ HttpsAcceptorPtr tls_acceptor =
+ boost::dynamic_pointer_cast<HttpsAcceptor>(acceptor_);
+ if (!tls_acceptor) {
+ if (!tcp_socket_) {
+ isc_throw(Unexpected, "internal error: TCP socket is null");
+ }
+ acceptor_->asyncAccept(*tcp_socket_, cb);
+ } else {
+ if (!tls_socket_) {
+ isc_throw(Unexpected, "internal error: TLS socket is null");
+ }
+ tls_acceptor->asyncAccept(*tls_socket_, cb);
+ }
+ } catch (const std::exception& ex) {
+ isc_throw(HttpConnectionError, "unable to start accepting TCP "
+ "connections: " << ex.what());
+ }
+}
+
+void
+HttpConnection::doHandshake() {
+ // Skip the handshake if the socket is not a TLS one.
+ if (!tls_socket_) {
+ doRead();
+ return;
+ }
+
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying boost functions make copies
+ // as needed.
+ SocketCallback cb(std::bind(&HttpConnection::handshakeCallback,
+ shared_from_this(),
+ ph::_1)); // error
+ try {
+ tls_socket_->handshake(cb);
+
+ } catch (const std::exception& ex) {
+ isc_throw(HttpConnectionError, "unable to perform TLS handshake: "
+ << ex.what());
+ }
+}
+
+void
+HttpConnection::doRead(TransactionPtr transaction) {
+ try {
+ TCPEndpoint endpoint;
+
+ // Transaction hasn't been created if we are starting to read the
+ // new request.
+ if (!transaction) {
+ transaction = Transaction::create(response_creator_);
+ recordParameters(transaction->getRequest());
+ }
+
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying std functions make copies
+ // as needed.
+ SocketCallback cb(std::bind(&HttpConnection::socketReadCallback,
+ shared_from_this(),
+ transaction,
+ ph::_1, // error
+ ph::_2)); //bytes_transferred
+ if (tcp_socket_) {
+ tcp_socket_->asyncReceive(static_cast<void*>(transaction->getInputBufData()),
+ transaction->getInputBufSize(),
+ 0, &endpoint, cb);
+ return;
+ }
+ if (tls_socket_) {
+ tls_socket_->asyncReceive(static_cast<void*>(transaction->getInputBufData()),
+ transaction->getInputBufSize(),
+ 0, &endpoint, cb);
+ return;
+ }
+ } catch (...) {
+ stopThisConnection();
+ }
+}
+
+void
+HttpConnection::doWrite(HttpConnection::TransactionPtr transaction) {
+ try {
+ if (transaction->outputDataAvail()) {
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying std functions make copies
+ // as needed.
+ SocketCallback cb(std::bind(&HttpConnection::socketWriteCallback,
+ shared_from_this(),
+ transaction,
+ ph::_1, // error
+ ph::_2)); // bytes_transferred
+ if (tcp_socket_) {
+ tcp_socket_->asyncSend(transaction->getOutputBufData(),
+ transaction->getOutputBufSize(),
+ cb);
+ return;
+ }
+ if (tls_socket_) {
+ tls_socket_->asyncSend(transaction->getOutputBufData(),
+ transaction->getOutputBufSize(),
+ cb);
+ return;
+ }
+ } else {
+ // The isPersistent() function may throw if the request hasn't
+ // been created, i.e. the HTTP headers weren't parsed. We catch
+ // this exception below and close the connection since we're
+ // unable to tell if the connection should remain persistent
+ // or not. The default is to close it.
+ if (!transaction->getRequest()->isPersistent()) {
+ stopThisConnection();
+
+ } else {
+ // The connection is persistent and we are done sending
+ // the previous response. Start listening for the next
+ // requests.
+ setupIdleTimer();
+ doRead();
+ }
+ }
+ } catch (...) {
+ stopThisConnection();
+ }
+}
+
+void
+HttpConnection::asyncSendResponse(const ConstHttpResponsePtr& response,
+ TransactionPtr transaction) {
+ transaction->setOutputBuf(response->toString());
+ doWrite(transaction);
+}
+
+
+void
+HttpConnection::acceptorCallback(const boost::system::error_code& ec) {
+ if (!acceptor_->isOpen()) {
+ return;
+ }
+
+ if (ec) {
+ stopThisConnection();
+ }
+
+ acceptor_callback_(ec);
+
+ if (!ec) {
+ if (!tls_context_) {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTP_REQUEST_RECEIVE_START)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(static_cast<unsigned>(request_timeout_/1000));
+ } else {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTP_CONNECTION_HANDSHAKE_START)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(static_cast<unsigned>(request_timeout_/1000));
+ }
+
+ setupRequestTimer();
+ doHandshake();
+ }
+}
+
+void
+HttpConnection::handshakeCallback(const boost::system::error_code& ec) {
+ if (ec) {
+ LOG_INFO(http_logger, HTTP_CONNECTION_HANDSHAKE_FAILED)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(ec.message());
+ stopThisConnection();
+ } else {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTPS_REQUEST_RECEIVE_START)
+ .arg(getRemoteEndpointAddressAsText());
+
+ doRead();
+ }
+}
+
+void
+HttpConnection::socketReadCallback(HttpConnection::TransactionPtr transaction,
+ boost::system::error_code ec, size_t length) {
+ if (ec) {
+ // IO service has been stopped and the connection is probably
+ // going to be shutting down.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ // EWOULDBLOCK and EAGAIN are special cases. Everything else is
+ // treated as fatal error.
+ } else if ((ec.value() != boost::asio::error::try_again) &&
+ (ec.value() != boost::asio::error::would_block)) {
+ stopThisConnection();
+
+ // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
+ // read something from the socket on the next attempt. Just make sure
+ // we don't try to read anything now in case there is any garbage
+ // passed in length.
+ } else {
+ length = 0;
+ }
+ }
+
+ // Receiving is in progress, so push back the timeout.
+ setupRequestTimer(transaction);
+
+ if (length != 0) {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ HTTP_DATA_RECEIVED)
+ .arg(length)
+ .arg(getRemoteEndpointAddressAsText());
+
+ transaction->getParser()->postBuffer(static_cast<void*>(transaction->getInputBufData()),
+ length);
+ transaction->getParser()->poll();
+ }
+
+ if (transaction->getParser()->needData()) {
+ // The parser indicates that the some part of the message being
+ // received is still missing, so continue to read.
+ doRead(transaction);
+
+ } else {
+ try {
+ // The whole message has been received, so let's finalize it.
+ transaction->getRequest()->finalize();
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
+ HTTP_CLIENT_REQUEST_RECEIVED)
+ .arg(getRemoteEndpointAddressAsText());
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
+ HTTP_CLIENT_REQUEST_RECEIVED_DETAILS)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(transaction->getParser()->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
+
+ } catch (const std::exception& ex) {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
+ HTTP_BAD_CLIENT_REQUEST_RECEIVED)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(ex.what());
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
+ HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(transaction->getParser()->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
+ }
+
+ // Don't want to timeout if creation of the response takes long.
+ request_timer_.cancel();
+
+ // Create the response from the received request using the custom
+ // response creator.
+ HttpResponsePtr response = response_creator_->createHttpResponse(transaction->getRequest());
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
+ HTTP_SERVER_RESPONSE_SEND)
+ .arg(response->toBriefString())
+ .arg(getRemoteEndpointAddressAsText());
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
+ HTTP_SERVER_RESPONSE_SEND_DETAILS)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(HttpMessageParserBase::logFormatHttpMessage(response->toString(),
+ MAX_LOGGED_MESSAGE_SIZE));
+
+ // Response created. Activate the timer again.
+ setupRequestTimer(transaction);
+
+ // Start sending the response.
+ asyncSendResponse(response, transaction);
+ }
+}
+
+void
+HttpConnection::socketWriteCallback(HttpConnection::TransactionPtr transaction,
+ boost::system::error_code ec, size_t length) {
+ if (ec) {
+ // IO service has been stopped and the connection is probably
+ // going to be shutting down.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ // EWOULDBLOCK and EAGAIN are special cases. Everything else is
+ // treated as fatal error.
+ } else if ((ec.value() != boost::asio::error::try_again) &&
+ (ec.value() != boost::asio::error::would_block)) {
+ stopThisConnection();
+
+ // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
+ // read something from the socket on the next attempt.
+ } else {
+ // Sending is in progress, so push back the timeout.
+ setupRequestTimer(transaction);
+
+ doWrite(transaction);
+ }
+ }
+
+ // Since each transaction has its own output buffer, it is not really
+ // possible that the number of bytes written is larger than the size
+ // of the buffer. But, let's be safe and set the length to the size
+ // of the buffer if that unexpected condition occurs.
+ if (length > transaction->getOutputBufSize()) {
+ length = transaction->getOutputBufSize();
+ }
+
+ if (length <= transaction->getOutputBufSize()) {
+ // Sending is in progress, so push back the timeout.
+ setupRequestTimer(transaction);
+ }
+
+ // Eat the 'length' number of bytes from the output buffer and only
+ // leave the part of the response that hasn't been sent.
+ transaction->consumeOutputBuf(length);
+
+ // Schedule the write of the unsent data.
+ doWrite(transaction);
+}
+
+void
+HttpConnection::setupRequestTimer(TransactionPtr transaction) {
+ // Pass raw pointer rather than shared_ptr to this object,
+ // because IntervalTimer already passes shared pointer to the
+ // IntervalTimerImpl to make sure that the callback remains
+ // valid.
+ request_timer_.setup(std::bind(&HttpConnection::requestTimeoutCallback,
+ this, transaction),
+ request_timeout_, IntervalTimer::ONE_SHOT);
+}
+
+void
+HttpConnection::setupIdleTimer() {
+ request_timer_.setup(std::bind(&HttpConnection::idleTimeoutCallback,
+ this),
+ idle_timeout_, IntervalTimer::ONE_SHOT);
+}
+
+void
+HttpConnection::requestTimeoutCallback(TransactionPtr transaction) {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED)
+ .arg(getRemoteEndpointAddressAsText());
+
+ // We need to differentiate the transactions between a normal response and the
+ // timeout. We create new transaction from the current transaction. It is
+ // to preserve the request we're responding to.
+ auto spawned_transaction = Transaction::spawn(response_creator_, transaction);
+
+ // The new transaction inherits the request from the original transaction
+ // if such transaction exists.
+ auto request = spawned_transaction->getRequest();
+
+ // Depending on when the timeout occurred, the HTTP version of the request
+ // may or may not be available. Therefore we check if the HTTP version is
+ // set in the request. If it is not available, we need to create a dummy
+ // request with the default HTTP/1.0 version. This version will be used
+ // in the response.
+ if (request->context()->http_version_major_ == 0) {
+ request.reset(new HttpRequest(HttpRequest::Method::HTTP_POST, "/",
+ HttpVersion::HTTP_10(),
+ HostHttpHeader("dummy")));
+ request->finalize();
+ }
+
+ // Create the timeout response.
+ HttpResponsePtr response =
+ response_creator_->createStockHttpResponse(request,
+ HttpStatusCode::REQUEST_TIMEOUT);
+
+ // Send the HTTP 408 status.
+ asyncSendResponse(response, spawned_transaction);
+}
+
+void
+HttpConnection::idleTimeoutCallback() {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED)
+ .arg(getRemoteEndpointAddressAsText());
+ // In theory we should shutdown first and stop/close after but
+ // it is better to put the connection management responsibility
+ // on the client... so simply drop idle connections.
+ stopThisConnection();
+}
+
+std::string
+HttpConnection::getRemoteEndpointAddressAsText() const {
+ try {
+ if (tcp_socket_) {
+ if (tcp_socket_->getASIOSocket().is_open()) {
+ return (tcp_socket_->getASIOSocket().remote_endpoint().address().to_string());
+ }
+ } else if (tls_socket_) {
+ if (tls_socket_->getASIOSocket().is_open()) {
+ return (tls_socket_->getASIOSocket().remote_endpoint().address().to_string());
+ }
+ }
+ } catch (...) {
+ }
+ return ("(unknown address)");
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/connection.h b/src/lib/http/connection.h
new file mode 100644
index 0000000..70109de
--- /dev/null
+++ b/src/lib/http/connection.h
@@ -0,0 +1,432 @@
+// Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_CONNECTION_H
+#define HTTP_CONNECTION_H
+
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <http/http_acceptor.h>
+#include <http/request_parser.h>
+#include <http/response_creator_factory.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/system/error_code.hpp>
+#include <boost/shared_ptr.hpp>
+#include <array>
+#include <functional>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Generic error reported within @ref HttpConnection class.
+class HttpConnectionError : public Exception {
+public:
+ HttpConnectionError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Forward declaration to the @ref HttpConnectionPool.
+///
+/// This declaration is needed because we don't include the header file
+/// declaring @ref HttpConnectionPool to avoid circular inclusion.
+class HttpConnectionPool;
+
+class HttpConnection;
+/// @brief Pointer to the @ref HttpConnection.
+typedef boost::shared_ptr<HttpConnection> HttpConnectionPtr;
+
+/// @brief Accepts and handles a single HTTP connection.
+class HttpConnection : public boost::enable_shared_from_this<HttpConnection> {
+private:
+
+ /// @brief Type of the function implementing a callback invoked by the
+ /// @c SocketCallback functor.
+ typedef std::function<void(boost::system::error_code ec, size_t length)>
+ SocketCallbackFunction;
+
+ /// @brief Functor associated with the socket object.
+ ///
+ /// This functor calls a callback function specified in the constructor.
+ class SocketCallback {
+ public:
+
+ /// @brief Constructor.
+ ///
+ /// @param socket_callback Callback to be invoked by the functor upon
+ /// an event associated with the socket.
+ SocketCallback(SocketCallbackFunction socket_callback)
+ : callback_(socket_callback) {
+ }
+
+ /// @brief Operator called when event associated with a socket occurs.
+ ///
+ /// This operator returns immediately when received error code is
+ /// @c boost::system::error_code is equal to
+ /// @c boost::asio::error::operation_aborted, i.e. the callback is not
+ /// invoked.
+ ///
+ /// @param ec Error code.
+ /// @param length Data length.
+ void operator()(boost::system::error_code ec, size_t length = 0);
+
+ private:
+ /// @brief Supplied callback.
+ SocketCallbackFunction callback_;
+ };
+
+protected:
+
+ class Transaction;
+
+ /// @brief Shared pointer to the @c Transaction.
+ typedef boost::shared_ptr<Transaction> TransactionPtr;
+
+ /// @brief Represents a single exchange of the HTTP messages.
+ ///
+ /// In HTTP/1.1 multiple HTTP message exchanges may be conducted
+ /// over the same persistent connection before the connection is
+ /// closed. Since ASIO handlers for these exchanges may be sometimes
+ /// executed out of order, there is a need to associate the states of
+ /// the exchanges with the appropriate ASIO handlers. This object
+ /// represents such state and includes: received request, request
+ /// parser (being in the particular state of parsing), input buffer
+ /// and the output buffer.
+ ///
+ /// The new @c Transaction instance is created when the connection
+ /// is established and the server starts receiving the HTTP request.
+ /// The shared pointer to the created transaction is passed between
+ /// the asynchronous handlers. Therefore, as long as the asynchronous
+ /// communication is conducted the instance of the transaction is
+ /// held by the IO service which runs the handlers. The transaction
+ /// instance exists as long as the asynchronous handlers for the
+ /// given request/response exchange are executed. When the server
+ /// responds to the client and all corresponding IO handlers are
+ /// invoked the transaction is automatically destroyed.
+ ///
+ /// The timeout may occur anytime during the transaction. In such
+ /// cases, a new transaction instance is created to send the
+ /// HTTP 408 (timeout) response to the client. Creation of the
+ /// new transaction for the timeout response is necessary because
+ /// there may be some asynchronous handlers scheduled by the
+ /// original transaction which rely on the original transaction's
+ /// state. The timeout response's state is held within the new
+ /// transaction spawned from the original transaction.
+ class Transaction {
+ public:
+
+ /// @brief Constructor.
+ ///
+ /// @param response_creator Pointer to the response creator being
+ /// used for generating a response from the request.
+ /// @param request Pointer to the HTTP request. If the request is
+ /// null, the constructor creates new request instance using the
+ /// provided response creator.
+ Transaction(const HttpResponseCreatorPtr& response_creator,
+ const HttpRequestPtr& request = HttpRequestPtr());
+
+ /// @brief Creates new transaction instance.
+ ///
+ /// It is called when the HTTP server has just scheduled asynchronous
+ /// reading of the HTTP message.
+ ///
+ /// @param response_creator Pointer to the response creator to be passed
+ /// to the transaction's constructor.
+ ///
+ /// @return Pointer to the created transaction instance.
+ static TransactionPtr create(const HttpResponseCreatorPtr& response_creator);
+
+ /// @brief Creates new transaction from the current transaction.
+ ///
+ /// This method creates new transaction and inherits the request
+ /// from the existing transaction. This is used when the timeout
+ /// occurs during the messages exchange. The server creates the new
+ /// transaction to handle the timeout but this new transaction must
+ /// include the request instance so as HTTP version information can
+ /// be inferred from it while sending the timeout response. The
+ /// HTTP version information should match between the request and
+ /// the response.
+ ///
+ /// @param response_creator Pointer to the response creator.
+ /// @param transaction Existing transaction from which the request
+ /// should be inherited. If the transaction is null, the new (dummy)
+ /// request is created for the new transaction.
+ static TransactionPtr spawn(const HttpResponseCreatorPtr& response_creator,
+ const TransactionPtr& transaction);
+
+ /// @brief Returns request instance associated with the transaction.
+ HttpRequestPtr getRequest() const {
+ return (request_);
+ }
+
+ /// @brief Returns parser instance associated with the transaction.
+ HttpRequestParserPtr getParser() const {
+ return (parser_);
+ }
+
+ /// @brief Returns pointer to the first byte of the input buffer.
+ char* getInputBufData() {
+ return (input_buf_.data());
+ }
+
+ /// @brief Returns input buffer size.
+ size_t getInputBufSize() const {
+ return (input_buf_.size());
+ }
+
+ /// @brief Checks if the output buffer contains some data to be
+ /// sent.
+ ///
+ /// @return true if the output buffer contains data to be sent,
+ /// false otherwise.
+ bool outputDataAvail() const {
+ return (!output_buf_.empty());
+ }
+
+ /// @brief Returns pointer to the first byte of the output buffer.
+ const char* getOutputBufData() const {
+ return (output_buf_.data());
+ }
+
+ /// @brief Returns size of the output buffer.
+ size_t getOutputBufSize() const {
+ return (output_buf_.size());
+ }
+
+ /// @brief Replaces output buffer contents with new contents.
+ ///
+ /// @param response New contents for the output buffer.
+ void setOutputBuf(const std::string& response) {
+ output_buf_ = response;
+ }
+
+ /// @brief Erases n bytes from the beginning of the output buffer.
+ ///
+ /// @param length Number of bytes to be erased.
+ void consumeOutputBuf(const size_t length) {
+ output_buf_.erase(0, length);
+ }
+
+ private:
+
+ /// @brief Pointer to the request received over this connection.
+ HttpRequestPtr request_;
+
+ /// @brief Pointer to the HTTP request parser.
+ HttpRequestParserPtr parser_;
+
+ /// @brief Buffer for received data.
+ std::array<char, 32768> input_buf_;
+
+ /// @brief Buffer used for outbound data.
+ std::string output_buf_;
+ };
+
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the connection.
+ /// @param acceptor Pointer to the TCP acceptor object used to listen for
+ /// new HTTP connections.
+ /// @param tls_context TLS context.
+ /// @param connection_pool Connection pool in which this connection is
+ /// stored.
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ /// @param request_timeout Configured timeout for a HTTP request.
+ /// @param idle_timeout Timeout after which persistent HTTP connection is
+ /// closed by the server.
+ HttpConnection(asiolink::IOService& io_service,
+ const HttpAcceptorPtr& acceptor,
+ const asiolink::TlsContextPtr& tls_context,
+ HttpConnectionPool& connection_pool,
+ const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback,
+ const long request_timeout,
+ const long idle_timeout);
+
+ /// @brief Destructor.
+ ///
+ /// Closes current connection.
+ virtual ~HttpConnection();
+
+ /// @brief Asynchronously accepts new connection.
+ ///
+ /// When the connection is established successfully, the timeout timer is
+ /// setup and the asynchronous handshake with client is performed.
+ void asyncAccept();
+
+ /// @brief Shutdown the socket.
+ void shutdown();
+
+ /// @brief Closes the socket.
+ void close();
+
+ /// @brief Records connection parameters into the HTTP request.
+ ///
+ /// @param request Pointer to the HTTP request.
+ void recordParameters(const HttpRequestPtr& request) const;
+
+ /// @brief Asynchronously performs TLS handshake.
+ ///
+ /// When the handshake is performed successfully or skipped because TLS
+ /// was not enabled, the asynchronous read from the socket is started.
+ void doHandshake();
+
+ /// @brief Starts asynchronous read from the socket.
+ ///
+ /// The data received over the socket are supplied to the HTTP parser until
+ /// the parser signals that the entire request has been received or until
+ /// the parser signals an error. In the former case the server creates an
+ /// HTTP response using supplied response creator object.
+ ///
+ /// In case of error the connection is stopped.
+ ///
+ /// @param transaction Pointer to the transaction for which the read
+ /// operation should be performed. It defaults to null pointer which
+ /// indicates that this function should create new transaction.
+ void doRead(TransactionPtr transaction = TransactionPtr());
+
+protected:
+
+ /// @brief Starts asynchronous write to the socket.
+ ///
+ /// The @c output_buf_ must contain the data to be sent.
+ ///
+ /// In case of error the connection is stopped.
+ ///
+ /// @param transaction Pointer to the transaction for which the write
+ /// operation should be performed.
+ void doWrite(TransactionPtr transaction);
+
+ /// @brief Sends HTTP response asynchronously.
+ ///
+ /// Internally it calls @ref HttpConnection::doWrite to send the data.
+ ///
+ /// @param response Pointer to the HTTP response to be sent.
+ /// @param transaction Pointer to the transaction.
+ void asyncSendResponse(const ConstHttpResponsePtr& response,
+ TransactionPtr transaction);
+
+ /// @brief Local callback invoked when new connection is accepted.
+ ///
+ /// It invokes external (supplied via constructor) acceptor callback. If
+ /// the acceptor is not opened it returns immediately. If the connection
+ /// is accepted successfully the @ref HttpConnection::doRead or
+ /// @ref HttpConnection::doHandshake is called.
+ ///
+ /// @param ec Error code.
+ void acceptorCallback(const boost::system::error_code& ec);
+
+ /// @brief Local callback invoked when TLS handshake is performed.
+ ///
+ /// If the handshake is performed successfully the @ref
+ /// HttpConnection::doRead is called.
+ ///
+ /// @param ec Error code.
+ void handshakeCallback(const boost::system::error_code& ec);
+
+ /// @brief Callback invoked when new data is received over the socket.
+ ///
+ /// This callback supplies the data to the HTTP parser and continues
+ /// parsing. When the parser signals end of the HTTP request the callback
+ /// prepares a response and starts asynchronous send over the socket.
+ ///
+ /// @param transaction Pointer to the transaction for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the received data.
+ void socketReadCallback(TransactionPtr transaction,
+ boost::system::error_code ec,
+ size_t length);
+
+ /// @brief Callback invoked when data is sent over the socket.
+ ///
+ /// @param transaction Pointer to the transaction for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the data sent.
+ virtual void socketWriteCallback(TransactionPtr transaction,
+ boost::system::error_code ec,
+ size_t length);
+
+ /// @brief Callback invoked when TLS shutdown is performed.
+ ///
+ /// The TLS socket is unconditionally closed but the callback is called
+ /// only when the peer has answered so the connection should be
+ /// explicitly closed in all cases, i.e. do not rely on this handler.
+ ///
+ /// @param ec Error code (ignored).
+ void shutdownCallback(const boost::system::error_code& ec);
+
+ /// @brief Reset timer for detecting request timeouts.
+ ///
+ /// @param transaction Pointer to the transaction to be guarded by the timeout.
+ void setupRequestTimer(TransactionPtr transaction = TransactionPtr());
+
+ /// @brief Reset timer for detecting idle timeout in persistent connections.
+ void setupIdleTimer();
+
+ /// @brief Callback invoked when the HTTP Request Timeout occurs.
+ ///
+ /// This callback creates HTTP response with Request Timeout error code
+ /// and sends it to the client.
+ ///
+ /// @param transaction Pointer to the transaction for which timeout occurs.
+ void requestTimeoutCallback(TransactionPtr transaction);
+
+ void idleTimeoutCallback();
+
+ /// @brief Shuts down current connection.
+ ///
+ /// Copied from the next method @ref stopThisConnection
+ void shutdownConnection();
+
+ /// @brief Stops current connection.
+ void stopThisConnection();
+
+ /// @brief returns remote address in textual form
+ std::string getRemoteEndpointAddressAsText() const;
+
+ /// @brief Timer used to detect Request Timeout.
+ asiolink::IntervalTimer request_timer_;
+
+ /// @brief Configured Request Timeout in milliseconds.
+ long request_timeout_;
+
+ /// @brief TLS context.
+ asiolink::TlsContextPtr tls_context_;
+
+ /// @brief Timeout after which the persistent HTTP connection is shut
+ /// down by the server.
+ long idle_timeout_;
+
+ /// @brief TCP socket used by this connection.
+ std::unique_ptr<asiolink::TCPSocket<SocketCallback> > tcp_socket_;
+
+ /// @brief TLS socket used by this connection.
+ std::unique_ptr<asiolink::TLSSocket<SocketCallback> > tls_socket_;
+
+ /// @brief Pointer to the TCP acceptor used to accept new connections.
+ HttpAcceptorPtr acceptor_;
+
+ /// @brief Connection pool holding this connection.
+ HttpConnectionPool& connection_pool_;
+
+ /// @brief Pointer to the @ref HttpResponseCreator object used to create
+ /// HTTP responses.
+ HttpResponseCreatorPtr response_creator_;
+
+ /// @brief External TCP acceptor callback.
+ HttpAcceptorCallback acceptor_callback_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/connection_pool.cc b/src/lib/http/connection_pool.cc
new file mode 100644
index 0000000..de92eb2
--- /dev/null
+++ b/src/lib/http/connection_pool.cc
@@ -0,0 +1,74 @@
+// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <http/connection_pool.h>
+#include <util/multi_threading_mgr.h>
+
+namespace isc {
+namespace http {
+
+void
+HttpConnectionPool::start(const HttpConnectionPtr& connection) {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ connections_.insert(connections_.end(), connection);
+ } else {
+ connections_.insert(connections_.end(), connection);
+ }
+
+ connection->asyncAccept();
+}
+
+void
+HttpConnectionPool::stop(const HttpConnectionPtr& connection) {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ connections_.remove(connection);
+ } else {
+ connections_.remove(connection);
+ }
+
+ connection->close();
+}
+
+void
+HttpConnectionPool::shutdown(const HttpConnectionPtr& connection) {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ connections_.remove(connection);
+ } else {
+ connections_.remove(connection);
+ }
+
+ connection->shutdown();
+}
+
+void
+HttpConnectionPool::stopAll() {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ stopAllInternal();
+ } else {
+ stopAllInternal();
+ }
+}
+
+void
+HttpConnectionPool::stopAllInternal() {
+ for (auto connection = connections_.begin();
+ connection != connections_.end();
+ ++connection) {
+ (*connection)->close();
+ }
+
+ connections_.clear();
+}
+
+}
+}
diff --git a/src/lib/http/connection_pool.h b/src/lib/http/connection_pool.h
new file mode 100644
index 0000000..a9110bc
--- /dev/null
+++ b/src/lib/http/connection_pool.h
@@ -0,0 +1,78 @@
+// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_CONNECTION_POOL_H
+#define HTTP_CONNECTION_POOL_H
+
+#include <http/connection.h>
+
+#include <list>
+#include <mutex>
+
+namespace isc {
+namespace http {
+
+/// @brief Pool of active HTTP connections.
+///
+/// The HTTP server is designed to handle many connections simultaneously.
+/// The communication between the client and the server may take long time
+/// and the server must be able to react on other events while the communication
+/// with the clients is in progress. Thus, the server must track active
+/// connections and gracefully close them when needed. An obvious case when the
+/// connections must be terminated by the server is when the shutdown signal
+/// is received.
+///
+/// This object is a simple container for the server connections which provides
+/// means to terminate them on request.
+class HttpConnectionPool {
+public:
+
+ /// @brief Start new connection.
+ ///
+ /// The connection is inserted to the pool and the
+ /// @ref HttpConnection::asyncAccept is invoked.
+ ///
+ /// @param connection Pointer to the new connection.
+ void start(const HttpConnectionPtr& connection);
+
+ /// @brief Removes a connection from the pool and shutdown it.
+ ///
+ /// Shutdown is specific to TLS and is a first part of graceful close (note it is
+ /// NOT the same as TCP shutdown system call).
+ ///
+ /// @note if the TLS connection stalls e.g. the peer does not try I/O
+ /// on it the connection has to be explicitly stopped.
+ ///
+ /// @param connection Pointer to the connection.
+ void shutdown(const HttpConnectionPtr& connection);
+
+ /// @brief Removes a connection from the pool and stops it.
+ ///
+ /// @param connection Pointer to the connection.
+ void stop(const HttpConnectionPtr& connection);
+
+ /// @brief Stops all connections and removes them from the pool.
+ void stopAll();
+
+protected:
+
+ /// @brief Stops all connections and removes them from the pool.
+ ///
+ /// Must be called from with a thread-safe context.
+ void stopAllInternal();
+
+ /// @brief Set of connections.
+ std::list<HttpConnectionPtr> connections_;
+
+ /// @brief Mutex to protect the internal state.
+ std::mutex mutex_;
+};
+
+}
+}
+
+#endif
+
diff --git a/src/lib/http/date_time.cc b/src/lib/http/date_time.cc
new file mode 100644
index 0000000..cd7824a
--- /dev/null
+++ b/src/lib/http/date_time.cc
@@ -0,0 +1,158 @@
+// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/date_time.h>
+#include <boost/date_time/time_facet.hpp>
+#include <boost/date_time/local_time/local_time.hpp>
+#include <locale>
+#include <sstream>
+
+using namespace boost::local_time;
+using namespace boost::posix_time;
+
+namespace isc {
+namespace http {
+
+HttpDateTime::HttpDateTime()
+ : time_(boost::posix_time::second_clock::universal_time()) {
+}
+
+HttpDateTime::HttpDateTime(const boost::posix_time::ptime& t)
+ : time_(t) {
+}
+
+std::string
+HttpDateTime::rfc1123Format() const {
+ return (toString("%a, %d %b %Y %H:%M:%S GMT", "RFC 1123"));
+}
+
+std::string
+HttpDateTime::rfc850Format() const {
+ return (toString("%A, %d-%b-%y %H:%M:%S GMT", "RFC 850"));
+}
+
+std::string
+HttpDateTime::asctimeFormat() const {
+ return (toString("%a %b %e %H:%M:%S %Y", "asctime"));
+}
+
+HttpDateTime
+HttpDateTime::fromRfc1123(const std::string& time_string) {
+ return (HttpDateTime(fromString(time_string,
+ "%a, %d %b %Y %H:%M:%S %ZP",
+ "RFC 1123")));
+}
+
+HttpDateTime
+HttpDateTime::fromRfc850(const std::string& time_string) {
+ return (HttpDateTime(fromString(time_string,
+ "%A, %d-%b-%y %H:%M:%S %ZP",
+ "RFC 850")));
+}
+
+HttpDateTime
+HttpDateTime::fromAsctime(const std::string& time_string) {
+ // The asctime() puts space instead of leading 0 in days of
+ // month. The %e # formatter of time_input_facet doesn't deal
+ // with this. To deal with this, we make a copy of the string
+ // holding formatted time and replace a space preceding day
+ // number with 0. Thanks to this workaround we can use the
+ // %d formatter which seems to work fine. This has a side
+ // effect of accepting timestamps such as Sun Nov 06 08:49:37 1994,
+ // but it should be ok to be liberal in this case.
+ std::string time_string_copy(time_string);
+ boost::replace_all(time_string_copy, " ", " 0");
+ return (HttpDateTime(fromString(time_string_copy,
+ "%a %b %d %H:%M:%S %Y",
+ "asctime",
+ false)));
+}
+
+HttpDateTime
+HttpDateTime::fromAny(const std::string& time_string) {
+ HttpDateTime date_time;
+ // Try to parse as a timestamp specified in RFC 1123 format.
+ try {
+ date_time = fromRfc1123(time_string);
+ return (date_time);
+ } catch (...) {
+ // Ignore errors, simply try different format.
+ }
+
+ // Try to parse as a timestamp specified in RFC 850 format.
+ try {
+ date_time = fromRfc850(time_string);
+ return (date_time);
+ } catch (...) {
+ // Ignore errors, simply try different format.
+ }
+
+ // Try to parse as a timestamp output by asctime() function.
+ try {
+ date_time = fromAsctime(time_string);
+ } catch (...) {
+ isc_throw(HttpTimeConversionError,
+ "unsupported time format of the '" << time_string
+ << "'");
+ }
+
+ return (date_time);
+
+}
+
+std::string
+HttpDateTime::toString(const std::string& format,
+ const std::string& method_name) const {
+ std::ostringstream s;
+ // Create raw pointer. The output stream will take responsibility for
+ // deleting the object.
+ time_facet* df(new time_facet(format.c_str()));
+ s.imbue(std::locale(std::locale::classic(), df));
+
+ // Convert time value to a string.
+ s << time_;
+ if (s.fail()) {
+ isc_throw(HttpTimeConversionError, "unable to convert "
+ << "time value of '" << time_ << "'"
+ << " to " << method_name << " format");
+ }
+ return (s.str());
+}
+
+
+ptime
+HttpDateTime::fromString(const std::string& time_string,
+ const std::string& format,
+ const std::string& method_name,
+ const bool zone_check) {
+ std::istringstream s(time_string);
+ // Create raw pointer. The input stream will take responsibility for
+ // deleting the object.
+ time_input_facet* tif(new time_input_facet(format));
+ s.imbue(std::locale(std::locale::classic(), tif));
+
+ time_zone_ptr zone(new posix_time_zone("GMT"));
+ local_date_time ldt = local_microsec_clock::local_time(zone);
+
+ // Parse the time value. The stream will not automatically detect whether
+ // the zone is GMT. We need to check it on our own.
+ s >> ldt;
+ if (s.fail() ||
+ (zone_check && (!ldt.zone() ||
+ ldt.zone()->std_zone_abbrev() != "GMT"))) {
+ isc_throw(HttpTimeConversionError, "unable to parse "
+ << method_name << " time value of '"
+ << time_string << "'");
+ }
+
+ return (ldt.local_time());
+}
+
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/date_time.h b/src/lib/http/date_time.h
new file mode 100644
index 0000000..ba96ba3
--- /dev/null
+++ b/src/lib/http/date_time.h
@@ -0,0 +1,161 @@
+// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_DATE_TIME_H
+#define HTTP_DATE_TIME_H
+
+#include <exceptions/exceptions.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Exception thrown when there is an error during time conversion.
+///
+/// The most common reason for this exception is that the unsupported time
+/// format was used as an input to the time parsing functions.
+class HttpTimeConversionError : public Exception {
+public:
+ HttpTimeConversionError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief This class parses and generates time values used in HTTP.
+///
+/// The HTTP protocol have historically allowed 3 different date/time formats
+/// (see https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html). These are:
+/// - Sun, 06 Nov 1994 08:49:37 GMT
+/// - Sunday, 06-Nov-94 08:49:37 GMT
+/// - Sun Nov 6 08:49:37 1994
+///
+/// The first format is preferred but implementations must also support
+/// remaining two obsolete formats for compatibility. This class implements
+/// parsers and generators for all three formats. It uses @c boost::posix_time
+/// to represent time and date. It uses @c boost::date_time::time_facet
+/// and @c boost::date_time::time_input_facet to generate and parse the
+/// timestamps.
+class HttpDateTime {
+public:
+
+ /// @brief Default constructor.
+ ///
+ /// Sets current universal time as time value.
+ /// Time resolution is to seconds (i.e no fractional seconds).
+ HttpDateTime();
+
+ /// @brief Construct from @c boost::posix_time::ptime object.
+ ///
+ /// @param t time value to be set.
+ explicit HttpDateTime(const boost::posix_time::ptime& t);
+
+ /// @brief Returns time encapsulated by this class.
+ ///
+ /// @return @c boost::posix_time::ptime value encapsulated by the instance
+ /// of this class.
+ boost::posix_time::ptime getPtime() const {
+ return (time_);
+ }
+
+ /// @brief Returns time value formatted as specified in RFC 1123.
+ ///
+ /// @return A string containing time value formatted as
+ /// Sun, 06 Nov 1994 08:49:37 GMT.
+ std::string rfc1123Format() const;
+
+ /// @brief Returns time value formatted as specified in RFC 850.
+ ///
+ /// @return A string containing time value formatted as
+ /// Sunday, 06-Nov-94 08:49:37 GMT.
+ std::string rfc850Format() const;
+
+ /// @brief Returns time value formatted as output of ANSI C's
+ /// asctime().
+ ///
+ /// @return A string containing time value formatted as
+ /// Sun Nov 6 08:49:37 1994.
+ std::string asctimeFormat() const;
+
+ /// @brief Creates an instance from a string containing time value
+ /// formatted as specified in RFC 1123.
+ ///
+ /// @param time_string Input string holding formatted time value.
+ /// @return Instance of @ref HttpDateTime.
+ /// @throw HttpTimeConversionError if provided timestamp has invalid
+ /// format.
+ static HttpDateTime fromRfc1123(const std::string& time_string);
+
+ /// @brief Creates an instance from a string containing time value
+ /// formatted as specified in RFC 850.
+ ///
+ /// @param time_string Input string holding formatted time value.
+ /// @return Instance of @ref HttpDateTime.
+ /// @throw HttpTimeConversionError if provided timestamp has invalid
+ /// format.
+ static HttpDateTime fromRfc850(const std::string& time_string);
+
+ /// @brief Creates an instance from a string containing time value
+ /// formatted as output from asctime() function.
+ ///
+ /// @param time_string Input string holding formatted time value.
+ /// @return Instance of @ref HttpDateTime.
+ /// @throw HttpTimeConversionError if provided timestamp has invalid
+ /// format.
+ static HttpDateTime fromAsctime(const std::string& time_string);
+
+ /// @brief Creates an instance from a string containing time value
+ /// formatted in one of the supported formats.
+ ///
+ /// This method will detect the format of the time value and parse it.
+ /// It tries parsing the value in the following order:
+ /// - a format specified in RFC 1123,
+ /// - a format specified in RFC 850,
+ /// - a format of asctime output.
+ ///
+ /// @param time_string Input string holding formatted time value.
+ /// @return Instance of @ref HttpDateTime.
+ /// @throw HttpTimeConversionError if provided value doesn't match any
+ /// of the supported formats.
+ static HttpDateTime fromAny(const std::string& time_string);
+
+private:
+
+ /// @brief Generic method formatting a time value to a specified format.
+ ////
+ /// @param format Time format as accepted by the
+ /// @c boost::date_time::time_facet.
+ std::string toString(const std::string& format,
+ const std::string& method_name) const;
+
+ /// @brief Generic method parsing time value and converting it to the
+ /// instance of @c boost::posix_time::ptime.
+ ///
+ /// @param time_string Input string holding formatted time value.
+ /// @param format Time format as accepted by the
+ /// @c boost::date_time::time_input_facet.
+ /// @param method_name Name of the expected format to appear in the error
+ /// message if parsing fails, e.g. RFC 1123, RFC 850 or asctime.
+ /// @param zone_check Indicates if the time zone name should be validated
+ /// during parsing. This should be set to false for the formats which
+ /// lack time zones (e.g. asctime).
+ ///
+ /// @return Instance of the @ref boost::posix_time::ptime created from the
+ /// input string.
+ /// @throw HttpTimeConversionError if provided value doesn't match the
+ /// specified format.
+ static boost::posix_time::ptime
+ fromString(const std::string& time_string, const std::string& format,
+ const std::string& method_name, const bool zone_check = true);
+
+ /// @brief Time value encapsulated by this class instance.
+ boost::posix_time::ptime time_;
+
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/header_context.h b/src/lib/http/header_context.h
new file mode 100644
index 0000000..623e263
--- /dev/null
+++ b/src/lib/http/header_context.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_HEADER_CONTEXT_H
+#define HTTP_HEADER_CONTEXT_H
+
+#include <boost/lexical_cast.hpp>
+#include <cstdint>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief HTTP header context.
+struct HttpHeaderContext {
+ std::string name_;
+ std::string value_;
+
+ /// @brief Constructor.
+ ///
+ /// Sets header name and value to empty strings.
+ HttpHeaderContext()
+ : name_(), value_() {
+ }
+
+ /// @brief Constructor.
+ ///
+ /// @param name Header name.
+ /// @param value Header value.
+ HttpHeaderContext(const std::string& name, const std::string& value)
+ : name_(name), value_(value) {
+ }
+
+ /// @brief Constructor.
+ ///
+ /// @param name Header name.
+ /// @param value Numeric value for the header.
+ HttpHeaderContext(const std::string& name, const int64_t value)
+ : name_(name), value_(boost::lexical_cast<std::string>(value)) {
+ }
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/http.dox b/src/lib/http/http.dox
new file mode 100644
index 0000000..c6e4933
--- /dev/null
+++ b/src/lib/http/http.dox
@@ -0,0 +1,24 @@
+// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/**
+ @page libhttp libkea-http - Kea HTTP Library
+
+@section httpMTConsiderations Multi-Threading Consideration for HTTP Library
+
+By default this library is not thread safe, for instance HTTP listeners
+and HTTP messages are not thread safe. Exceptions are:
+
+ - HTTP client is Kea thread safe (i.e. is thread safe when the
+ multi-threading mode is true).
+
+ - date time is thread safe (mainly because its encapsulated POSIX time
+ is private and read-only, or because all methods are instance const methods
+ or class methods).
+
+ - URL is thread safe (all public methods are const methods).
+
+*/
diff --git a/src/lib/http/http_acceptor.h b/src/lib/http/http_acceptor.h
new file mode 100644
index 0000000..eb6f55d
--- /dev/null
+++ b/src/lib/http/http_acceptor.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_ACCEPTOR_H
+#define HTTP_ACCEPTOR_H
+
+#include <asiolink/tls_acceptor.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/system/system_error.hpp>
+#include <functional>
+
+namespace isc {
+namespace http {
+
+/// @brief Type of the callback for the TCP acceptor used in this library.
+typedef std::function<void(const boost::system::error_code&)>
+HttpAcceptorCallback;
+
+/// @brief Type of the TCP acceptor used in this library.
+typedef asiolink::TCPAcceptor<HttpAcceptorCallback> HttpAcceptor;
+
+/// @brief Type of the TLS acceptor used in this library.
+///
+/// @note It is a derived type of HttpAcceptor.
+typedef asiolink::TLSAcceptor<HttpAcceptorCallback> HttpsAcceptor;
+
+/// @brief Type of shared pointer to TCP acceptors.
+typedef boost::shared_ptr<HttpAcceptor> HttpAcceptorPtr;
+
+/// @brief Type of shared pointer to TLS acceptors.
+typedef boost::shared_ptr<HttpsAcceptor> HttpsAcceptorPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/http_header.cc b/src/lib/http/http_header.cc
new file mode 100644
index 0000000..a543e66
--- /dev/null
+++ b/src/lib/http/http_header.cc
@@ -0,0 +1,56 @@
+// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <http/http_header.h>
+#include <util/strutil.h>
+#include <boost/lexical_cast.hpp>
+
+namespace isc {
+namespace http {
+
+HttpHeader::HttpHeader(const std::string& header_name,
+ const std::string& header_value)
+ : header_name_(header_name), header_value_(header_value) {
+}
+
+uint64_t
+HttpHeader::getUint64Value() const {
+ try {
+ return (boost::lexical_cast<uint64_t>(header_value_));
+
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(BadValue, header_name_ << " HTTP header value "
+ << header_value_ << " is not a valid number");
+ }
+}
+
+std::string
+HttpHeader::getLowerCaseName() const {
+ std::string ln = header_name_;
+ util::str::lowercase(ln);
+ return (ln);
+}
+
+std::string
+HttpHeader::getLowerCaseValue() const {
+ std::string lc = header_value_;
+ util::str::lowercase(lc);
+ return (lc);
+}
+
+bool
+HttpHeader::isValueEqual(const std::string& v) const {
+ std::string lcv = v;
+ util::str::lowercase(lcv);
+ return (lcv == getLowerCaseValue());
+}
+
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/http_header.h b/src/lib/http/http_header.h
new file mode 100644
index 0000000..35d4c17
--- /dev/null
+++ b/src/lib/http/http_header.h
@@ -0,0 +1,86 @@
+// Copyright (C) 2017-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_HEADER_H
+#define HTTP_HEADER_H
+
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Represents HTTP header including a header name and value.
+///
+/// It includes methods for retrieving header name and value in lower case
+/// and for case insensitive comparison of header values.
+class HttpHeader {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param header_name Header name.
+ /// @param header_value Header value.
+ explicit HttpHeader(const std::string& header_name,
+ const std::string& header_value = "");
+
+ /// @brief Returns header name.
+ std::string getName() const {
+ return (header_name_);
+ }
+
+ /// @brief Returns header value.
+ std::string getValue() const {
+ return (header_value_);
+ }
+
+ /// @brief Returns header value as unsigned integer.
+ ///
+ /// @throw BadValue if the header value is not a valid number.
+ uint64_t getUint64Value() const;
+
+ /// @brief Returns lower case header name.
+ std::string getLowerCaseName() const;
+
+ /// @brief Returns lower case header value.
+ std::string getLowerCaseValue() const;
+
+ /// @brief Case insensitive comparison of header value.
+ ///
+ /// @param v Value to be compared.
+ ///
+ /// @return true if header value is equal, false otherwise.
+ bool isValueEqual(const std::string& v) const;
+
+private:
+
+ std::string header_name_; ///< Header name.
+ std::string header_value_; ///< Header value.
+};
+
+/// @brief Pointer to the @c HttpHeader class.
+typedef boost::shared_ptr<HttpHeader> HttpHeaderPtr;
+
+/// @brief Represents HTTP Host header.
+class HostHttpHeader : public HttpHeader {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param header_value Host header value. The default is empty
+ /// string.
+ explicit HostHttpHeader(const std::string& header_value = "")
+ : HttpHeader("Host", header_value) {
+ }
+};
+
+/// @brief Pointer to the HTTP host header.
+typedef boost::shared_ptr<HostHttpHeader> HostHttpHeaderPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // HTTP_HEADER_H
diff --git a/src/lib/http/http_log.cc b/src/lib/http/http_log.cc
new file mode 100644
index 0000000..8e1994d
--- /dev/null
+++ b/src/lib/http/http_log.cc
@@ -0,0 +1,21 @@
+// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// Defines the logger used by the libkea-http library.
+
+#include <config.h>
+
+#include <http/http_log.h>
+
+namespace isc {
+namespace http {
+
+/// @brief Defines the logger used within libkea-http library.
+isc::log::Logger http_logger("http");
+
+} // namespace http
+} // namespace isc
+
diff --git a/src/lib/http/http_log.h b/src/lib/http/http_log.h
new file mode 100644
index 0000000..0b7d8ad
--- /dev/null
+++ b/src/lib/http/http_log.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_LOG_H
+#define HTTP_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <http/http_messages.h>
+
+namespace isc {
+namespace http {
+
+/// Define the logger used within libkea-http library.
+extern isc::log::Logger http_logger;
+
+} // namespace http
+} // namespace isc
+
+#endif // HTTP_LOG_H
diff --git a/src/lib/http/http_message.cc b/src/lib/http/http_message.cc
new file mode 100644
index 0000000..ce012ca
--- /dev/null
+++ b/src/lib/http/http_message.cc
@@ -0,0 +1,108 @@
+// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/http_message.h>
+
+namespace isc {
+namespace http {
+
+HttpMessage::HttpMessage(const HttpMessage::Direction& direction)
+ : direction_(direction), required_versions_(),
+ http_version_(HttpVersion::HTTP_10()), required_headers_(),
+ created_(false), finalized_(false), headers_() {
+}
+
+HttpMessage::~HttpMessage() {
+}
+
+void
+HttpMessage::requireHttpVersion(const HttpVersion& version) {
+ required_versions_.insert(version);
+}
+
+void
+HttpMessage::requireHeader(const std::string& header_name) {
+ // Empty value denotes that the header is required but no specific
+ // value is expected.
+ HttpHeaderPtr hdr(new HttpHeader(header_name));
+ required_headers_[hdr->getLowerCaseName()] = hdr;
+}
+
+void
+HttpMessage::requireHeaderValue(const std::string& header_name,
+ const std::string& header_value) {
+ HttpHeaderPtr hdr(new HttpHeader(header_name, header_value));
+ required_headers_[hdr->getLowerCaseName()] = hdr;
+}
+
+bool
+HttpMessage::requiresBody() const {
+ // If Content-Length is required the body must exist too. There may
+ // be probably some cases when Content-Length is not provided but
+ // the body is provided. But, probably not in our use cases.
+ // Use lower case header name because this is how it is indexed in
+ // the storage.
+ return (required_headers_.find("content-length") != required_headers_.end());
+}
+
+HttpVersion
+HttpMessage::getHttpVersion() const {
+ checkCreated();
+ return (http_version_);
+}
+
+HttpHeaderPtr
+HttpMessage::getHeader(const std::string& header_name) const {
+ checkCreated();
+
+ HttpHeader hdr(header_name);
+ auto header_it = headers_.find(hdr.getLowerCaseName());
+ if (header_it != headers_.end()) {
+ return (header_it->second);
+ }
+
+ isc_throw(HttpMessageNonExistingHeader, header_name << " HTTP header"
+ " not found in the request");
+}
+
+std::string
+HttpMessage::getHeaderValue(const std::string& header_name) const {
+ return (getHeader(header_name)->getValue());
+}
+
+uint64_t
+HttpMessage::getHeaderValueAsUint64(const std::string& header_name) const {
+ try {
+ return (getHeader(header_name)->getUint64Value());
+
+ } catch (const std::exception& ex) {
+ // The specified header does exist, but the value is not a number.
+ isc_throw(HttpMessageError, ex.what());
+ }
+}
+
+void
+HttpMessage::checkCreated() const {
+ if (!created_) {
+ isc_throw(HttpMessageError, "unable to retrieve values of HTTP"
+ " message because the HttpMessage::create() must be"
+ " called first. This is a programmatic error");
+ }
+}
+
+void
+HttpMessage::checkFinalized() const {
+ if (!finalized_) {
+ isc_throw(HttpMessageError, "unable to retrieve body of HTTP"
+ " message because the HttpMessage::finalize() must be"
+ " called first. This is a programmatic error");
+ }
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/http_message.h b/src/lib/http/http_message.h
new file mode 100644
index 0000000..3634c80
--- /dev/null
+++ b/src/lib/http/http_message.h
@@ -0,0 +1,265 @@
+// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_MESSAGE_H
+#define HTTP_MESSAGE_H
+
+#include <exceptions/exceptions.h>
+#include <http/http_header.h>
+#include <http/http_types.h>
+#include <map>
+#include <set>
+#include <cstdint>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Generic exception thrown by @ref HttpMessage class.
+class HttpMessageError : public Exception {
+public:
+ HttpMessageError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when attempt is made to retrieve a
+/// non-existing header.
+class HttpMessageNonExistingHeader : public HttpMessageError {
+public:
+ HttpMessageNonExistingHeader(const char* file, size_t line,
+ const char* what) :
+ HttpMessageError(file, line, what) { };
+};
+
+
+/// @brief Base class for @ref HttpRequest and @ref HttpResponse.
+///
+/// This abstract class provides a common functionality for the HTTP
+/// requests and responses. Each such message can be marked as outbound
+/// or inbound. An HTTP inbound request is the one received by the server
+/// and HTTP inbound response is the response received by the client.
+/// Conversely, an HTTP outbound request is the request created by the
+/// client and HTTP outbound response is the response created by the
+/// server. There are differences in how the inbound and outbound
+/// messages are created. The inbound messages are received over the
+/// TCP sockets and parsed by the parsers. The parsed information is
+/// stored in a context, i.e. structure holding raw information and
+/// associated with the given @c HttpMessage instance. Once the message
+/// is parsed and all required information is stored in the context,
+/// the @c create method is called to validate and fetch information
+/// from the context into the message. The @c finalize method is called
+/// to commit the HTTP message body into the message.
+///
+/// The outbound message is created locally from the known data, e.g.
+/// HTTP version number, URI, method etc. The headers can be then
+/// appended to the message via the context. In order to use this message
+/// the @c finalize method must be called to commit this information.
+/// Them, @c toString method can be called to generate the message in
+/// the textual form, which can be transferred via TCP socket.
+class HttpMessage {
+public:
+
+ /// @brief Specifies the direction of the HTTP message.
+ enum Direction {
+ INBOUND,
+ OUTBOUND
+ };
+
+ /// @brief Constructor.
+ ///
+ /// @param direction Direction of the message (inbound or outbound).
+ explicit HttpMessage(const Direction& direction);
+
+ /// @brief Destructor.
+ virtual ~HttpMessage();
+
+ /// @brief Returns HTTP message direction.
+ Direction getDirection() const {
+ return (direction_);
+ }
+
+ /// @brief Sets direction for the HTTP message.
+ ///
+ /// This is mostly useful in unit testing.
+ ///
+ /// @param direction New direction of the HTTP message.
+ void setDirection(const Direction& direction) {
+ direction_ = direction;
+ }
+
+ /// @brief Specifies HTTP version allowed.
+ ///
+ /// Allowed HTTP versions must be specified prior to calling @ref create
+ /// method. If no version is specified, all versions are allowed.
+ ///
+ /// @param version Version number allowed for the request.
+ void requireHttpVersion(const HttpVersion& version);
+
+ /// @brief Specifies a required HTTP header for the HTTP message.
+ ///
+ /// Required headers must be specified prior to calling @ref create method.
+ /// The specified header must exist in the received HTTP request. This puts
+ /// no requirement on the header value.
+ ///
+ /// @param header_name Required header name.
+ void requireHeader(const std::string& header_name);
+
+ /// @brief Specifies a required value of a header in the message.
+ ///
+ /// Required header values must be specified prior to calling @ref create
+ /// method. The specified header must exist and its value must be equal to
+ /// the value specified as second parameter.
+ ///
+ /// @param header_name HTTP header name.
+ /// @param header_value HTTP header value.
+ void requireHeaderValue(const std::string& header_name,
+ const std::string& header_value);
+
+ /// @brief Checks if the body is required for the HTTP message.
+ ///
+ /// Current implementation simply checks if the "Content-Length" header
+ /// is required.
+ ///
+ /// @return true if the body is required, false otherwise.
+ bool requiresBody() const;
+
+ /// @brief Reads parsed message from the context, validates the message and
+ /// stores parsed information.
+ ///
+ /// This method must be called before retrieving parsed data using accessors.
+ /// This method doesn't parse the HTTP request body.
+ virtual void create() = 0;
+
+ /// @brief Complete parsing HTTP message or creating an HTTP outbound message.
+ ///
+ /// This method is used in two situations: when a message has been received
+ /// into a context and may be fully parsed (including the body) or when the
+ /// data for the creation of the outbound message have been stored in a context
+ /// and the message can be now created from the context.
+ ///
+ /// This method should call @c create method if it hasn't been called yet and
+ /// then read the message body from the context and interpret it. If the body
+ /// doesn't adhere to the requirements for the message (in particular, when the
+ /// content type of the body is invalid) an exception should be thrown.
+ virtual void finalize() = 0;
+
+ /// @brief Reset the state of the object.
+ virtual void reset() = 0;
+
+ /// @brief Returns HTTP version number (major and minor).
+ HttpVersion getHttpVersion() const;
+
+ /// @brief Returns object encapsulating HTTP header.
+ ///
+ /// @param header_name HTTP header name.
+ ///
+ /// @return Non-null pointer to the header.
+ /// @throw HttpMessageNonExistingHeader if header with the specified name
+ /// doesn't exist.
+ /// @throw HttpMessageError if the request hasn't been created.
+ HttpHeaderPtr getHeader(const std::string& header_name) const;
+
+ /// @brief Returns a value of the specified HTTP header.
+ ///
+ /// @param header_name Name of the HTTP header.
+ ///
+ /// @throw HttpMessageError if the header doesn't exist.
+ std::string getHeaderValue(const std::string& header_name) const;
+
+ /// @brief Returns a value of the specified HTTP header as number.
+ ///
+ /// @param header_name Name of the HTTP header.
+ ///
+ /// @throw HttpMessageError if the header doesn't exist or if the
+ /// header value is not number.
+ uint64_t getHeaderValueAsUint64(const std::string& header_name) const;
+
+ /// @brief Returns HTTP message body as string.
+ virtual std::string getBody() const = 0;
+
+ /// @brief Returns HTTP message as text.
+ ///
+ /// This method is called to generate the outbound HTTP message. Make
+ /// sure to call @c finalize prior to calling this method.
+ virtual std::string toString() const = 0;
+
+ /// @brief Checks if the message has been successfully finalized.
+ ///
+ /// The message gets finalized on successful call to @c finalize.
+ ///
+ /// @return true if the message has been finalized, false otherwise.
+ bool isFinalized() const {
+ return (finalized_);
+ }
+
+protected:
+
+ /// @brief Checks if the @ref create was called.
+ ///
+ /// @throw HttpMessageError if @ref create wasn't called.
+ void checkCreated() const;
+
+ /// @brief Checks if the @ref finalize was called.
+ ///
+ /// @throw HttpMessageError if @ref finalize wasn't called.
+ void checkFinalized() const;
+
+ /// @brief Checks if the set is empty or the specified element belongs
+ /// to this set.
+ ///
+ /// This is a convenience method used by the class to verify that the
+ /// given HTTP method belongs to "required methods", HTTP version belongs
+ /// to "required versions" etc.
+ ///
+ /// @param element Reference to the element.
+ /// @param element_set Reference to the set of elements.
+ /// @tparam T Element type, @ref HttpVersion etc.
+ ///
+ /// @return true if the element set is empty or if the element belongs
+ /// to the set.
+ template<typename T>
+ bool inRequiredSet(const T& element,
+ const std::set<T>& element_set) const {
+ return (element_set.empty() || element_set.count(element) > 0);
+ }
+
+ /// @brief Message direction (inbound or outbound).
+ Direction direction_;
+
+ /// @brief Set of required HTTP versions.
+ ///
+ /// If the set is empty, all versions are allowed.
+ std::set<HttpVersion> required_versions_;
+
+ /// @brief HTTP version numbers.
+ HttpVersion http_version_;
+
+ /// @brief Map of HTTP headers indexed by lower case header names.
+ typedef std::map<std::string, HttpHeaderPtr> HttpHeaderMap;
+
+ /// @brief Map holding required HTTP headers.
+ ///
+ /// The key of this map specifies the lower case HTTP header name.
+ /// If the value of the HTTP header is empty, the header is required
+ /// but the value of the header is not checked. If the value is
+ /// non-empty, the value in the HTTP request must be equal (case
+ /// insensitive) to the value in the map.
+ HttpHeaderMap required_headers_;
+
+ /// @brief Flag indicating whether @ref create was called.
+ bool created_;
+
+ /// @brief Flag indicating whether @ref finalize was called.
+ bool finalized_;
+
+ /// @brief Parsed HTTP headers.
+ HttpHeaderMap headers_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // HTTP_MESSAGE_H
diff --git a/src/lib/http/http_message_parser_base.cc b/src/lib/http/http_message_parser_base.cc
new file mode 100644
index 0000000..000e343
--- /dev/null
+++ b/src/lib/http/http_message_parser_base.cc
@@ -0,0 +1,307 @@
+// Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/http_message_parser_base.h>
+#include <functional>
+#include <sstream>
+
+using namespace isc::util;
+
+namespace isc {
+namespace http {
+
+const int HttpMessageParserBase::HTTP_PARSE_OK_ST;
+const int HttpMessageParserBase::HTTP_PARSE_FAILED_ST;
+
+const int HttpMessageParserBase::DATA_READ_OK_EVT;
+const int HttpMessageParserBase::NEED_MORE_DATA_EVT;
+const int HttpMessageParserBase::MORE_DATA_PROVIDED_EVT;
+const int HttpMessageParserBase::HTTP_PARSE_OK_EVT;
+const int HttpMessageParserBase::HTTP_PARSE_FAILED_EVT;
+
+
+HttpMessageParserBase::HttpMessageParserBase(HttpMessage& message)
+ : StateModel(), message_(message), buffer_(), buffer_pos_(0),
+ error_message_() {
+}
+
+void
+HttpMessageParserBase::poll() {
+ try {
+ // Run the parser until it runs out of input data or until
+ // parsing completes.
+ do {
+ getState(getCurrState())->run();
+
+ } while (!isModelDone() && (getNextEvent() != NOP_EVT) &&
+ (getNextEvent() != NEED_MORE_DATA_EVT));
+ } catch (const std::exception& ex) {
+ abortModel(ex.what());
+ }
+}
+
+bool
+HttpMessageParserBase::needData() const {
+ return ((getNextEvent() == NEED_MORE_DATA_EVT) ||
+ (getNextEvent() == START_EVT));
+}
+
+bool
+HttpMessageParserBase::httpParseOk() const {
+ return ((getNextEvent() == END_EVT) &&
+ (getLastEvent() == HTTP_PARSE_OK_EVT));
+}
+
+void
+HttpMessageParserBase::postBuffer(const void* buf, const size_t buf_size) {
+ if (buf_size > 0) {
+ // The next event is NEED_MORE_DATA_EVT when the parser wants to
+ // signal that more data is needed. This method is called to supply
+ // more data and thus it should change the next event to
+ // MORE_DATA_PROVIDED_EVT.
+ if (getNextEvent() == NEED_MORE_DATA_EVT) {
+ transition(getCurrState(), MORE_DATA_PROVIDED_EVT);
+ }
+ buffer_.insert(buffer_.end(), static_cast<const char*>(buf),
+ static_cast<const char*>(buf) + buf_size);
+ }
+}
+
+std::string
+HttpMessageParserBase::getBufferAsString(const size_t limit) const {
+ std::string message(buffer_.begin(), buffer_.end());
+ return (logFormatHttpMessage(message, limit));
+}
+
+std::string
+HttpMessageParserBase::logFormatHttpMessage(const std::string& message,
+ const size_t limit) {
+ if ((limit > 0) && !message.empty()) {
+ if (limit < message.size()) {
+ std::ostringstream s;
+ s << message.substr(0, limit)
+ << ".........\n(truncating HTTP message larger than "
+ << limit << " characters)\n";
+ return (s.str());
+ }
+ }
+
+ // Return original message if it is empty or does not exceed the
+ // limit.
+ return (message);
+}
+
+
+void
+HttpMessageParserBase::defineEvents() {
+ StateModel::defineEvents();
+
+ // Define HTTP parser specific events.
+ defineEvent(DATA_READ_OK_EVT, "DATA_READ_OK_EVT");
+ defineEvent(NEED_MORE_DATA_EVT, "NEED_MORE_DATA_EVT");
+ defineEvent(MORE_DATA_PROVIDED_EVT, "MORE_DATA_PROVIDED_EVT");
+ defineEvent(HTTP_PARSE_OK_EVT, "HTTP_PARSE_OK_EVT");
+ defineEvent(HTTP_PARSE_FAILED_EVT, "HTTP_PARSE_FAILED_EVT");
+}
+
+void
+HttpMessageParserBase::verifyEvents() {
+ StateModel::verifyEvents();
+
+ getEvent(DATA_READ_OK_EVT);
+ getEvent(NEED_MORE_DATA_EVT);
+ getEvent(MORE_DATA_PROVIDED_EVT);
+ getEvent(HTTP_PARSE_OK_EVT);
+ getEvent(HTTP_PARSE_FAILED_EVT);
+}
+
+void
+HttpMessageParserBase::defineStates() {
+ // Call parent class implementation first.
+ StateModel::defineStates();
+
+ defineState(HTTP_PARSE_OK_ST, "HTTP_PARSE_OK_ST",
+ std::bind(&HttpMessageParserBase::parseEndedHandler, this));
+
+ defineState(HTTP_PARSE_FAILED_ST, "HTTP_PARSE_FAILED_ST",
+ std::bind(&HttpMessageParserBase::parseEndedHandler, this));
+}
+
+void
+HttpMessageParserBase::stateWithReadHandler(const std::string& handler_name,
+ std::function<void(const char c)>
+ after_read_logic) {
+ std::string bytes;
+ getNextFromBuffer(bytes);
+ // Do nothing if we reached the end of buffer.
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch(getNextEvent()) {
+ case DATA_READ_OK_EVT:
+ case MORE_DATA_PROVIDED_EVT:
+ after_read_logic(bytes[0]);
+ break;
+ default:
+ invalidEventError(handler_name, getNextEvent());
+ }
+ }
+}
+
+void
+HttpMessageParserBase::stateWithMultiReadHandler(const std::string& handler_name,
+ std::function<void(const std::string&)>
+ after_read_logic) {
+ std::string bytes;
+ getNextFromBuffer(bytes, 0);
+ // Do nothing if we reached the end of buffer.
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch(getNextEvent()) {
+ case DATA_READ_OK_EVT:
+ case MORE_DATA_PROVIDED_EVT:
+ after_read_logic(bytes);
+ break;
+ default:
+ invalidEventError(handler_name, getNextEvent());
+ }
+ }
+}
+
+void
+HttpMessageParserBase::parseFailure(const std::string& error_msg) {
+ error_message_ = error_msg + " : " + getContextStr();
+ transition(HTTP_PARSE_FAILED_ST, HTTP_PARSE_FAILED_EVT);
+}
+
+void
+HttpMessageParserBase::onModelFailure(const std::string& explanation) {
+ if (error_message_.empty()) {
+ error_message_ = explanation;
+ }
+}
+
+void
+HttpMessageParserBase::getNextFromBuffer(std::string& bytes, const size_t limit) {
+ unsigned int ev = getNextEvent();
+ bytes = "\0";
+ // The caller should always provide additional data when the
+ // NEED_MORE_DATA_EVT occurs. If the next event is still
+ // NEED_MORE_DATA_EVT it indicates that the caller hasn't provided
+ // the data.
+ if (ev == NEED_MORE_DATA_EVT) {
+ isc_throw(HttpParseError,
+ "HTTP request parser requires new data to progress, but no data"
+ " have been provided. The transaction is aborted to avoid"
+ " a deadlock. This is a Kea HTTP server logic error!");
+
+ } else {
+ // Try to retrieve characters from the buffer.
+ const bool data_exist = popNextFromBuffer(bytes, limit);
+ if (!data_exist) {
+ // There is no more data so it is really not possible that we're
+ // at MORE_DATA_PROVIDED_EVT.
+ if (ev == MORE_DATA_PROVIDED_EVT) {
+ isc_throw(HttpParseError,
+ "HTTP server state indicates that new data have been"
+ " provided to be parsed, but the transaction buffer"
+ " contains no new data. This is a Kea HTTP server logic"
+ " error!");
+
+ } else {
+ // If there is no more data we should set NEED_MORE_DATA_EVT
+ // event to indicate that new data should be provided.
+ postNextEvent(NEED_MORE_DATA_EVT);
+ }
+ }
+ }
+}
+
+void
+HttpMessageParserBase::invalidEventError(const std::string& handler_name,
+ const unsigned int event) {
+ isc_throw(HttpParseError, handler_name << ": invalid event "
+ << getEventLabel(static_cast<int>(event)));
+}
+
+void
+HttpMessageParserBase::parseEndedHandler() {
+ switch(getNextEvent()) {
+ case HTTP_PARSE_OK_EVT:
+ message_.finalize();
+ transition(END_ST, END_EVT);
+ break;
+ case HTTP_PARSE_FAILED_EVT:
+ abortModel("HTTP message parsing failed");
+ break;
+
+ default:
+ invalidEventError("parseEndedHandler", getNextEvent());
+ }
+}
+
+bool
+HttpMessageParserBase::popNextFromBuffer(std::string& next, const size_t limit) {
+ // If there are any characters in the buffer, pop next.
+ if (buffer_pos_ < buffer_.size()) {
+ next = buffer_.substr(buffer_pos_, limit == 0 ? std::string::npos : limit);
+
+ if (limit > 0) {
+ buffer_pos_ += limit;
+ }
+
+ if ((buffer_pos_ > buffer_.size()) || (limit == 0)) {
+ buffer_pos_ = buffer_.size();
+ }
+ return (true);
+ }
+ return (false);
+}
+
+bool
+HttpMessageParserBase::isChar(const char c) const {
+ // was (c >= 0) && (c <= 127)
+ return (c >= 0);
+}
+
+bool
+HttpMessageParserBase::isCtl(const char c) const {
+ return (((c >= 0) && (c <= 31)) || (c == 127));
+}
+
+bool
+HttpMessageParserBase::isSpecial(const char c) const {
+ switch (c) {
+ case '(':
+ case ')':
+ case '<':
+ case '>':
+ case '@':
+ case ',':
+ case ';':
+ case ':':
+ case '\\':
+ case '"':
+ case '/':
+ case '[':
+ case ']':
+ case '?':
+ case '=':
+ case '{':
+ case '}':
+ case ' ':
+ case '\t':
+ return true;
+
+ default:
+ ;
+ }
+
+ return false;
+}
+
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/http_message_parser_base.h b/src/lib/http/http_message_parser_base.h
new file mode 100644
index 0000000..01f9831
--- /dev/null
+++ b/src/lib/http/http_message_parser_base.h
@@ -0,0 +1,316 @@
+// Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_MESSAGE_PARSER_BASE_H
+#define HTTP_MESSAGE_PARSER_BASE_H
+
+#include <exceptions/exceptions.h>
+#include <http/http_message.h>
+#include <util/state_model.h>
+#include <functional>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Exception thrown when an error during parsing HTTP message
+/// has occurred.
+///
+/// The most common errors are due to receiving malformed requests.
+class HttpParseError : public Exception {
+public:
+ HttpParseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Base class for the HTTP message parsers.
+///
+/// This is a base class for @c HttpRequestParser and @c HttpResponseParser
+/// classes. It provides common states, events and functionality for processing
+/// the received HTTP messages.
+///
+/// This class must not be used directly. Instead, an instance of the
+/// derived class should be used.
+///
+/// HTTP uses TCP as a transport which is asynchronous in nature, i.e. the
+/// HTTP message is received in chunks and multiple TCP connections can be
+/// established at the same time. Multiplexing between these connections
+/// requires providing a separate state machine per connection to "remember"
+/// the state of each transaction when the parser is waiting for asynchronous
+/// data to be delivered. While the parser is waiting for the data, it can
+/// parse requests received over other connections. This class provides means
+/// for parsing partial data received over the specific connection and
+/// interrupting data parsing to switch to a different context.
+///
+/// A new method @ref HttpMessageParserBase::poll has been created to run the
+/// parser's state machine as long as there are unparsed data in the parser's
+/// internal buffer. This method returns control to the caller when the parser
+/// runs out of data in this buffer. The caller must feed the buffer by calling
+/// @ref HttpMessageParserBase::postBuffer and then run
+/// @ref HttpMessageParserBase::poll again.
+///
+/// In case, the caller provides more data than indicated by the "Content-Length"
+/// header the parser will return from @c poll() after parsing the data which
+/// constitute the HTTP request and not parse the extraneous data. The caller
+/// should test the @ref HttpMessageParserBase::needData and
+/// @ref HttpMessageParserBase::httpParseOk to determine whether parsing has
+/// completed.
+///
+/// The @ref util::StateModel::runModel must not be used to run the parser
+/// state machine, thus it is made private method.
+class HttpMessageParserBase : public util::StateModel {
+public:
+
+ /// @name States supported by the HttpMessageParserBase.
+ ///
+ //@{
+
+ /// @brief Parsing successfully completed.
+ static const int HTTP_PARSE_OK_ST = SM_DERIVED_STATE_MIN + 1000;
+
+ /// @brief Parsing failed.
+ static const int HTTP_PARSE_FAILED_ST = SM_DERIVED_STATE_MIN + 1001;
+
+ //@}
+
+ /// @name Events used during HTTP message parsing.
+ ///
+ //@{
+
+ /// @brief Chunk of data successfully read and parsed.
+ static const int DATA_READ_OK_EVT = SM_DERIVED_EVENT_MIN + 1;
+
+ /// @brief Unable to proceed with parsing until new data is provided.
+ static const int NEED_MORE_DATA_EVT = SM_DERIVED_EVENT_MIN + 2;
+
+ /// @brief New data provided and parsing should continue.
+ static const int MORE_DATA_PROVIDED_EVT = SM_DERIVED_EVENT_MIN + 3;
+
+ /// @brief Parsing HTTP request successful.
+ static const int HTTP_PARSE_OK_EVT = SM_DERIVED_EVENT_MIN + 1000;
+
+ /// @brief Parsing HTTP request failed.
+ static const int HTTP_PARSE_FAILED_EVT = SM_DERIVED_EVENT_MIN + 1001;
+
+ //@}
+
+ /// @brief Constructor.
+ ///
+ /// @param message Reference to the HTTP request or response message.
+ HttpMessageParserBase(HttpMessage& message);
+
+ /// @brief Run the parser as long as the amount of data is sufficient.
+ ///
+ /// The data to be parsed should be provided by calling
+ /// @ref HttpMessageParserBase::postBuffer. When the parser reaches the end
+ /// of the data buffer the @ref HttpMessageParserBase::poll sets the next
+ /// event to @ref NEED_MORE_DATA_EVT and returns. The caller should then invoke
+ /// @ref HttpMessageParserBase::postBuffer again to provide more data to the
+ /// parser, and call @ref HttpMessageParserBase::poll to continue parsing.
+ ///
+ /// This method also returns when parsing completes or fails. The last
+ /// event can be examined to check whether parsing was successful or not.
+ void poll();
+
+ /// @brief Returns true if the parser needs more data to continue.
+ ///
+ /// @return true if the next event is NEED_MORE_DATA_EVT.
+ bool needData() const;
+
+ /// @brief Returns true if the message has been parsed successfully.
+ bool httpParseOk() const;
+
+ /// @brief Returns error message.
+ std::string getErrorMessage() const {
+ return (error_message_);
+ }
+
+ /// @brief Provides more input data to the parser.
+ ///
+ /// This method must be called prior to calling @ref HttpMessageParserBase::poll
+ /// to deliver data to be parsed. HTTP messages are received over TCP and
+ /// multiple reads may be necessary to retrieve the entire request. There is
+ /// no need to accumulate the entire request to start parsing it. A chunk
+ /// of data can be provided to the parser using this method and parsed right
+ /// away using @ref HttpMessageParserBase::poll.
+ ///
+ /// @param buf A pointer to the buffer holding the data.
+ /// @param buf_size Size of the data within the buffer.
+ void postBuffer(const void* buf, const size_t buf_size);
+
+ /// @brief Returns parser's input buffer as string.
+ ///
+ /// @param limit Maximum length of the buffer to be output. If the limit is 0,
+ /// the length of the output is unlimited.
+ /// @return Textual representation of the input buffer.
+ std::string getBufferAsString(const size_t limit = 0) const;
+
+ /// @brief Formats provided HTTP message for logging.
+ ///
+ /// This method is useful in cases when there is a need to log a HTTP message
+ /// (as text). If the @c limit is specified the message output is limited to
+ /// this size. If the @c limit is set to 0 (default), the whole message is
+ /// output. The @c getBufferAsString method calls this method internally.
+ ///
+ /// @param message HTTP message to be logged.
+ /// @param limit Maximum length of the buffer to be output. If the limit is 0,
+ /// the length of the output is unlimited.
+ /// @return HTTP message formatted for logging.
+ static std::string logFormatHttpMessage(const std::string& message,
+ const size_t limit = 0);
+
+private:
+
+ /// @brief Make @ref runModel private to make sure that the caller uses
+ /// @ref poll method instead.
+ using StateModel::runModel;
+
+protected:
+
+ /// @brief Define events used by the parser.
+ virtual void defineEvents();
+
+ /// @brief Verifies events used by the parser.
+ virtual void verifyEvents();
+
+ /// @brief Defines states of the parser.
+ virtual void defineStates();
+
+ /// @brief Generic parser handler which reads a single byte of data and
+ /// parses it using specified callback function.
+ ///
+ /// This generic handler is used in most of the parser states to parse a
+ /// single byte of input data. If there is no more data it simply returns.
+ /// Otherwise, if the next event is DATA_READ_OK_EVT or
+ /// MORE_DATA_PROVIDED_EVT, it calls the provided callback function to
+ /// parse the new byte of data. For all other states it throws an exception.
+ ///
+ /// @param handler_name Name of the handler function which called this
+ /// method.
+ /// @param after_read_logic Callback function to parse the byte of data.
+ /// This callback function implements state specific logic.
+ ///
+ /// @throw HttpRequestParserError when invalid event occurred.
+ void stateWithReadHandler(const std::string& handler_name,
+ std::function<void(const char c)>
+ after_read_logic);
+
+ /// @brief Generic parser handler which reads multiple bytes of data and
+ /// parses it using specified callback function.
+ ///
+ /// This handler is mostly used for parsing body of the HTTP message,
+ /// where we don't validate the content read. Reading multiple bytes
+ /// is the most efficient. If there is no more data it simply returns.
+ /// Otherwise, if the next event is DATA_READ_OK_EVT or
+ /// MORE_DATA_PROVIDED_EVT, it calls the provided callback function to
+ /// parse the new byte of data.
+ ///
+ /// @param handler_name Name of the handler function which called this
+ /// method.
+ /// @param after_read_logic Callback function to parse multiple bytes of
+ /// data. This callback function implements state specific logic.
+ ///
+ /// @throw HttpRequestParserError when invalid event occurred.
+ void stateWithMultiReadHandler(const std::string& handler_name,
+ std::function<void(const std::string&)>
+ after_read_logic);
+
+ /// @brief Transition parser to failure state.
+ ///
+ /// This method transitions the parser to @ref HTTP_PARSE_FAILED_ST and
+ /// sets next event to HTTP_PARSE_FAILED_EVT.
+ ///
+ /// @param error_msg Error message explaining the failure.
+ void parseFailure(const std::string& error_msg);
+
+ /// @brief A method called when parsing fails.
+ ///
+ /// @param explanation Error message explaining the reason for parsing
+ /// failure.
+ virtual void onModelFailure(const std::string& explanation);
+
+ /// @brief Retrieves next bytes of data from the buffer.
+ ///
+ /// During normal operation, when there is no more data in the buffer,
+ /// the parser sets NEED_MORE_DATA_EVT as next event to signal the need for
+ /// calling @ref HttpMessageParserBase::postBuffer.
+ ///
+ /// @param [out] bytes Reference to the variable where read data should be stored.
+ /// @param limit Maximum number of bytes to be read.
+ ///
+ /// @throw HttpMessageParserBaseError If current event is already set to
+ /// NEED_MORE_DATA_EVT or MORE_DATA_PROVIDED_EVT. In the former case, it
+ /// indicates that the caller failed to provide new data using
+ /// @ref HttpMessageParserBase::postBuffer. The latter case is highly unlikely
+ /// as it indicates that no new data were provided but the state of the
+ /// parser was changed from NEED_MORE_DATA_EVT or the data were provided
+ /// but the data buffer is empty. In both cases, it is an internal server
+ /// error.
+ void getNextFromBuffer(std::string& bytes, const size_t limit = 1);
+
+ /// @brief This method is called when invalid event occurred in a particular
+ /// parser state.
+ ///
+ /// This method simply throws @ref HttpParseError informing about invalid
+ /// event occurring for the particular parser state. The error message
+ /// includes the name of the handler in which the exception has been
+ /// thrown. It also includes the event which caused the exception.
+ ///
+ /// @param handler_name Name of the handler in which the exception is
+ /// thrown.
+ /// @param event An event which caused the exception.
+ ///
+ /// @throw HttpMessageParserBaseError.
+ void invalidEventError(const std::string& handler_name,
+ const unsigned int event);
+
+ /// @brief Handler for HTTP_PARSE_OK_ST and HTTP_PARSE_FAILED_ST.
+ ///
+ /// If parsing is successful, it calls @ref HttpRequest::create to validate
+ /// the HTTP request. In both cases it transitions the parser to the END_ST.
+ void parseEndedHandler();
+
+ /// @brief Tries to read next byte from buffer.
+ ///
+ /// @param [out] next A reference to the variable where read data should be
+ /// stored.
+ /// @param limit Maximum number of characters to be read.
+ ///
+ /// @return true if data was successfully read, false otherwise.
+ bool popNextFromBuffer(std::string& next, const size_t limit = 1);
+
+ /// @brief Checks if specified value is a character.
+ ///
+ /// @return true, if specified value is a character.
+ bool isChar(const char c) const;
+
+ /// @brief Checks if specified value is a control value.
+ ///
+ /// @return true, if specified value is a control value.
+ bool isCtl(const char c) const;
+
+ /// @brief Checks if specified value is a special character.
+ ///
+ /// @return true, if specified value is a special character.
+ bool isSpecial(const char c) const;
+
+ /// @brief Reference to the parsed HTTP message.
+ HttpMessage& message_;
+
+ /// @brief Internal buffer from which parser reads data.
+ std::string buffer_;
+
+ /// @brief Position of the next character to read from the buffer.
+ size_t buffer_pos_;
+
+ /// @brief Error message set by @ref onModelFailure.
+ std::string error_message_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/http_messages.cc b/src/lib/http/http_messages.cc
new file mode 100644
index 0000000..292d2d8
--- /dev/null
+++ b/src/lib/http/http_messages.cc
@@ -0,0 +1,77 @@
+// File created from ../../../src/lib/http/http_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace http {
+
+extern const isc::log::MessageID HTTPS_REQUEST_RECEIVE_START = "HTTPS_REQUEST_RECEIVE_START";
+extern const isc::log::MessageID HTTP_BAD_CLIENT_REQUEST_RECEIVED = "HTTP_BAD_CLIENT_REQUEST_RECEIVED";
+extern const isc::log::MessageID HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS = "HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS";
+extern const isc::log::MessageID HTTP_BAD_SERVER_RESPONSE_RECEIVED = "HTTP_BAD_SERVER_RESPONSE_RECEIVED";
+extern const isc::log::MessageID HTTP_BAD_SERVER_RESPONSE_RECEIVED_DETAILS = "HTTP_BAD_SERVER_RESPONSE_RECEIVED_DETAILS";
+extern const isc::log::MessageID HTTP_CLIENT_MT_STARTED = "HTTP_CLIENT_MT_STARTED";
+extern const isc::log::MessageID HTTP_CLIENT_QUEUE_SIZE_GROWING = "HTTP_CLIENT_QUEUE_SIZE_GROWING";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_RECEIVED = "HTTP_CLIENT_REQUEST_RECEIVED";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_RECEIVED_DETAILS = "HTTP_CLIENT_REQUEST_RECEIVED_DETAILS";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_SEND = "HTTP_CLIENT_REQUEST_SEND";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_SEND_DETAILS = "HTTP_CLIENT_REQUEST_SEND_DETAILS";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED = "HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED";
+extern const isc::log::MessageID HTTP_CONNECTION_CLOSE_CALLBACK_FAILED = "HTTP_CONNECTION_CLOSE_CALLBACK_FAILED";
+extern const isc::log::MessageID HTTP_CONNECTION_HANDSHAKE_FAILED = "HTTP_CONNECTION_HANDSHAKE_FAILED";
+extern const isc::log::MessageID HTTP_CONNECTION_HANDSHAKE_START = "HTTP_CONNECTION_HANDSHAKE_START";
+extern const isc::log::MessageID HTTP_CONNECTION_SHUTDOWN = "HTTP_CONNECTION_SHUTDOWN";
+extern const isc::log::MessageID HTTP_CONNECTION_SHUTDOWN_FAILED = "HTTP_CONNECTION_SHUTDOWN_FAILED";
+extern const isc::log::MessageID HTTP_CONNECTION_STOP = "HTTP_CONNECTION_STOP";
+extern const isc::log::MessageID HTTP_CONNECTION_STOP_FAILED = "HTTP_CONNECTION_STOP_FAILED";
+extern const isc::log::MessageID HTTP_DATA_RECEIVED = "HTTP_DATA_RECEIVED";
+extern const isc::log::MessageID HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED = "HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED";
+extern const isc::log::MessageID HTTP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED = "HTTP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED";
+extern const isc::log::MessageID HTTP_REQUEST_RECEIVE_START = "HTTP_REQUEST_RECEIVE_START";
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_RECEIVED = "HTTP_SERVER_RESPONSE_RECEIVED";
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_RECEIVED_DETAILS = "HTTP_SERVER_RESPONSE_RECEIVED_DETAILS";
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_SEND = "HTTP_SERVER_RESPONSE_SEND";
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_SEND_DETAILS = "HTTP_SERVER_RESPONSE_SEND_DETAILS";
+
+} // namespace http
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "HTTPS_REQUEST_RECEIVE_START", "start receiving request from %1",
+ "HTTP_BAD_CLIENT_REQUEST_RECEIVED", "bad request received from %1: %2",
+ "HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS", "detailed information about bad request received from %1:\n%2",
+ "HTTP_BAD_SERVER_RESPONSE_RECEIVED", "bad response received when communicating with %1: %2",
+ "HTTP_BAD_SERVER_RESPONSE_RECEIVED_DETAILS", "detailed information about bad response received from %1:\n%2",
+ "HTTP_CLIENT_MT_STARTED", "HttpClient has been started in multi-threaded mode running %1 threads",
+ "HTTP_CLIENT_QUEUE_SIZE_GROWING", "queue for URL: %1, now has %2 entries and may be growing too quickly",
+ "HTTP_CLIENT_REQUEST_RECEIVED", "received HTTP request from %1",
+ "HTTP_CLIENT_REQUEST_RECEIVED_DETAILS", "detailed information about well-formed request received from %1:\n%2",
+ "HTTP_CLIENT_REQUEST_SEND", "sending HTTP request %1 to %2",
+ "HTTP_CLIENT_REQUEST_SEND_DETAILS", "detailed information about request sent to %1:\n%2",
+ "HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED", "HTTP request timeout occurred when communicating with %1",
+ "HTTP_CONNECTION_CLOSE_CALLBACK_FAILED", "Connection close callback threw an exception",
+ "HTTP_CONNECTION_HANDSHAKE_FAILED", "TLS handshake with %1 failed with %2",
+ "HTTP_CONNECTION_HANDSHAKE_START", "start TLS handshake with %1 with timeout %2",
+ "HTTP_CONNECTION_SHUTDOWN", "shutting down HTTP connection from %1",
+ "HTTP_CONNECTION_SHUTDOWN_FAILED", "shutting down HTTP connection failed",
+ "HTTP_CONNECTION_STOP", "stopping HTTP connection from %1",
+ "HTTP_CONNECTION_STOP_FAILED", "stopping HTTP connection failed",
+ "HTTP_DATA_RECEIVED", "received %1 bytes from %2",
+ "HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED", "closing persistent connection with %1 as a result of a timeout",
+ "HTTP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED", "premature connection timeout occurred: in transaction ? %1, transid: %2, current_transid: %3",
+ "HTTP_REQUEST_RECEIVE_START", "start receiving request from %1 with timeout %2",
+ "HTTP_SERVER_RESPONSE_RECEIVED", "received HTTP response from %1",
+ "HTTP_SERVER_RESPONSE_RECEIVED_DETAILS", "detailed information about well-formed response received from %1:\n%2",
+ "HTTP_SERVER_RESPONSE_SEND", "sending HTTP response %1 to %2",
+ "HTTP_SERVER_RESPONSE_SEND_DETAILS", "detailed information about response sent to %1:\n%2",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/http/http_messages.h b/src/lib/http/http_messages.h
new file mode 100644
index 0000000..4ac3030
--- /dev/null
+++ b/src/lib/http/http_messages.h
@@ -0,0 +1,42 @@
+// File created from ../../../src/lib/http/http_messages.mes
+
+#ifndef HTTP_MESSAGES_H
+#define HTTP_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace http {
+
+extern const isc::log::MessageID HTTPS_REQUEST_RECEIVE_START;
+extern const isc::log::MessageID HTTP_BAD_CLIENT_REQUEST_RECEIVED;
+extern const isc::log::MessageID HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS;
+extern const isc::log::MessageID HTTP_BAD_SERVER_RESPONSE_RECEIVED;
+extern const isc::log::MessageID HTTP_BAD_SERVER_RESPONSE_RECEIVED_DETAILS;
+extern const isc::log::MessageID HTTP_CLIENT_MT_STARTED;
+extern const isc::log::MessageID HTTP_CLIENT_QUEUE_SIZE_GROWING;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_RECEIVED;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_RECEIVED_DETAILS;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_SEND;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_SEND_DETAILS;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED;
+extern const isc::log::MessageID HTTP_CONNECTION_CLOSE_CALLBACK_FAILED;
+extern const isc::log::MessageID HTTP_CONNECTION_HANDSHAKE_FAILED;
+extern const isc::log::MessageID HTTP_CONNECTION_HANDSHAKE_START;
+extern const isc::log::MessageID HTTP_CONNECTION_SHUTDOWN;
+extern const isc::log::MessageID HTTP_CONNECTION_SHUTDOWN_FAILED;
+extern const isc::log::MessageID HTTP_CONNECTION_STOP;
+extern const isc::log::MessageID HTTP_CONNECTION_STOP_FAILED;
+extern const isc::log::MessageID HTTP_DATA_RECEIVED;
+extern const isc::log::MessageID HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED;
+extern const isc::log::MessageID HTTP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED;
+extern const isc::log::MessageID HTTP_REQUEST_RECEIVE_START;
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_RECEIVED;
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_RECEIVED_DETAILS;
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_SEND;
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_SEND_DETAILS;
+
+} // namespace http
+} // namespace isc
+
+#endif // HTTP_MESSAGES_H
diff --git a/src/lib/http/http_messages.mes b/src/lib/http/http_messages.mes
new file mode 100644
index 0000000..625c5cb
--- /dev/null
+++ b/src/lib/http/http_messages.mes
@@ -0,0 +1,164 @@
+# Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$NAMESPACE isc::http
+
+% HTTPS_REQUEST_RECEIVE_START start receiving request from %1
+This debug message is issued when the server starts receiving new request
+over the established connection. The argument specifies the address
+of the remote endpoint.
+
+% HTTP_BAD_CLIENT_REQUEST_RECEIVED bad request received from %1: %2
+This debug message is issued when an HTTP client sends malformed request to
+the server. This includes HTTP requests using unexpected content types,
+including malformed JSON etc. The first argument specifies an address of
+the remote endpoint which sent the request. The second argument provides
+a detailed error message.
+
+% HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS detailed information about bad request received from %1:\n%2
+This debug message is issued when an HTTP client sends malformed request to
+the server. It includes detailed information about the received request
+rejected by the server. The first argument specifies an address of
+the remote endpoint which sent the request. The second argument provides
+a request in the textual format. The request is truncated by the logger
+if it is too large to be printed.
+
+% HTTP_BAD_SERVER_RESPONSE_RECEIVED bad response received when communicating with %1: %2
+This debug message is issued when an HTTP client fails to receive a response
+from the server or when this response is malformed. The first argument
+specifies the server URL. The second argument provides a detailed error
+message.
+
+% HTTP_BAD_SERVER_RESPONSE_RECEIVED_DETAILS detailed information about bad response received from %1:\n%2
+This debug message is issued when an HTTP client receives malformed response
+from the server. The first argument specifies an URL of the server. The
+second argument provides a response in the textual format. The request is
+truncated by the logger if it is too large to be printed.
+
+% HTTP_CLIENT_MT_STARTED HttpClient has been started in multi-threaded mode running %1 threads
+This debug message is issued when a multi-threaded HTTP client instance has
+been created. The argument specifies the maximum number of threads.
+
+% HTTP_CLIENT_QUEUE_SIZE_GROWING queue for URL: %1, now has %2 entries and may be growing too quickly
+This warning message is issued when the queue of pending requests for the
+given URL appears to be growing more quickly than the requests can be handled.
+It will be emitted periodically as long as the queue size continues to grow.
+This may occur with a surge of client traffic creating a momentary backlog
+which then subsides as the surge subsides. If it happens continually then
+it most likely indicates a deployment configuration that cannot sustain the
+client load.
+
+% HTTP_CLIENT_REQUEST_RECEIVED received HTTP request from %1
+This debug message is issued when the server finished receiving a HTTP
+request from the remote endpoint. The address of the remote endpoint is
+specified as an argument.
+
+% HTTP_CLIENT_REQUEST_RECEIVED_DETAILS detailed information about well-formed request received from %1:\n%2
+This debug message is issued when the HTTP server receives a well-formed
+request. It includes detailed information about the received request. The
+first argument specifies an address of the remote endpoint which sent the
+request. The second argument provides the request in the textual format.
+The request is truncated by the logger if it is too large to be printed.
+
+% HTTP_CLIENT_REQUEST_SEND sending HTTP request %1 to %2
+This debug message is issued when the client is starting to send a HTTP
+request to a server. The first argument holds basic information
+about the request (HTTP version number and status code). The second
+argument specifies a URL of the server.
+
+% HTTP_CLIENT_REQUEST_SEND_DETAILS detailed information about request sent to %1:\n%2
+This debug message is issued right before the client sends an HTTP request
+to the server. It includes detailed information about the request. The
+first argument specifies an URL of the server to which the request is
+being sent. The second argument provides the request in the textual form.
+The request is truncated by the logger if it is too large to be printed.
+
+% HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED HTTP request timeout occurred when communicating with %1
+This debug message is issued when the HTTP request timeout has occurred and
+the server is going to send a response with Http Request timeout status
+code.
+
+% HTTP_CONNECTION_CLOSE_CALLBACK_FAILED Connection close callback threw an exception
+This is an error message emitted when the close connection callback
+registered on the connection failed unexpectedly. This is a programmatic
+error that should be submitted as a bug.
+
+% HTTP_CONNECTION_HANDSHAKE_FAILED TLS handshake with %1 failed with %2
+This information message is issued when the TLS handshake failed at the
+server side. The client address and the error message are displayed.
+
+% HTTP_CONNECTION_HANDSHAKE_START start TLS handshake with %1 with timeout %2
+This debug message is issued when the server starts the TLS handshake
+with the remote endpoint. The first argument specifies the address
+of the remote endpoint. The second argument specifies request timeout in
+seconds.
+
+% HTTP_CONNECTION_SHUTDOWN shutting down HTTP connection from %1
+This debug message is issued when one of the HTTP connections is shut down.
+The connection can be stopped as a result of an error or after the
+successful message exchange with a client.
+
+% HTTP_CONNECTION_SHUTDOWN_FAILED shutting down HTTP connection failed
+This error message is issued when an error occurred during shutting down
+a HTTP connection with a client.
+
+% HTTP_CONNECTION_STOP stopping HTTP connection from %1
+This debug message is issued when one of the HTTP connections is stopped.
+The connection can be stopped as a result of an error or after the
+successful message exchange with a client.
+
+% HTTP_CONNECTION_STOP_FAILED stopping HTTP connection failed
+This error message is issued when an error occurred during closing a
+HTTP connection with a client.
+
+% HTTP_DATA_RECEIVED received %1 bytes from %2
+This debug message is issued when the server receives a chunk of data from
+the remote endpoint. This may include the whole request or only a part
+of the request. The first argument specifies the amount of received data.
+The second argument specifies an address of the remote endpoint which
+produced the data.
+
+% HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED closing persistent connection with %1 as a result of a timeout
+This debug message is issued when the persistent HTTP connection is being
+closed as a result of being idle.
+
+% HTTP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED premature connection timeout occurred: in transaction ? %1, transid: %2, current_transid: %3
+This warning message is issued when unexpected timeout occurred during the
+transaction. This is proven to occur when the system clock is moved manually
+or as a result of synchronization with a time server. Any ongoing transactions
+will be interrupted. New transactions should be conducted normally.
+
+% HTTP_REQUEST_RECEIVE_START start receiving request from %1 with timeout %2
+This debug message is issued when the server starts receiving new request
+over the established connection. The first argument specifies the address
+of the remote endpoint. The second argument specifies request timeout in
+seconds.
+
+% HTTP_SERVER_RESPONSE_RECEIVED received HTTP response from %1
+This debug message is issued when the client finished receiving an HTTP
+response from the server. The URL of the server is specified as an argument.
+
+% HTTP_SERVER_RESPONSE_RECEIVED_DETAILS detailed information about well-formed response received from %1:\n%2
+This debug message is issued when the HTTP client receives a well-formed
+response from the server. It includes detailed information about the
+received response. The first argument specifies a URL of the server which
+sent the response. The second argument provides the response in the textual
+format. The response is truncated by the logger if it is too large to
+be printed.
+
+% HTTP_SERVER_RESPONSE_SEND sending HTTP response %1 to %2
+This debug message is issued when the server is starting to send a HTTP
+response to a remote endpoint. The first argument holds basic information
+about the response (HTTP version number and status code). The second
+argument specifies an address of the remote endpoint.
+
+% HTTP_SERVER_RESPONSE_SEND_DETAILS detailed information about response sent to %1:\n%2
+This debug message is issued right before the server sends a HTTP response
+to the client. It includes detailed information about the response. The
+first argument specifies an address of the remote endpoint to which the
+response is being sent. The second argument provides a response in the
+textual form. The response is truncated by the logger if it is too large
+to be printed.
diff --git a/src/lib/http/http_thread_pool.cc b/src/lib/http/http_thread_pool.cc
new file mode 100644
index 0000000..b0961b1
--- /dev/null
+++ b/src/lib/http/http_thread_pool.cc
@@ -0,0 +1,279 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/interval_timer.h>
+#include <exceptions/exceptions.h>
+#include <http/http_log.h>
+#include <http/http_messages.h>
+#include <http/http_thread_pool.h>
+#include <util/multi_threading_mgr.h>
+#include <util/unlock_guard.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <atomic>
+#include <functional>
+#include <iostream>
+#include <list>
+#include <mutex>
+#include <thread>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::http;
+using namespace isc::util;
+
+HttpThreadPool::HttpThreadPool(IOServicePtr io_service, size_t pool_size,
+ bool defer_start /* = false */)
+ : pool_size_(pool_size), io_service_(io_service),
+ run_state_(State::STOPPED), mutex_(), thread_cv_(),
+ main_cv_(), paused_(0), running_(0), exited_(0) {
+ if (!pool_size) {
+ isc_throw(BadValue, "pool_size must be non 0");
+ }
+
+ // If we weren't given an IOService, create our own.
+ if (!io_service_) {
+ io_service_.reset(new IOService());
+ }
+
+ // If we're not deferring the start, do it now.
+ if (!defer_start) {
+ run();
+ }
+}
+
+HttpThreadPool::~HttpThreadPool() {
+ stop();
+}
+
+void
+HttpThreadPool::run() {
+ setState(State::RUNNING);
+}
+
+void
+HttpThreadPool::pause() {
+ setState(State::PAUSED);
+}
+
+void
+HttpThreadPool::stop() {
+ setState(State::STOPPED);
+}
+
+HttpThreadPool::State
+HttpThreadPool::getState() {
+ std::lock_guard<std::mutex> lck(mutex_);
+ return (run_state_);
+}
+
+bool
+HttpThreadPool::validateStateChange(State state) const {
+ switch (run_state_) {
+ case State::STOPPED:
+ return (state == State::RUNNING);
+ case State::RUNNING:
+ return (state != State::RUNNING);
+ case State::PAUSED:
+ return (state != State::PAUSED);
+ }
+ return (false);
+}
+
+std::string
+HttpThreadPool::stateToText(State state) {
+ switch (state) {
+ case State::STOPPED:
+ return (std::string("stopped"));
+ case State::RUNNING:
+ return (std::string("running"));
+ case State::PAUSED:
+ return (std::string("paused"));
+ }
+ return (std::string("unknown-state"));
+}
+
+void
+HttpThreadPool::checkPausePermissions() {
+ checkPermissions(State::PAUSED);
+}
+
+void
+HttpThreadPool::checkPermissions(State state) {
+ auto id = std::this_thread::get_id();
+ if (checkThreadId(id)) {
+ isc_throw(MultiThreadingInvalidOperation, "invalid thread pool state change to "
+ << HttpThreadPool::stateToText(state) << " performed by worker thread");
+ }
+}
+
+bool
+HttpThreadPool::checkThreadId(std::thread::id id) {
+ for (auto thread : threads_) {
+ if (id == thread->get_id()) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+void
+HttpThreadPool::setState(State state) {
+ checkPermissions(state);
+
+ std::unique_lock<std::mutex> main_lck(mutex_);
+
+ // Bail if the transition is invalid.
+ if (!validateStateChange(state)) {
+ return;
+ }
+
+ run_state_ = state;
+ // Notify threads of state change.
+ thread_cv_.notify_all();
+
+ switch (state) {
+ case State::RUNNING: {
+ // Restart the IOService.
+ io_service_->restart();
+
+ // While we have fewer threads than we should, make more.
+ while (threads_.size() < pool_size_) {
+ boost::shared_ptr<std::thread> thread(new std::thread(
+ std::bind(&HttpThreadPool::threadWork, this)));
+
+ // Add thread to the pool.
+ threads_.push_back(thread);
+ }
+
+ // Main thread waits here until all threads are running.
+ main_cv_.wait(main_lck,
+ [&]() {
+ return (running_ == threads_.size());
+ });
+
+ exited_ = 0;
+ break;
+ }
+
+ case State::PAUSED: {
+ // Stop IOService.
+ if (!io_service_->stopped()) {
+ io_service_->poll();
+ io_service_->stop();
+ }
+
+ // Main thread waits here until all threads are paused.
+ main_cv_.wait(main_lck,
+ [&]() {
+ return (paused_ == threads_.size());
+ });
+
+ break;
+ }
+
+ case State::STOPPED: {
+ // Stop IOService.
+ if (!io_service_->stopped()) {
+ io_service_->poll();
+ io_service_->stop();
+ }
+
+ // Main thread waits here until all threads have exited.
+ main_cv_.wait(main_lck,
+ [&]() {
+ return (exited_ == threads_.size());
+ });
+
+ for (auto const& thread : threads_) {
+ thread->join();
+ }
+
+ threads_.clear();
+ break;
+ }}
+}
+
+void
+HttpThreadPool::threadWork() {
+ bool done = false;
+ while (!done) {
+ switch (getState()) {
+ case State::RUNNING: {
+ {
+ std::unique_lock<std::mutex> lck(mutex_);
+ running_++;
+
+ // If We're all running notify main thread.
+ if (running_ == pool_size_) {
+ main_cv_.notify_all();
+ }
+ }
+
+ // Run the IOService.
+ io_service_->run();
+
+ {
+ std::unique_lock<std::mutex> lck(mutex_);
+ running_--;
+ }
+
+ break;
+ }
+
+ case State::PAUSED: {
+ std::unique_lock<std::mutex> lck(mutex_);
+ paused_++;
+
+ // If we're all paused notify main.
+ if (paused_ == threads_.size()) {
+ main_cv_.notify_all();
+ }
+
+ // Wait here till I'm released.
+ thread_cv_.wait(lck,
+ [&]() {
+ return (run_state_ != State::PAUSED);
+ });
+
+ paused_--;
+ break;
+ }
+
+ case State::STOPPED: {
+ done = true;
+ break;
+ }}
+ }
+
+ std::unique_lock<std::mutex> lck(mutex_);
+ exited_++;
+
+ // If we've all exited, notify main.
+ if (exited_ == threads_.size()) {
+ main_cv_.notify_all();
+ }
+}
+
+IOServicePtr
+HttpThreadPool::getIOService() const {
+ return (io_service_);
+}
+
+uint16_t
+HttpThreadPool::getPoolSize() const {
+ return (pool_size_);
+}
+
+uint16_t
+HttpThreadPool::getThreadCount() const {
+ return (threads_.size());
+}
diff --git a/src/lib/http/http_thread_pool.h b/src/lib/http/http_thread_pool.h
new file mode 100644
index 0000000..730f8c9
--- /dev/null
+++ b/src/lib/http/http_thread_pool.h
@@ -0,0 +1,265 @@
+// Copyright (C) 2021-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_THREAD_POOL_H
+#define HTTP_THREAD_POOL_H
+
+#include <asiolink/io_service.h>
+#include <util/unlock_guard.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <condition_variable>
+#include <list>
+#include <mutex>
+#include <thread>
+
+namespace isc {
+namespace http {
+
+/// @brief Implements a pausable pool of IOService driven threads.
+class HttpThreadPool {
+public:
+ /// @brief Describes the possible operational state of the thread pool.
+ enum class State {
+ STOPPED, /// Pool is not operational.
+ RUNNING, /// Pool is populated with running threads.
+ PAUSED, /// Pool is populated with threads that are paused.
+ };
+
+ /// @brief Constructor
+ ///
+ /// @param io_service IOService that will drive the pool's IO. If empty, it
+ /// create its own instance.
+ /// @param pool_size Maximum number of threads in the pool. Currently the
+ /// number of threads is fixed at this value.
+ /// @param defer_start If true, creation of the threads is deferred until
+ /// a subsequent call to @ref run(). In this case the pool's operational
+ /// state post construction is STOPPED. If false, the constructor will
+ /// invoke run() to transition the pool into the RUNNING state.
+ HttpThreadPool(asiolink::IOServicePtr io_service, size_t pool_size,
+ bool defer_start = false);
+
+ /// @brief Destructor
+ ///
+ /// Ensures the thread pool is stopped prior to destruction.
+ ~HttpThreadPool();
+
+ /// @brief Transitions the pool from STOPPED or PAUSED to RUNNING.
+ ///
+ /// When called from the STOPPED state, the pool threads are created and
+ /// begin processing events.
+ /// When called from the PAUSED state, the pool threads are released
+ /// from PAUSED and resume processing events.
+ /// Has no effect if the pool is already in the RUNNING state.
+ void run();
+
+ /// @brief Transitions the pool from RUNNING to PAUSED.
+ ///
+ /// Pool threads suspend event processing and pause until they
+ /// are released to either resume running or stop.
+ /// Has no effect if the pool is already in the PAUSED or STOPPED
+ /// state.
+ void pause();
+
+ /// @brief Transitions the pool from RUNNING or PAUSED to STOPPED.
+ ///
+ /// Stops thread event processing and then destroys the pool's threads
+ /// Has no effect if the pool is already in the STOPPED state.
+ void stop();
+
+ /// @brief Check if the thread pool is running.
+ ///
+ /// @return True if the thread pool is running, false otherwise.
+ bool isRunning() {
+ return (getState() == State::RUNNING);
+ }
+
+ /// @brief Check if the thread pool is paused.
+ ///
+ /// @return True if the thread pool is paused, false otherwise.
+ bool isPaused() {
+ return (getState() == State::PAUSED);
+ }
+
+ /// @brief Check if the thread pool is stopped.
+ ///
+ /// @return True if the thread pool is stopped, false otherwise.
+ bool isStopped() {
+ return (getState() == State::STOPPED);
+ }
+
+ /// @brief Check current thread permissions to transition to the new PAUSED
+ /// state.
+ ///
+ /// This function throws @ref MultiThreadingInvalidOperation if the calling
+ /// thread is one of the worker threads. This would prevent a dead-lock if
+ /// the calling thread would try to perform a thread pool state transition
+ /// to PAUSED state.
+ ///
+ /// @throw MultiThreadingInvalidOperation if the state transition is done on
+ /// any of the worker threads.
+ void checkPausePermissions();
+
+private:
+ /// @brief Check current thread permissions to transition to the new state.
+ ///
+ /// This function throws @ref MultiThreadingInvalidOperation if the calling
+ /// thread is one of the worker threads. This would prevent a dead-lock if
+ /// the calling thread would try to perform a thread pool state transition.
+ ///
+ /// @param state The new transition state for the pool.
+ /// @throw MultiThreadingInvalidOperation if the state transition is done on
+ /// any of the worker threads.
+ void checkPermissions(State state);
+
+ /// @brief Check specified thread id against own threads.
+ ///
+ /// @return true if thread is owned, false otherwise.
+ bool checkThreadId(std::thread::id id);
+
+ /// @brief Thread-safe change of the pool's run state.
+ ///
+ /// Transitions a pool from one run state to another:
+ ///
+ /// When moving from STOPPED or PAUSED to RUNNING:
+ /// -# Sets state to RUNNING.
+ /// -# Notifies threads of state change.
+ /// -# Restarts the IOService.
+ /// -# Creates the threads if they do not yet exist (true only
+ /// when transitioning from STOPPED).
+ /// -# Waits until all threads are running.
+ /// -# Sets the count of exited threads to 0.
+ /// -# Returns to caller.
+ ///
+ /// When moving from RUNNING or PAUSED to STOPPED:
+ /// -# Sets state to STOPPED
+ /// -# Notifies threads of state change.
+ /// -# Polls the IOService to flush handlers.
+ /// -# Stops the IOService.
+ /// -# Waits until all threads have exited the work function.
+ /// -# Joins and destroys the threads.
+ /// -# Returns to caller.
+ ///
+ /// When moving from RUNNING to PAUSED:
+ /// -# Sets state to PAUSED
+ /// -# Notifies threads of state change.
+ /// -# Polls the IOService to flush handlers.
+ /// -# Stops the IOService.
+ /// -# Waits until all threads have paused.
+ /// -# Returns to caller.
+ ///
+ /// @param state The new transition state for the pool.
+ /// @throw MultiThreadingInvalidOperation if the state transition is done on
+ /// any of the worker threads.
+ void setState(State state);
+
+ /// @brief Thread-safe fetch of the pool's operational state.
+ ///
+ /// @return Thread pool state.
+ State getState();
+
+ /// @brief Validates whether the pool can change to a given state.
+ ///
+ /// @param state new state for the pool.
+ /// @return true if the change is valid, false otherwise.
+ /// @note Must be called from a thread-safe context.
+ bool validateStateChange(State state) const;
+
+ /// @brief Text representation of a given state.
+ ///
+ /// @param state The state for the pool.
+ /// @return The text representation of a given state.
+ static std::string stateToText(State state);
+
+ /// @brief Work function executed by each thread in the pool.
+ ///
+ /// Implements the run state responsibilities for a given thread.
+ /// It executes a run loop until the pool is stopped. At the top
+ /// of each iteration of the loop the pool's run state is checked
+ /// and when it is:
+ ///
+ /// RUNNING:
+ /// -# The count of threads running is incremented.
+ /// -# If the count has reached the number of threads in pool the
+ /// main thread is notified.
+ /// -# IOService::run() is invoked.
+ /// -# When IOService::run() returns, the count of threads running
+ /// is decremented.
+ ///
+ /// PAUSED:
+ /// -# The count of threads paused is incremented.
+ /// -# If the count has reached the number of threads in pool the
+ /// main thread is notified.
+ /// -# Thread blocks until notified the pool's run state is no
+ /// longer PAUSED.
+ /// -# The count of threads paused is decremented.
+ ///
+ /// STOPPED:
+ /// -# The run loop is exited.
+ /// -# The count of threads exited is incremented.
+ /// -# If the count has reached the number of threads in pool the
+ /// main thread is notified.
+ /// -# The function exits.
+ void threadWork();
+
+public:
+ /// @brief Fetches the IOService that drives the pool.
+ ///
+ /// @return the pointer to the IOService.
+ asiolink::IOServicePtr getIOService() const;
+
+ /// @brief Fetches the maximum size of the thread pool.
+ ///
+ /// @return the maximum size of the thread pool.
+ uint16_t getPoolSize() const;
+
+ /// @brief Fetches the number of threads in the pool.
+ ///
+ /// @return the number of running threads.
+ uint16_t getThreadCount() const;
+
+private:
+ /// @brief Maximum number of threads in the thread pool.
+ size_t pool_size_;
+
+ /// @brief Pointer to private IOService used in multi-threaded mode.
+ asiolink::IOServicePtr io_service_;
+
+ /// @brief Tracks the operational state of the pool.
+ State run_state_;
+
+ /// @brief Mutex to protect the internal state.
+ std::mutex mutex_;
+
+ /// @brief Condition variable used by threads for synchronization.
+ std::condition_variable thread_cv_;
+
+ /// @brief Condition variable used by main thread to wait on threads
+ /// state transitions.
+ std::condition_variable main_cv_;
+
+ /// @brief Number of threads currently paused.
+ size_t paused_;
+
+ /// @brief Number of threads currently running.
+ size_t running_;
+
+ /// @brief Number of threads that have exited the work function.
+ size_t exited_;
+
+ /// @brief Pool of threads used to service connections in multi-threaded
+ /// mode.
+ std::list<boost::shared_ptr<std::thread> > threads_;
+};
+
+/// @brief Defines a pointer to a thread pool.
+typedef boost::shared_ptr<HttpThreadPool> HttpThreadPoolPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/http_types.h b/src/lib/http/http_types.h
new file mode 100644
index 0000000..68d1c09
--- /dev/null
+++ b/src/lib/http/http_types.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_TYPES_H
+#define HTTP_TYPES_H
+
+namespace isc {
+namespace http {
+
+/// @brief HTTP protocol version.
+struct HttpVersion {
+ unsigned major_; ///< Major HTTP version.
+ unsigned minor_; ///< Minor HTTP version.
+
+ /// @brief Constructor.
+ ///
+ /// @param major Major HTTP version.
+ /// @param minor Minor HTTP version.
+ explicit HttpVersion(const unsigned major, const unsigned minor)
+ : major_(major), minor_(minor) {
+ }
+
+ /// @brief Operator less.
+ ///
+ /// @param rhs Version to compare to.
+ bool operator<(const HttpVersion& rhs) const {
+ return ((major_ < rhs.major_) ||
+ ((major_ == rhs.major_) && (minor_ < rhs.minor_)));
+ }
+
+ /// @brief Operator equal.
+ ///
+ /// @param rhs Version to compare to.
+ bool operator==(const HttpVersion& rhs) const {
+ return ((major_ == rhs.major_) && (minor_ == rhs.minor_));
+ }
+
+ /// @brief Operator not equal.
+ ///
+ /// @param rhs Version to compare to.
+ bool operator!=(const HttpVersion& rhs) const {
+ return (!operator==(rhs));
+ }
+
+ /// @name Methods returning @c HttpVersion object encapsulating typical
+ /// HTTP version numbers.
+ //@{
+
+ /// @brief HTTP version 1.0.
+ static const HttpVersion& HTTP_10() {
+ static HttpVersion ver(1, 0);
+ return (ver);
+ };
+
+ /// @brief HTTP version 1.1.
+ static const HttpVersion& HTTP_11() {
+ static HttpVersion ver(1, 1);
+ return (ver);
+ }
+
+ /// @brief HTTP version 2.0.
+ static const HttpVersion& HTTP_20() {
+ static HttpVersion ver(2, 0);
+ return (ver);
+ }
+
+ //@}
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/listener.cc b/src/lib/http/listener.cc
new file mode 100644
index 0000000..2e6d2e1
--- /dev/null
+++ b/src/lib/http/listener.cc
@@ -0,0 +1,56 @@
+// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <http/listener.h>
+#include <http/listener_impl.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace http {
+
+HttpListener::HttpListener(IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const HttpListener::RequestTimeout& request_timeout,
+ const HttpListener::IdleTimeout& idle_timeout)
+ : impl_(new HttpListenerImpl(io_service, server_address, server_port,
+ tls_context, creator_factory,
+ request_timeout.value_,
+ idle_timeout.value_)) {
+}
+
+HttpListener::~HttpListener() {
+ stop();
+}
+
+IOAddress
+HttpListener::getLocalAddress() const {
+ return (impl_->getEndpoint().getAddress());
+}
+
+uint16_t
+HttpListener::getLocalPort() const {
+ return (impl_->getEndpoint().getPort());
+}
+
+void
+HttpListener::start() {
+ impl_->start();
+}
+
+void
+HttpListener::stop() {
+ impl_->stop();
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/listener.h b/src/lib/http/listener.h
new file mode 100644
index 0000000..8965f29
--- /dev/null
+++ b/src/lib/http/listener.h
@@ -0,0 +1,147 @@
+// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_LISTENER_H
+#define HTTP_LISTENER_H
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/crypto_tls.h>
+#include <exceptions/exceptions.h>
+#include <http/response_creator_factory.h>
+#include <boost/shared_ptr.hpp>
+#include <cstdint>
+
+namespace isc {
+namespace http {
+
+/// @brief A generic error raised by the @ref HttpListener class.
+class HttpListenerError : public Exception {
+public:
+ HttpListenerError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief HttpListener implementation.
+class HttpListenerImpl;
+
+/// @brief HTTP listener.
+///
+/// This class is an entry point to the use of HTTP services in Kea.
+/// It creates a transport acceptor service on the specified address and
+/// port and listens to the incoming HTTP connections. The constructor
+/// receives a pointer to the implementation of the
+/// @ref HttpResponseCreatorFactory, which is used by the @ref HttpListener
+/// to create/retrieve an instance of the @ref HttpResponseCreator when the
+/// new HTTP response needs to be generated. The @ref HttpResponseCreator
+/// creates an object derived from the @ref HttpResponse class, encapsulating
+/// a HTTP response following some specific rules, e.g. having
+/// "application/json" content type.
+///
+/// When the listener is started it creates an instance of a @ref HttpConnection
+/// and stores them in the pool of active connections. The @ref HttpConnection
+/// is responsible for managing the next connection received and receiving the
+/// HTTP request and sending appropriate response. The listener can handle
+/// many HTTP connections simultaneously.
+///
+/// When the @ref HttpListener::stop is invoked, all active connections are
+/// closed and the listener stops accepting new connections.
+class HttpListener {
+public:
+
+ /// @brief HTTP request timeout value.
+ struct RequestTimeout {
+ /// @brief Constructor.
+ ///
+ /// @param value Request timeout value in milliseconds.
+ explicit RequestTimeout(long value)
+ : value_(value) {
+ }
+ long value_; ///< Request timeout value specified.
+ };
+
+ /// @brief Idle connection timeout.
+ struct IdleTimeout {
+ /// @brief Constructor.
+ ///
+ /// @param value Connection idle timeout value in milliseconds.
+ explicit IdleTimeout(long value)
+ : value_(value) {
+ }
+ long value_; ///< Connection idle timeout value specified.
+ };
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new server endpoint using the specified IP
+ /// address and port. It also validates other specified parameters.
+ ///
+ /// This constructor does not start accepting new connections! To start
+ /// accepting connections run @ref HttpListener::start.
+ ///
+ /// @param io_service IO service to be used by the listener.
+ /// @param server_address Address on which the HTTP service should run.
+ /// @param server_port Port number on which the HTTP service should run.
+ /// @param tls_context TLS context.
+ /// @param creator_factory Pointer to the caller-defined
+ /// @ref HttpResponseCreatorFactory derivation which should be used to
+ /// create @ref HttpResponseCreator instances.
+ /// @param request_timeout Timeout after which the HTTP Request Timeout
+ /// is generated.
+ /// @param idle_timeout Timeout after which an idle persistent HTTP
+ /// connection is closed by the server.
+ ///
+ /// @throw HttpListenerError when any of the specified parameters is
+ /// invalid.
+ HttpListener(asiolink::IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const asiolink::TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const RequestTimeout& request_timeout,
+ const IdleTimeout& idle_timeout);
+
+ /// @brief Destructor.
+ ///
+ /// Stops all active connections and closes transport acceptor service.
+ ~HttpListener();
+
+ /// @brief Returns local address on which server is listening.
+ asiolink::IOAddress getLocalAddress() const;
+
+ /// @brief Returns local port on which server is listening.
+ uint16_t getLocalPort() const;
+
+ /// @brief Starts accepting new connections.
+ ///
+ /// This method starts accepting and handling new HTTP connections on
+ /// the IP address and port number specified in the constructor.
+ ///
+ /// If the method is invoked successfully, it must not be invoked again
+ /// until @ref HttpListener::stop is called.
+ ///
+ /// @throw HttpListenerError if an error occurred.
+ void start();
+
+ /// @brief Stops all active connections and shuts down the service.
+ void stop();
+
+protected:
+
+ /// @brief Pointer to the implementation of the @ref HttpListener.
+ boost::shared_ptr<HttpListenerImpl> impl_;
+};
+
+/// @brief Pointer to the @ref HttpListener.
+typedef boost::shared_ptr<HttpListener> HttpListenerPtr;
+
+/// @brief Pointer to the const @ref HttpListener.
+typedef boost::shared_ptr<const HttpListener> ConstHttpListenerPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/listener_impl.cc b/src/lib/http/listener_impl.cc
new file mode 100644
index 0000000..fdcbdd0
--- /dev/null
+++ b/src/lib/http/listener_impl.cc
@@ -0,0 +1,125 @@
+// Copyright (C) 2019-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <http/connection_pool.h>
+#include <http/listener.h>
+#include <http/listener_impl.h>
+
+using namespace isc::asiolink;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace http {
+
+HttpListenerImpl::HttpListenerImpl(IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const long request_timeout,
+ const long idle_timeout)
+ : io_service_(io_service), tls_context_(tls_context), acceptor_(),
+ endpoint_(), connections_(),
+ creator_factory_(creator_factory),
+ request_timeout_(request_timeout), idle_timeout_(idle_timeout) {
+ // Create the TCP or TLS acceptor.
+ if (!tls_context) {
+ acceptor_.reset(new HttpAcceptor(io_service));
+ } else {
+ acceptor_.reset(new HttpsAcceptor(io_service));
+ }
+
+ // Try creating an endpoint. This may cause exceptions.
+ try {
+ endpoint_.reset(new TCPEndpoint(server_address, server_port));
+
+ } catch (...) {
+ isc_throw(HttpListenerError, "unable to create TCP endpoint for "
+ << server_address << ":" << server_port);
+ }
+
+ // The factory must not be null.
+ if (!creator_factory_) {
+ isc_throw(HttpListenerError, "HttpResponseCreatorFactory must not"
+ " be null");
+ }
+
+ // Request timeout is signed and must be greater than 0.
+ if (request_timeout_ <= 0) {
+ isc_throw(HttpListenerError, "Invalid desired HTTP request timeout "
+ << request_timeout_);
+ }
+
+ // Idle persistent connection timeout is signed and must be greater than 0.
+ if (idle_timeout_ <= 0) {
+ isc_throw(HttpListenerError, "Invalid desired HTTP idle persistent connection"
+ " timeout " << idle_timeout_);
+ }
+}
+
+const TCPEndpoint&
+HttpListenerImpl::getEndpoint() const {
+ return (*endpoint_);
+}
+
+void
+HttpListenerImpl::start() {
+ try {
+ acceptor_->open(*endpoint_);
+ acceptor_->setOption(HttpAcceptor::ReuseAddress(true));
+ acceptor_->bind(*endpoint_);
+ acceptor_->listen();
+
+ } catch (const boost::system::system_error& ex) {
+ stop();
+ isc_throw(HttpListenerError, "unable to setup TCP acceptor for "
+ "listening to the incoming HTTP requests: " << ex.what());
+ }
+
+ accept();
+}
+
+void
+HttpListenerImpl::stop() {
+ connections_.stopAll();
+ acceptor_->close();
+}
+
+void
+HttpListenerImpl::accept() {
+ // In some cases we may need HttpResponseCreator instance per connection.
+ // But, the factory may also return the same instance each time. It
+ // depends on the use case.
+ HttpResponseCreatorPtr response_creator = creator_factory_->create();
+ HttpAcceptorCallback acceptor_callback =
+ std::bind(&HttpListenerImpl::acceptHandler, this, ph::_1);
+ HttpConnectionPtr conn = createConnection(response_creator,
+ acceptor_callback);
+ // Add this new connection to the pool.
+ connections_.start(conn);
+}
+
+void
+HttpListenerImpl::acceptHandler(const boost::system::error_code&) {
+ // The new connection has arrived. Set the acceptor to continue
+ // accepting new connections.
+ accept();
+}
+
+HttpConnectionPtr
+HttpListenerImpl::createConnection(const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback) {
+ HttpConnectionPtr
+ conn(new HttpConnection(io_service_, acceptor_, tls_context_,
+ connections_, response_creator, callback,
+ request_timeout_, idle_timeout_));
+ return (conn);
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/listener_impl.h b/src/lib/http/listener_impl.h
new file mode 100644
index 0000000..4ad1960
--- /dev/null
+++ b/src/lib/http/listener_impl.h
@@ -0,0 +1,136 @@
+// Copyright (C) 2019-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_LISTENER_IMPL_H
+#define HTTP_LISTENER_IMPL_H
+
+#include <asiolink/io_service.h>
+#include <asiolink/io_address.h>
+#include <asiolink/tcp_endpoint.h>
+#include <http/connection.h>
+#include <http/connection_pool.h>
+#include <http/response_creator_factory.h>
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+/// @brief Implementation of the @ref HttpListener.
+class HttpListenerImpl {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new server endpoint using the specified IP
+ /// address and port. It also validates other specified parameters.
+ ///
+ /// This constructor does not start accepting new connections! To start
+ /// accepting connections run @ref HttpListener::start.
+ ///
+ /// @param io_service IO service to be used by the listener.
+ /// @param server_address Address on which the HTTP service should run.
+ /// @param server_port Port number on which the HTTP service should run.
+ /// @param tls_context TLS context.
+ /// @param creator_factory Pointer to the caller-defined
+ /// @ref HttpResponseCreatorFactory derivation which should be used to
+ /// create @ref HttpResponseCreator instances.
+ /// @param request_timeout Timeout after which the HTTP Request Timeout
+ /// is generated.
+ /// @param idle_timeout Timeout after which an idle persistent HTTP
+ /// connection is closed by the server.
+ ///
+ /// @throw HttpListenerError when any of the specified parameters is
+ /// invalid.
+ HttpListenerImpl(asiolink::IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const asiolink::TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const long request_timeout,
+ const long idle_timeout);
+
+ /// @brief Virtual destructor.
+ virtual ~HttpListenerImpl() {
+ }
+
+ /// @brief Returns reference to the current listener endpoint.
+ const asiolink::TCPEndpoint& getEndpoint() const;
+
+ /// @brief Starts accepting new connections.
+ ///
+ /// This method starts accepting and handling new HTTP connections on
+ /// the IP address and port number specified in the constructor.
+ ///
+ /// If the method is invoked successfully, it must not be invoked again
+ /// until @ref HttpListener::stop is called.
+ ///
+ /// @throw HttpListenerError if an error occurred.
+ void start();
+
+ /// @brief Stops all active connections and shuts down the service.
+ void stop();
+
+protected:
+
+ /// @brief Creates @ref HttpConnection instance and adds it to the
+ /// pool of active connections.
+ ///
+ /// The next accepted connection will be handled by this instance.
+ void accept();
+
+ /// @brief Callback invoked when the new connection is accepted.
+ ///
+ /// It calls @c HttpListener::accept to create new @c HttpConnection
+ /// instance.
+ ///
+ /// @param ec Error code passed to the handler. This is currently ignored.
+ void acceptHandler(const boost::system::error_code& ec);
+
+ /// @brief Creates an instance of the @c HttpConnection.
+ ///
+ /// This method is virtual so as it can be overridden when customized
+ /// connections are to be used, e.g. in case of unit testing.
+ ///
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ ///
+ /// @return Pointer to the created connection.
+ virtual HttpConnectionPtr createConnection(const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback);
+
+ /// @brief Reference to the IO service.
+ asiolink::IOService& io_service_;
+
+ /// @brief TLS context.
+ asiolink::TlsContextPtr tls_context_;
+
+ /// @brief Acceptor instance.
+ HttpAcceptorPtr acceptor_;
+
+ /// @brief Pointer to the endpoint representing IP address and port on
+ /// which the service is running.
+ boost::scoped_ptr<asiolink::TCPEndpoint> endpoint_;
+
+ /// @brief Pool of active connections.
+ HttpConnectionPool connections_;
+
+ /// @brief Pointer to the @ref HttpResponseCreatorFactory.
+ HttpResponseCreatorFactoryPtr creator_factory_;
+
+ /// @brief Timeout for HTTP Request Timeout desired.
+ long request_timeout_;
+
+ /// @brief Timeout after which idle persistent connection is closed by
+ /// the server.
+ long idle_timeout_;
+};
+
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // HTTP_LISTENER_IMPL_H
diff --git a/src/lib/http/post_request.cc b/src/lib/http/post_request.cc
new file mode 100644
index 0000000..1658985
--- /dev/null
+++ b/src/lib/http/post_request.cc
@@ -0,0 +1,33 @@
+// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/post_request.h>
+
+namespace isc {
+namespace http {
+
+PostHttpRequest::PostHttpRequest()
+ : HttpRequest() {
+ requireHttpMethod(Method::HTTP_POST);
+ requireHeader("Content-Length");
+ requireHeader("Content-Type");
+}
+
+PostHttpRequest::PostHttpRequest(const Method& method, const std::string& uri,
+ const HttpVersion& version,
+ const HostHttpHeader& host_header,
+ const BasicHttpAuthPtr& basic_auth)
+ : HttpRequest(method, uri, version, host_header, basic_auth) {
+ requireHttpMethod(Method::HTTP_POST);
+ requireHeader("Content-Length");
+ requireHeader("Content-Type");
+}
+
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/post_request.h b/src/lib/http/post_request.h
new file mode 100644
index 0000000..095fdab
--- /dev/null
+++ b/src/lib/http/post_request.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_POST_REQUEST_H
+#define HTTP_POST_REQUEST_H
+
+#include <http/request.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+class PostHttpRequest;
+
+/// @brief Pointer to @ref PostHttpRequest.
+typedef boost::shared_ptr<PostHttpRequest> PostHttpRequestPtr;
+/// @brief Pointer to const @ref PostHttpRequest.
+typedef boost::shared_ptr<const PostHttpRequest> ConstPostHttpRequestPtr;
+
+/// @brief Represents HTTP POST request.
+///
+/// Instructs the parent class to require:
+/// - HTTP POST message type,
+/// - Content-Length header,
+/// - Content-Type header.
+class PostHttpRequest : public HttpRequest {
+public:
+
+ /// @brief Constructor for inbound HTTP request.
+ PostHttpRequest();
+
+ /// @brief Constructor for outbound HTTP request.
+ ///
+ /// @param method HTTP method, e.g. POST.
+ /// @param uri URI.
+ /// @param version HTTP version.
+ /// @param host_header Host header to be included in the request. The default
+ /// is the empty Host header.
+ /// @param basic_auth Basic HTTP authentication credential. The default
+ /// is no authentication.
+ PostHttpRequest(const Method& method, const std::string& uri, const HttpVersion& version,
+ const HostHttpHeader& host_header = HostHttpHeader(),
+ const BasicHttpAuthPtr& basic_auth = BasicHttpAuthPtr());
+};
+
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/post_request_json.cc b/src/lib/http/post_request_json.cc
new file mode 100644
index 0000000..909a901
--- /dev/null
+++ b/src/lib/http/post_request_json.cc
@@ -0,0 +1,102 @@
+// Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/post_request_json.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace http {
+
+PostHttpRequestJson::PostHttpRequestJson()
+ : PostHttpRequest(), json_() {
+ requireHeaderValue("Content-Type", "application/json");
+}
+
+PostHttpRequestJson::PostHttpRequestJson(const Method& method, const std::string& uri,
+ const HttpVersion& version,
+ const HostHttpHeader& host_header,
+ const BasicHttpAuthPtr& basic_auth)
+ : PostHttpRequest(method, uri, version, host_header, basic_auth) {
+ requireHeaderValue("Content-Type", "application/json");
+ context()->headers_.push_back(HttpHeaderContext("Content-Type", "application/json"));
+}
+
+
+void
+PostHttpRequestJson::finalize() {
+ if (!created_) {
+ create();
+ }
+
+ // Parse JSON body and store.
+ parseBodyAsJson();
+ finalized_ = true;
+}
+
+void
+PostHttpRequestJson::reset() {
+ PostHttpRequest::reset();
+ json_.reset();
+}
+
+ConstElementPtr
+PostHttpRequestJson::getBodyAsJson() const {
+ checkFinalized();
+ return (json_);
+}
+
+void
+PostHttpRequestJson::setBodyAsJson(const data::ConstElementPtr& body) {
+ if (body) {
+ context_->body_ = body->str();
+ json_ = body;
+
+ } else {
+ context_->body_.clear();
+ }
+}
+
+ConstElementPtr
+PostHttpRequestJson::getJsonElement(const std::string& element_name) const {
+ try {
+ ConstElementPtr body = getBodyAsJson();
+ if (body) {
+ const std::map<std::string, ConstElementPtr>& map_value = body->mapValue();
+ auto map_element = map_value.find(element_name);
+ if (map_element != map_value.end()) {
+ return (map_element->second);
+ }
+ }
+
+ } catch (const std::exception& ex) {
+ isc_throw(HttpRequestJsonError, "unable to get JSON element "
+ << element_name << ": " << ex.what());
+ }
+ return (ConstElementPtr());
+}
+
+void
+PostHttpRequestJson::parseBodyAsJson() {
+ try {
+ // Only parse the body if it hasn't been parsed yet.
+ if (!json_ && !context_->body_.empty()) {
+ ElementPtr json = Element::fromJSON(context_->body_);
+ if (!remote_.empty() && (json->getType() == Element::map)) {
+ json->set("remote-address", Element::create(remote_));
+ }
+ json_ = json;
+ }
+ } catch (const std::exception& ex) {
+ isc_throw(HttpRequestJsonError, "unable to parse the body of the HTTP"
+ " request: " << ex.what());
+ }
+}
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/post_request_json.h b/src/lib/http/post_request_json.h
new file mode 100644
index 0000000..7a2faba
--- /dev/null
+++ b/src/lib/http/post_request_json.h
@@ -0,0 +1,110 @@
+// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_POST_REQUEST_JSON_H
+#define HTTP_POST_REQUEST_JSON_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <http/post_request.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Exception thrown when body of the HTTP message is not JSON.
+class HttpRequestJsonError : public HttpRequestError {
+public:
+ HttpRequestJsonError(const char* file, size_t line, const char* what) :
+ HttpRequestError(file, line, what) { };
+};
+
+class PostHttpRequestJson;
+
+/// @brief Pointer to @ref PostHttpRequestJson.
+typedef boost::shared_ptr<PostHttpRequestJson> PostHttpRequestJsonPtr;
+
+/// @brief Represents HTTP POST request with JSON body.
+///
+/// In addition to the requirements specified by the @ref PostHttpRequest
+/// this class requires that the "Content-Type" is "application/json".
+///
+/// This class provides methods to parse and retrieve JSON data structures.
+class PostHttpRequestJson : public PostHttpRequest {
+public:
+
+ /// @brief Constructor for inbound HTTP request.
+ explicit PostHttpRequestJson();
+
+ /// @brief Constructor for outbound HTTP request.
+ ///
+ /// This constructor adds "Content-Type" header with the value of
+ /// "application/json" to the context.
+ ///
+ /// @param method HTTP method, e.g. POST.
+ /// @param uri URI.
+ /// @param version HTTP version.
+ /// @param host_header Host header to be included in the request. The default
+ /// is the empty Host header.
+ /// @param basic_auth Basic HTTP authentication credential. The default
+ /// is no authentication.
+ explicit PostHttpRequestJson(const Method& method, const std::string& uri,
+ const HttpVersion& version,
+ const HostHttpHeader& host_header = HostHttpHeader(),
+ const BasicHttpAuthPtr& basic_auth = BasicHttpAuthPtr());
+
+ /// @brief Complete parsing of the HTTP request.
+ ///
+ /// This method parses the JSON body into the structure of
+ /// @ref data::ConstElementPtr objects.
+ virtual void finalize();
+
+ /// @brief Reset the state of the object.
+ virtual void reset();
+
+ /// @brief Retrieves JSON body.
+ ///
+ /// @return Pointer to the root element of the JSON structure.
+ /// @throw HttpRequestJsonError if an error occurred.
+ data::ConstElementPtr getBodyAsJson() const;
+
+ /// @brief Sets JSON body for an outbound message.
+ ///
+ /// Note that this method copies the pointer to the body, rather than
+ /// the entire data structure. Thus, the original object should not be
+ /// modified after this method is called. If the specified pointer is
+ /// null, the empty body is set.
+ ///
+ /// @param body JSON structure to be used as a body.
+ void setBodyAsJson(const data::ConstElementPtr& body);
+
+ /// @brief Retrieves a single JSON element.
+ ///
+ /// The element must be at top level of the JSON structure.
+ ///
+ /// @param element_name Element name.
+ ///
+ /// @return Pointer to the specified element or NULL if such element
+ /// doesn't exist.
+ /// @throw HttpRequestJsonError if an error occurred.
+ data::ConstElementPtr getJsonElement(const std::string& element_name) const;
+
+protected:
+
+ /// @brief Interprets body as JSON, which can be later retrieved using
+ /// data element objects.
+ void parseBodyAsJson();
+
+ /// @brief Pointer to the parsed JSON body.
+ data::ConstElementPtr json_;
+
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/request.cc b/src/lib/http/request.cc
new file mode 100644
index 0000000..f753fea
--- /dev/null
+++ b/src/lib/http/request.cc
@@ -0,0 +1,285 @@
+// Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/request.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+#include <sstream>
+
+namespace {
+
+/// @brief New line (CRLF).
+const std::string crlf = "\r\n";
+
+}
+
+namespace isc {
+namespace http {
+
+bool HttpRequest::recordSubject_ = false;
+
+bool HttpRequest::recordIssuer_ = false;
+
+bool HttpRequest::recordBasicAuth_ = false;
+
+HttpRequest::HttpRequest()
+ : HttpMessage(INBOUND), required_methods_(),
+ method_(Method::HTTP_METHOD_UNKNOWN),
+ context_(new HttpRequestContext()),
+ remote_(""), tls_(false), subject_(""), issuer_(""),
+ basic_auth_(""), custom_("") {
+}
+
+HttpRequest::HttpRequest(const Method& method,
+ const std::string& uri,
+ const HttpVersion& version,
+ const HostHttpHeader& host_header,
+ const BasicHttpAuthPtr& basic_auth)
+ : HttpMessage(OUTBOUND), required_methods_(),
+ method_(Method::HTTP_METHOD_UNKNOWN),
+ context_(new HttpRequestContext()),
+ remote_(""), tls_(false), subject_(""), issuer_(""),
+ basic_auth_(""), custom_("") {
+ context()->method_ = methodToString(method);
+ context()->uri_ = uri;
+ context()->http_version_major_ = version.major_;
+ context()->http_version_minor_ = version.minor_;
+ // The Host header is mandatory in HTTP/1.1 and should be placed before
+ // any other headers. We also include it for HTTP/1.0 as it doesn't
+ // harm to include it.
+ context()->headers_.push_back(HttpHeaderContext(host_header.getName(),
+ host_header.getValue()));
+ if (basic_auth) {
+ context()->headers_.push_back(BasicAuthHttpHeaderContext(*basic_auth));
+ }
+}
+
+void
+HttpRequest::requireHttpMethod(const HttpRequest::Method& method) {
+ required_methods_.insert(method);
+}
+
+void
+HttpRequest::create() {
+ try {
+ // The RequestParser doesn't validate the method name. Thus, this
+ // may throw an exception. But, we're fine with lower case names,
+ // e.g. get, post etc.
+ method_ = methodFromString(context_->method_);
+
+ // Check if the method is allowed for this request.
+ if (!inRequiredSet(method_, required_methods_)) {
+ isc_throw(BadValue, "use of HTTP " << methodToString(method_)
+ << " not allowed");
+ }
+
+ http_version_.major_ = context_->http_version_major_;
+ http_version_.minor_ = context_->http_version_minor_;
+
+ // Check if the HTTP version is allowed for this request.
+ if (!inRequiredSet(http_version_, required_versions_)) {
+ isc_throw(BadValue, "use of HTTP version "
+ << http_version_.major_ << "."
+ << http_version_.minor_
+ << " not allowed");
+ }
+
+ // Copy headers from the context.
+ for (auto header = context_->headers_.begin();
+ header != context_->headers_.end();
+ ++header) {
+ HttpHeaderPtr hdr(new HttpHeader(header->name_, header->value_));
+ headers_[hdr->getLowerCaseName()] = hdr;
+ }
+
+ if (getDirection() == HttpMessage::OUTBOUND) {
+ HttpHeaderPtr hdr(new HttpHeader("Content-Length",
+ boost::lexical_cast<std::string>(context_->body_.length())));
+ headers_["content-length"] = hdr;
+ }
+
+ // Iterate over required headers and check that they exist
+ // in the HTTP request.
+ for (auto req_header = required_headers_.begin();
+ req_header != required_headers_.end();
+ ++req_header) {
+ auto header = headers_.find(req_header->first);
+ if (header == headers_.end()) {
+ isc_throw(BadValue, "required header " << req_header->first
+ << " not found in the HTTP request");
+ } else if (!req_header->second->getValue().empty() &&
+ !header->second->isValueEqual(req_header->second->getValue())) {
+ // If specific value is required for the header, check
+ // that the value in the HTTP request matches it.
+ isc_throw(BadValue, "required header's " << header->first
+ << " value is " << req_header->second->getValue()
+ << ", but " << header->second->getValue() << " was found");
+ }
+ }
+
+ } catch (const std::exception& ex) {
+ // Reset the state of the object if we failed at any point.
+ reset();
+ isc_throw(HttpRequestError, ex.what());
+ }
+
+ // All ok.
+ created_ = true;
+}
+
+void
+HttpRequest::finalize() {
+ if (!created_) {
+ create();
+ }
+
+ // Copy the body from the context. Derive classes may further
+ // interpret the body contents, e.g. against the Content-Type.
+ finalized_ = true;
+}
+
+void
+HttpRequest::reset() {
+ created_ = false;
+ finalized_ = false;
+ method_ = HttpRequest::Method::HTTP_METHOD_UNKNOWN;
+ headers_.clear();
+}
+
+HttpRequest::Method
+HttpRequest::getMethod() const {
+ checkCreated();
+ return (method_);
+}
+
+std::string
+HttpRequest::getUri() const {
+ checkCreated();
+ return (context_->uri_);
+}
+
+std::string
+HttpRequest::getBody() const {
+ checkFinalized();
+ return (context_->body_);
+}
+
+std::string
+HttpRequest::toBriefString() const {
+ checkFinalized();
+
+ std::ostringstream s;
+ s << methodToString(getMethod()) << " " << getUri() << " HTTP/" <<
+ getHttpVersion().major_ << "." << getHttpVersion().minor_;
+ return (s.str());
+}
+
+std::string
+HttpRequest::toString() const {
+ checkFinalized();
+
+ std::ostringstream s;
+ // HTTP method, URI and version number.
+ s << toBriefString() << crlf;
+
+ // Host header must go first.
+ HttpHeaderPtr host_header;
+ try {
+ host_header = getHeader("Host");
+ if (host_header) {
+ s << host_header->getName() << ": " << host_header->getValue() << crlf;
+ }
+
+ } catch (...) {
+ // impossible condition
+ }
+
+ // Add all other headers.
+ for (auto header_it = headers_.cbegin(); header_it != headers_.cend();
+ ++header_it) {
+ if (header_it->second->getName() != "Host") {
+ s << header_it->second->getName() << ": " << header_it->second->getValue()
+ << crlf;
+ }
+ }
+
+ s << crlf;
+
+ s << getBody();
+
+ return (s.str());
+}
+
+bool
+HttpRequest::isPersistent() const {
+ HttpHeaderPtr conn;
+
+ try {
+ conn = getHeader("connection");
+
+ } catch (...) {
+ // If there is an exception, it means that the header was not found.
+ }
+
+ std::string conn_value;
+ if (conn) {
+ conn_value = conn->getLowerCaseValue();
+ }
+
+ HttpVersion ver = getHttpVersion();
+
+ return (((ver == HttpVersion::HTTP_10()) && (conn_value == "keep-alive")) ||
+ ((HttpVersion::HTTP_10() < ver) && (conn_value.empty() || (conn_value != "close"))));
+}
+
+HttpRequest::Method
+HttpRequest::methodFromString(std::string method) const {
+ boost::to_upper(method);
+ if (method == "GET") {
+ return (Method::HTTP_GET);
+ } else if (method == "POST") {
+ return (Method::HTTP_POST);
+ } else if (method == "HEAD") {
+ return (Method::HTTP_HEAD);
+ } else if (method == "PUT") {
+ return (Method::HTTP_PUT);
+ } else if (method == "DELETE") {
+ return (Method::HTTP_DELETE);
+ } else if (method == "OPTIONS") {
+ return (Method::HTTP_OPTIONS);
+ } else if (method == "CONNECT") {
+ return (Method::HTTP_CONNECT);
+ } else {
+ isc_throw(HttpRequestError, "unknown HTTP method " << method);
+ }
+}
+
+std::string
+HttpRequest::methodToString(const HttpRequest::Method& method) const {
+ switch (method) {
+ case Method::HTTP_GET:
+ return ("GET");
+ case Method::HTTP_POST:
+ return ("POST");
+ case Method::HTTP_HEAD:
+ return ("HEAD");
+ case Method::HTTP_PUT:
+ return ("PUT");
+ case Method::HTTP_DELETE:
+ return ("DELETE");
+ case Method::HTTP_OPTIONS:
+ return ("OPTIONS");
+ case Method::HTTP_CONNECT:
+ return ("CONNECT");
+ default:
+ return ("unknown HTTP method");
+ }
+}
+
+}
+}
diff --git a/src/lib/http/request.h b/src/lib/http/request.h
new file mode 100644
index 0000000..014e6e4
--- /dev/null
+++ b/src/lib/http/request.h
@@ -0,0 +1,312 @@
+// Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_REQUEST_H
+#define HTTP_REQUEST_H
+
+#include <hooks/callout_handle_associate.h>
+#include <http/basic_auth.h>
+#include <http/http_message.h>
+#include <http/request_context.h>
+#include <boost/shared_ptr.hpp>
+#include <cstdint>
+
+namespace isc {
+namespace http {
+
+/// @brief Generic exception thrown by @ref HttpRequest class.
+class HttpRequestError : public HttpMessageError {
+public:
+ HttpRequestError(const char* file, size_t line, const char* what) :
+ HttpMessageError(file, line, what) { };
+};
+
+class HttpRequest;
+
+/// @brief Pointer to the @ref HttpRequest object.
+typedef boost::shared_ptr<HttpRequest> HttpRequestPtr;
+
+/// @brief Represents HTTP request message.
+///
+/// This derivation of the @c HttpMessage class is specialized to represent
+/// HTTP requests. This class provides two constructors for creating an inbound
+/// and outbound request instance respectively. This class is associated with
+/// an instance of the @c HttpRequestContext, which is used to provide request
+/// specific values, such as: HTTP method, version, URI and headers.
+///
+/// The derivations of this class provide specializations and specify the
+/// HTTP methods, versions and headers supported/required in the specific use
+/// cases. For example, the @c PostHttpRequest class derives from @c HttpRequest
+/// and it requires that request uses POST method. The @c PostHttpRequestJson,
+/// which derives from @c PostHttpRequest requires that the POST message
+/// includes body holding a JSON structure and provides methods to parse the
+/// JSON body.
+///
+/// Objects of this class is used to record some parameters for access control:
+/// - the remote address
+/// - the use of TLS
+/// - the first commonName of the SubjectName of the client certificate
+/// - the first commonName of the IssuerName of the client certificate
+/// - the user ID of the basic HTTP authentication
+/// - a custom value
+///
+/// Callouts are associated to the request.
+class HttpRequest : public HttpMessage, public hooks::CalloutHandleAssociate {
+public:
+
+ /// @brief HTTP methods.
+ enum class Method {
+ HTTP_GET,
+ HTTP_POST,
+ HTTP_HEAD,
+ HTTP_PUT,
+ HTTP_DELETE,
+ HTTP_OPTIONS,
+ HTTP_CONNECT,
+ HTTP_METHOD_UNKNOWN
+ };
+
+ /// @brief Constructor for inbound HTTP request.
+ HttpRequest();
+
+ /// @brief Constructor for outbound HTTP request.
+ ///
+ /// The constructor always includes Host header in the request, regardless
+ /// of the HTTP version used.
+ ///
+ /// @param method HTTP method, e.g. POST.
+ /// @param uri URI.
+ /// @param version HTTP version.
+ /// @param host_header Host header to be included in the request. The default
+ /// is the empty Host header.
+ /// @param basic_auth Basic HTTP authentication credential. The default
+ /// is no authentication.
+ HttpRequest(const Method& method, const std::string& uri, const HttpVersion& version,
+ const HostHttpHeader& host_header = HostHttpHeader(),
+ const BasicHttpAuthPtr& basic_auth = BasicHttpAuthPtr());
+
+ /// @brief Returns pointer to the @ref HttpRequestContext.
+ ///
+ /// The context holds intermediate data for creating a request. The request
+ /// parser stores parsed raw data in the context. When parsing is finished,
+ /// the data are validated and committed into the @c HttpRequest.
+ ///
+ /// @return Pointer to the underlying @ref HttpRequestContext.
+ const HttpRequestContextPtr& context() const {
+ return (context_);
+ }
+
+ /// @brief Returns remote address.
+ ///
+ /// @return remote address from HTTP connection
+ /// getRemote method.
+ std::string getRemote() const {
+ return (remote_);
+ }
+
+ /// @brief Set remote address.
+ ///
+ /// @param remote Remote end-point address in textual form.
+ void setRemote(const std::string& remote) {
+ remote_ = remote;
+ }
+
+ /// @brief Specifies an HTTP method allowed for the request.
+ ///
+ /// Allowed methods must be specified prior to calling @ref create method.
+ /// If no method is specified, all methods are supported.
+ ///
+ /// @param method HTTP method allowed for the request.
+ void requireHttpMethod(const HttpRequest::Method& method);
+
+ /// @brief Commits information held in the context into the request.
+ ///
+ /// This function reads HTTP method, version and headers from the context
+ /// and validates their values. For the outbound messages, it automatically
+ /// appends Content-Length header to the request, based on the length of the
+ /// request body.
+ ///
+ /// @throw HttpRequestError if the parsed request doesn't meet the specified
+ /// requirements for it.
+ virtual void create();
+
+ /// @brief Completes creation of the HTTP request.
+ ///
+ /// This method marks the message as finalized. The outbound request may now be
+ /// sent over the TCP socket. The information from the inbound message may be
+ /// read, including the request body.
+ virtual void finalize();
+
+ /// @brief Reset the state of the object.
+ virtual void reset();
+
+ /// @brief Returns HTTP method of the request.
+ Method getMethod() const;
+
+ /// @brief Returns HTTP request URI.
+ std::string getUri() const;
+
+
+ /// @brief Returns HTTP message body as string.
+ std::string getBody() const;
+
+ /// @brief Returns HTTP method, URI and HTTP version as a string.
+ std::string toBriefString() const;
+
+ /// @brief Returns HTTP message as string.
+ ///
+ /// This method is called to generate the outbound HTTP message. Make
+ /// sure to call @c finalize prior to calling this method.
+ virtual std::string toString() const;
+
+ /// @brief Checks if the client has requested persistent connection.
+ ///
+ /// For the HTTP/1.0 case, the connection is persistent if the client has
+ /// included Connection: keep-alive header. For the HTTP/1.1 case, the
+ /// connection is assumed to be persistent unless Connection: close header
+ /// has been included.
+ ///
+ /// @return true if the client has requested persistent connection, false
+ /// otherwise.
+ bool isPersistent() const;
+
+ /// Access control parameters: get/set methods.
+
+ /// @brief Returns recorded TLS usage.
+ ///
+ /// @return recorded TLS usage.
+ bool getTls() const {
+ return (tls_);
+ }
+
+ /// @brief Set (record) TLS usage.
+ ///
+ /// @param tls the TLS usage.
+ void setTls(bool tls) {
+ tls_ = tls;
+ }
+
+ /// @brief Returns recorded subject name.
+ ///
+ /// @return recorded subject name.
+ std::string getSubject() const {
+ return (subject_);
+ }
+
+ /// @brief Set (record) subject name.
+ ///
+ /// @param subject the subject name.
+ void setSubject(const std::string& subject) {
+ subject_ = subject;
+ }
+
+ /// @brief Returns recorded issuer name.
+ ///
+ /// @return recorded issuer name.
+ std::string getIssuer() const {
+ return (issuer_);
+ }
+
+ /// @brief Set (record) issuer name.
+ ///
+ /// @param issuer the issuer name.
+ void setIssuer(const std::string& issuer) {
+ issuer_ = issuer;
+ }
+
+ /// @brief Returns recorded basic auth.
+ ///
+ /// @return recorded basic auth.
+ std::string getBasicAuth() const {
+ return (basic_auth_);
+ }
+
+ /// @brief Set (record) basic auth.
+ ///
+ /// @param basic_auth the basic auth.
+ void setBasicAuth(const std::string& basic_auth) {
+ basic_auth_ = basic_auth;
+ }
+
+ /// @brief Returns recorded custom name.
+ ///
+ /// @return recorded custom name.
+ std::string getCustom() const {
+ return (custom_);
+ }
+
+ /// @brief Set (record) custom name.
+ ///
+ /// @param custom the custom name.
+ void setCustom(const std::string& custom) {
+ custom_ = custom;
+ }
+
+ /// Access control parameters: Flags which indicate what information to record.
+ /// Remote address and TLS usage are always recorded.
+
+ /// @brief Record subject name.
+ static bool recordSubject_;
+
+ /// @brief Record issuer name.
+ static bool recordIssuer_;
+
+ /// @brief Record basic auth.
+ static bool recordBasicAuth_;
+
+protected:
+
+ /// @brief Converts HTTP method specified in textual format to @ref Method.
+ ///
+ /// @param method HTTP method specified in the textual format. This value
+ /// is case insensitive.
+ ///
+ /// @return HTTP method as enum.
+ /// @throw HttpRequestError if unknown method specified.
+ Method methodFromString(std::string method) const;
+
+ /// @brief Converts HTTP method to string.
+ ///
+ /// @param method HTTP method specified as enum.
+ ///
+ /// @return HTTP method as string.
+ std::string methodToString(const HttpRequest::Method& method) const;
+
+ /// @brief Set of required HTTP methods.
+ ///
+ /// If the set is empty, all methods are allowed.
+ std::set<Method> required_methods_;
+
+ /// @brief HTTP method of the request.
+ Method method_;
+
+ /// @brief Pointer to the @ref HttpRequestContext holding parsed
+ /// data.
+ HttpRequestContextPtr context_;
+
+ /// @brief Remote address.
+ std::string remote_;
+
+ /// @brief TLS usage.
+ bool tls_;
+
+ /// @brief Subject name.
+ std::string subject_;
+
+ /// @brief Issuer name.
+ std::string issuer_;
+
+ /// @brief Basic auth.
+ std::string basic_auth_;
+
+ /// @brief Custom name.
+ std::string custom_;
+};
+
+}
+}
+
+#endif
diff --git a/src/lib/http/request_context.h b/src/lib/http/request_context.h
new file mode 100644
index 0000000..bcbacda
--- /dev/null
+++ b/src/lib/http/request_context.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_REQUEST_CONTEXT_H
+#define HTTP_REQUEST_CONTEXT_H
+
+#include <http/header_context.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace http {
+
+/// @brief HTTP request context.
+///
+/// The context is used by the @ref HttpRequestParser to store parsed
+/// data. This data is later used to create an instance of the
+/// @ref HttpRequest or its derivation.
+struct HttpRequestContext {
+ /// @brief HTTP request method.
+ std::string method_;
+ /// @brief HTTP request URI.
+ std::string uri_;
+ /// @brief HTTP major version number.
+ unsigned int http_version_major_;
+ /// @brief HTTP minor version number.
+ unsigned int http_version_minor_;
+ /// @brief Collection of HTTP headers.
+ std::vector<HttpHeaderContext> headers_;
+ /// @brief HTTP request body.
+ std::string body_;
+};
+
+/// @brief Pointer to the @ref HttpRequestContext.
+typedef boost::shared_ptr<HttpRequestContext> HttpRequestContextPtr;
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/request_parser.cc b/src/lib/http/request_parser.cc
new file mode 100644
index 0000000..d774807
--- /dev/null
+++ b/src/lib/http/request_parser.cc
@@ -0,0 +1,458 @@
+// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/request_parser.h>
+#include <functional>
+#include <iostream>
+
+using namespace isc::util;
+
+namespace isc {
+namespace http {
+
+const int HttpRequestParser::RECEIVE_START_ST;
+const int HttpRequestParser::HTTP_METHOD_ST;
+const int HttpRequestParser::HTTP_URI_ST;
+const int HttpRequestParser::HTTP_VERSION_H_ST;
+const int HttpRequestParser::HTTP_VERSION_T1_ST;
+const int HttpRequestParser::HTTP_VERSION_T2_ST;
+const int HttpRequestParser::HTTP_VERSION_P_ST;
+const int HttpRequestParser::HTTP_VERSION_SLASH_ST;
+const int HttpRequestParser::HTTP_VERSION_MAJOR_START_ST;
+const int HttpRequestParser::HTTP_VERSION_MAJOR_ST;
+const int HttpRequestParser::HTTP_VERSION_MINOR_START_ST;
+const int HttpRequestParser::HTTP_VERSION_MINOR_ST;
+const int HttpRequestParser::EXPECTING_NEW_LINE1_ST;
+const int HttpRequestParser::HEADER_LINE_START_ST;
+const int HttpRequestParser::HEADER_LWS_ST;
+const int HttpRequestParser::HEADER_NAME_ST;
+const int HttpRequestParser::SPACE_BEFORE_HEADER_VALUE_ST;
+const int HttpRequestParser::HEADER_VALUE_ST;
+const int HttpRequestParser::EXPECTING_NEW_LINE2_ST;
+const int HttpRequestParser::EXPECTING_NEW_LINE3_ST;
+const int HttpRequestParser::HTTP_BODY_ST;
+
+HttpRequestParser::HttpRequestParser(HttpRequest& request)
+ : HttpMessageParserBase(request), request_(request),
+ context_(request_.context()) {
+}
+
+void
+HttpRequestParser::initModel() {
+ // Initialize dictionaries of events and states.
+ initDictionaries();
+
+ // Set the current state to starting state and enter the run loop.
+ setState(RECEIVE_START_ST);
+
+ // Parsing starts from here.
+ postNextEvent(START_EVT);
+}
+
+void
+HttpRequestParser::defineStates() {
+ // Call parent class implementation first.
+ HttpMessageParserBase::defineStates();
+
+ // Define HTTP parser specific states.
+ defineState(RECEIVE_START_ST, "RECEIVE_START_ST",
+ std::bind(&HttpRequestParser::receiveStartHandler, this));
+
+ defineState(HTTP_METHOD_ST, "HTTP_METHOD_ST",
+ std::bind(&HttpRequestParser::httpMethodHandler, this));
+
+ defineState(HTTP_URI_ST, "HTTP_URI_ST",
+ std::bind(&HttpRequestParser::uriHandler, this));
+
+ defineState(HTTP_VERSION_H_ST, "HTTP_VERSION_H_ST",
+ std::bind(&HttpRequestParser::versionHTTPHandler, this, 'H',
+ HTTP_VERSION_T1_ST));
+
+ defineState(HTTP_VERSION_T1_ST, "HTTP_VERSION_T1_ST",
+ std::bind(&HttpRequestParser::versionHTTPHandler, this, 'T',
+ HTTP_VERSION_T2_ST));
+
+ defineState(HTTP_VERSION_T2_ST, "HTTP_VERSION_T2_ST",
+ std::bind(&HttpRequestParser::versionHTTPHandler, this, 'T',
+ HTTP_VERSION_P_ST));
+
+ defineState(HTTP_VERSION_P_ST, "HTTP_VERSION_P_ST",
+ std::bind(&HttpRequestParser::versionHTTPHandler, this, 'P',
+ HTTP_VERSION_SLASH_ST));
+
+ defineState(HTTP_VERSION_SLASH_ST, "HTTP_VERSION_SLASH_ST",
+ std::bind(&HttpRequestParser::versionHTTPHandler, this, '/',
+ HTTP_VERSION_MAJOR_ST));
+
+ defineState(HTTP_VERSION_MAJOR_START_ST, "HTTP_VERSION_MAJOR_START_ST",
+ std::bind(&HttpRequestParser::versionNumberStartHandler, this,
+ HTTP_VERSION_MAJOR_ST,
+ &context_->http_version_major_));
+
+ defineState(HTTP_VERSION_MAJOR_ST, "HTTP_VERSION_MAJOR_ST",
+ std::bind(&HttpRequestParser::versionNumberHandler, this,
+ '.', HTTP_VERSION_MINOR_START_ST,
+ &context_->http_version_major_));
+
+ defineState(HTTP_VERSION_MINOR_START_ST, "HTTP_VERSION_MINOR_START_ST",
+ std::bind(&HttpRequestParser::versionNumberStartHandler, this,
+ HTTP_VERSION_MINOR_ST,
+ &context_->http_version_minor_));
+
+ defineState(HTTP_VERSION_MINOR_ST, "HTTP_VERSION_MINOR_ST",
+ std::bind(&HttpRequestParser::versionNumberHandler, this,
+ '\r', EXPECTING_NEW_LINE1_ST,
+ &context_->http_version_minor_));
+
+ defineState(EXPECTING_NEW_LINE1_ST, "EXPECTING_NEW_LINE1_ST",
+ std::bind(&HttpRequestParser::expectingNewLineHandler, this,
+ HEADER_LINE_START_ST));
+
+ defineState(HEADER_LINE_START_ST, "HEADER_LINE_START_ST",
+ std::bind(&HttpRequestParser::headerLineStartHandler, this));
+
+ defineState(HEADER_LWS_ST, "HEADER_LWS_ST",
+ std::bind(&HttpRequestParser::headerLwsHandler, this));
+
+ defineState(HEADER_NAME_ST, "HEADER_NAME_ST",
+ std::bind(&HttpRequestParser::headerNameHandler, this));
+
+ defineState(SPACE_BEFORE_HEADER_VALUE_ST, "SPACE_BEFORE_HEADER_VALUE_ST",
+ std::bind(&HttpRequestParser::spaceBeforeHeaderValueHandler, this));
+
+ defineState(HEADER_VALUE_ST, "HEADER_VALUE_ST",
+ std::bind(&HttpRequestParser::headerValueHandler, this));
+
+ defineState(EXPECTING_NEW_LINE2_ST, "EXPECTING_NEW_LINE2",
+ std::bind(&HttpRequestParser::expectingNewLineHandler, this,
+ HEADER_LINE_START_ST));
+
+ defineState(EXPECTING_NEW_LINE3_ST, "EXPECTING_NEW_LINE3_ST",
+ std::bind(&HttpRequestParser::expectingNewLineHandler, this,
+ HTTP_PARSE_OK_ST));
+
+ defineState(HTTP_BODY_ST, "HTTP_BODY_ST",
+ std::bind(&HttpRequestParser::bodyHandler, this));
+}
+
+void
+HttpRequestParser::receiveStartHandler() {
+ std::string bytes;
+ getNextFromBuffer(bytes);
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch(getNextEvent()) {
+ case START_EVT:
+ // The first byte should contain a first character of the
+ // HTTP method name.
+ if (!isChar(bytes[0]) || isCtl(bytes[0]) || isSpecial(bytes[0])) {
+ parseFailure("invalid first character " + std::string(1, bytes[0]) +
+ " in HTTP method name");
+
+ } else {
+ context_->method_.push_back(bytes[0]);
+ transition(HTTP_METHOD_ST, DATA_READ_OK_EVT);
+ }
+ break;
+
+ default:
+ invalidEventError("receiveStartHandler", getNextEvent());
+ }
+ }
+}
+
+void
+HttpRequestParser::httpMethodHandler() {
+ stateWithReadHandler("httpMethodHandler", [this](const char c) {
+ // Space character terminates the HTTP method name. Next thing
+ // is the URI.
+ if (c == ' ') {
+ transition(HTTP_URI_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " in HTTP method name");
+
+ } else {
+ // Still parsing the method. Append the next character to the
+ // method name.
+ context_->method_.push_back(c);
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::uriHandler() {
+ stateWithReadHandler("uriHandler", [this](const char c) {
+ // Space character terminates the URI.
+ if (c == ' ') {
+ transition(HTTP_VERSION_H_ST, DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in HTTP URI");
+
+ } else {
+ // Still parsing the URI. Append the next character to the
+ // method name.
+ context_->uri_.push_back(c);
+ transition(HTTP_URI_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::versionHTTPHandler(const char expected_letter,
+ const unsigned int next_state) {
+ stateWithReadHandler("versionHTTPHandler",
+ [this, expected_letter, next_state](const char c) {
+ // We're handling one of the letters: 'H', 'T' or 'P'.
+ if (c == expected_letter) {
+ // The HTTP version is specified as "HTTP/X.Y". If the current
+ // character is a slash we're starting to parse major HTTP version
+ // number. Let's reset the version numbers.
+ if (c == '/') {
+ context_->http_version_major_ = 0;
+ context_->http_version_minor_ = 0;
+ }
+ // In all cases, let's transition to next specified state.
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else {
+ // Unexpected character found. Parsing fails.
+ parseFailure("unexpected character " + std::string(1, c) +
+ " in HTTP version string");
+ }
+ });
+}
+
+void
+HttpRequestParser::versionNumberStartHandler(const unsigned int next_state,
+ unsigned int* storage) {
+ stateWithReadHandler("versionNumberStartHandler",
+ [this, next_state, storage](const char c) mutable {
+ // HTTP version number must be a digit.
+ if (isdigit(c)) {
+ // Update the version number using new digit being parsed.
+ *storage = *storage * 10 + c - '0';
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else {
+ parseFailure("expected digit in HTTP version, found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpRequestParser::versionNumberHandler(const char following_character,
+ const unsigned int next_state,
+ unsigned int* const storage) {
+ stateWithReadHandler("versionNumberHandler",
+ [this, following_character, next_state, storage](const char c)
+ mutable {
+ // We're getting to the end of the version number, let's transition
+ // to next state.
+ if (c == following_character) {
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else if (isdigit(c)) {
+ // Current character is a digit, so update the version number.
+ *storage = *storage * 10 + c - '0';
+
+ } else {
+ parseFailure("expected digit in HTTP version, found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpRequestParser::expectingNewLineHandler(const unsigned int next_state) {
+ stateWithReadHandler("expectingNewLineHandler", [this, next_state](const char c) {
+ // Only a new line character is allowed in this state.
+ if (c == '\n') {
+ // If next state is HTTP_PARSE_OK_ST it means that we're
+ // parsing 3rd new line in the HTTP request message. This
+ // terminates the HTTP request (if there is no body) or marks the
+ // beginning of the body.
+ if (next_state == HTTP_PARSE_OK_ST) {
+ // Whether there is a body in this message or not, we should
+ // parse the HTTP headers to validate it and to check if there
+ // is "Content-Length" specified. The "Content-Length" is
+ // required for parsing body.
+ request_.create();
+ try {
+ // This will throw exception if there is no Content-Length.
+ uint64_t content_length =
+ request_.getHeaderValueAsUint64("Content-Length");
+ if (content_length > 0) {
+ // There is body in this request, so let's parse it.
+ transition(HTTP_BODY_ST, DATA_READ_OK_EVT);
+ } else {
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ } catch (const std::exception& ex) {
+ // There is no body in this message. If the body is required
+ // parsing fails.
+ if (request_.requiresBody()) {
+ parseFailure("HTTP message lacks a body");
+
+ } else {
+ // Body not required so simply terminate parsing.
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ }
+
+ } else {
+ // This is 1st or 2nd new line, so let's transition to the
+ // next state required by this handler.
+ transition(next_state, DATA_READ_OK_EVT);
+ }
+ } else {
+ parseFailure("expecting new line after CR, found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpRequestParser::headerLineStartHandler() {
+ stateWithReadHandler("headerLineStartHandler", [this](const char c) {
+ // If we're parsing HTTP headers and we found CR it marks the
+ // end of headers section.
+ if (c == '\r') {
+ transition(EXPECTING_NEW_LINE3_ST, DATA_READ_OK_EVT);
+
+ } else if (!context_->headers_.empty() && ((c == ' ') || (c == '\t'))) {
+ // New line in headers section followed by space or tab is an LWS,
+ // a line break within header value.
+ transition(HEADER_LWS_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " in header name");
+
+ } else {
+ // Update header name with the parse letter.
+ context_->headers_.push_back(HttpHeaderContext());
+ context_->headers_.back().name_.push_back(c);
+ transition(HEADER_NAME_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::headerLwsHandler() {
+ stateWithReadHandler("headerLwsHandler", [this](const char c) {
+ if (c == '\r') {
+ // Found CR during parsing a header value. Next value
+ // should be new line.
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if ((c == ' ') || (c == '\t')) {
+ // Space and tab is used to mark LWS. Simply swallow
+ // this character.
+ transition(getCurrState(), DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header " +
+ context_->headers_.back().name_);
+
+ } else {
+ // We're parsing header value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::headerNameHandler() {
+ stateWithReadHandler("headerNameHandler", [this](const char c) {
+ // Colon follows header name and it has its own state.
+ if (c == ':') {
+ transition(SPACE_BEFORE_HEADER_VALUE_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " found in the HTTP header name");
+
+ } else {
+ // Parsing a header name, so update it.
+ context_->headers_.back().name_.push_back(c);
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::spaceBeforeHeaderValueHandler() {
+ stateWithReadHandler("spaceBeforeHeaderValueHandler", [this](const char c) {
+ if (c == ' ') {
+ // Remove leading whitespace from the header value.
+ transition(getCurrState(), DATA_READ_OK_EVT);
+
+ } else if (c == '\r') {
+ // If CR found during parsing header value, it marks the end
+ // of this value.
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header "
+ + context_->headers_.back().name_);
+
+ } else {
+ // Still parsing the value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::headerValueHandler() {
+ stateWithReadHandler("headerValueHandler", [this](const char c) {
+ // If CR found during parsing header value, it marks the end
+ // of this value.
+ if (c == '\r') {
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header "
+ + context_->headers_.back().name_);
+
+ } else {
+ // Still parsing the value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::bodyHandler() {
+ stateWithMultiReadHandler("bodyHandler", [this](const std::string& body) {
+ // We don't validate the body at this stage. Simply record the
+ // number of characters specified within "Content-Length".
+ context_->body_ += body;
+ size_t content_length = request_.getHeaderValueAsUint64("Content-Length");
+ if (context_->body_.length() < content_length) {
+ transition(HTTP_BODY_ST, DATA_READ_OK_EVT);
+
+ } else {
+ // If there was some extraneous data, ignore it.
+ if (context_->body_.length() > content_length) {
+ context_->body_.resize(content_length);
+ }
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ });
+}
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/request_parser.h b/src/lib/http/request_parser.h
new file mode 100644
index 0000000..4f415d0
--- /dev/null
+++ b/src/lib/http/request_parser.h
@@ -0,0 +1,246 @@
+// Copyright (C) 2016-2018,2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_REQUEST_PARSER_H
+#define HTTP_REQUEST_PARSER_H
+
+#include <http/http_message_parser_base.h>
+#include <http/request.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+class HttpRequestParser;
+
+/// @brief Pointer to the @ref HttpRequestParser.
+typedef boost::shared_ptr<HttpRequestParser> HttpRequestParserPtr;
+
+/// @brief A generic parser for HTTP requests.
+///
+/// This class implements a parser for HTTP requests. The parser derives from
+/// @ref HttpMessageParserBase class and implements its own state machine on
+/// top of it. The states of the parser reflect various parts of the HTTP
+/// message being parsed, e.g. parsing HTTP method, parsing URI, parsing
+/// message body etc. The descriptions of all parser states are provided
+/// below together with the constants defining these states.
+///
+/// The request parser validates the syntax of the received message as it
+/// progresses with parsing the data. Though, it doesn't interpret the received
+/// data until the whole message is parsed. In most cases we want to apply some
+/// restrictions on the message content, e.g. Kea Control API requires that
+/// commands are sent using HTTP POST, with a JSON command being carried in a
+/// message body. The parser doesn't verify if the message meets these
+/// restrictions until the whole message is parsed, i.e. stored in the
+/// @ref HttpRequestContext object. This object is associated with a
+/// @ref HttpRequest object (or its derivation). When the parsing is completed,
+/// the @ref HttpRequest::create method is called to retrieve the data from
+/// the @ref HttpRequestContext and interpret the data. In particular, the
+/// @ref HttpRequest or its derivation checks if the received message meets
+/// desired restrictions.
+///
+/// Kea Control API uses @ref PostHttpRequestJson class (which derives from the
+/// @ref HttpRequest) to interpret received request. This class requires
+/// that the HTTP request uses POST method and contains the following headers:
+/// - Content-Type: application/json,
+/// - Content-Length
+///
+/// If any of these restrictions is not met in the received message, an
+/// exception will be thrown, thereby @ref HttpRequestParser will fail parsing
+/// the message.
+class HttpRequestParser : public HttpMessageParserBase {
+public:
+
+ /// @name States supported by the HttpRequestParser.
+ ///
+ //@{
+
+ /// @brief State indicating a beginning of parsing.
+ static const int RECEIVE_START_ST = SM_DERIVED_STATE_MIN + 1;
+
+ /// @brief Parsing HTTP method, e.g. GET, POST etc.
+ static const int HTTP_METHOD_ST = SM_DERIVED_STATE_MIN + 2;
+
+ /// @brief Parsing URI.
+ static const int HTTP_URI_ST = SM_DERIVED_STATE_MIN + 3;
+
+ /// @brief Parsing letter "H" of "HTTP".
+ static const int HTTP_VERSION_H_ST = SM_DERIVED_STATE_MIN + 4;
+
+ /// @brief Parsing first occurrence of "T" in "HTTP".
+ static const int HTTP_VERSION_T1_ST = SM_DERIVED_STATE_MIN + 5;
+
+ /// @brief Parsing second occurrence of "T" in "HTTP".
+ static const int HTTP_VERSION_T2_ST = SM_DERIVED_STATE_MIN + 6;
+
+ /// @brief Parsing letter "P" in "HTTP".
+ static const int HTTP_VERSION_P_ST = SM_DERIVED_STATE_MIN + 7;
+
+ /// @brief Parsing slash character in "HTTP/Y.X"
+ static const int HTTP_VERSION_SLASH_ST = SM_DERIVED_STATE_MIN + 8;
+
+ /// @brief Starting to parse major HTTP version number.
+ static const int HTTP_VERSION_MAJOR_START_ST = SM_DERIVED_STATE_MIN + 9;
+
+ /// @brief Parsing major HTTP version number.
+ static const int HTTP_VERSION_MAJOR_ST = SM_DERIVED_STATE_MIN + 10;
+
+ /// @brief Starting to parse minor HTTP version number.
+ static const int HTTP_VERSION_MINOR_START_ST = SM_DERIVED_STATE_MIN + 11;
+
+ /// @brief Parsing minor HTTP version number.
+ static const int HTTP_VERSION_MINOR_ST = SM_DERIVED_STATE_MIN + 12;
+
+ /// @brief Parsing first new line (after HTTP version number).
+ static const int EXPECTING_NEW_LINE1_ST = SM_DERIVED_STATE_MIN + 13;
+
+ /// @brief Starting to parse a header line.
+ static const int HEADER_LINE_START_ST = SM_DERIVED_STATE_MIN + 14;
+
+ /// @brief Parsing LWS (Linear White Space), i.e. new line with a space
+ /// or tab character while parsing a HTTP header.
+ static const int HEADER_LWS_ST = SM_DERIVED_STATE_MIN + 15;
+
+ /// @brief Parsing header name.
+ static const int HEADER_NAME_ST = SM_DERIVED_STATE_MIN + 16;
+
+ /// @brief Parsing space before header value.
+ static const int SPACE_BEFORE_HEADER_VALUE_ST = SM_DERIVED_STATE_MIN + 17;
+
+ /// @brief Parsing header value.
+ static const int HEADER_VALUE_ST = SM_DERIVED_STATE_MIN + 18;
+
+ /// @brief Expecting new line after parsing header value.
+ static const int EXPECTING_NEW_LINE2_ST = SM_DERIVED_STATE_MIN + 19;
+
+ /// @brief Expecting second new line marking end of HTTP headers.
+ static const int EXPECTING_NEW_LINE3_ST = SM_DERIVED_STATE_MIN + 20;
+
+ /// @brief Parsing body of a HTTP message.
+ static const int HTTP_BODY_ST = SM_DERIVED_STATE_MIN + 21;
+
+ //@}
+
+
+ /// @brief Constructor.
+ ///
+ /// Creates new instance of the parser.
+ ///
+ /// @param request Reference to the @ref HttpRequest object or its
+ /// derivation that should be used to validate the parsed request and
+ /// to be used as a container for the parsed request.
+ explicit HttpRequestParser(HttpRequest& request);
+
+ /// @brief Initialize the state model for parsing.
+ ///
+ /// This method must be called before parsing the request, i.e. before
+ /// calling @ref HttpRequestParser::poll. It initializes dictionaries of
+ /// states and events, and sets the initial model state to RECEIVE_START_ST.
+ void initModel();
+
+private:
+
+ /// @brief Defines states of the parser.
+ virtual void defineStates();
+
+ /// @name State handlers.
+ ///
+ //@{
+
+ /// @brief Handler for RECEIVE_START_ST.
+ void receiveStartHandler();
+
+ /// @brief Handler for HTTP_METHOD_ST.
+ void httpMethodHandler();
+
+ /// @brief Handler for HTTP_URI_ST.
+ void uriHandler();
+
+ /// @brief Handler for states parsing "HTTP" string within the first line
+ /// of the HTTP request.
+ ///
+ /// @param expected_letter One of the 'H', 'T', 'P'.
+ /// @param next_state A state to which the parser should transition after
+ /// parsing the character.
+ void versionHTTPHandler(const char expected_letter,
+ const unsigned int next_state);
+
+ /// @brief Handler for HTTP_VERSION_MAJOR_START_ST and
+ /// HTTP_VERSION_MINOR_START_ST.
+ ///
+ /// This handler calculates version number using the following equation:
+ /// @code
+ /// storage = storage * 10 + c - '0';
+ /// @endcode
+ ///
+ /// @param next_state State to which the parser should transition.
+ /// @param [out] storage Reference to a number holding current product of
+ /// parsing major or minor version number.
+ void versionNumberStartHandler(const unsigned int next_state,
+ unsigned int* storage);
+
+ /// @brief Handler for HTTP_VERSION_MAJOR_ST and HTTP_VERSION_MINOR_ST.
+ ///
+ /// This handler calculates version number using the following equation:
+ /// @code
+ /// storage = storage * 10 + c - '0';
+ /// @endcode
+ ///
+ /// @param following_character Character following the version number, i.e.
+ /// '.' for major version, \r for minor version.
+ /// @param next_state State to which the parser should transition.
+ /// @param [out] storage Pointer to a number holding current product of
+ /// parsing major or minor version number.
+ void versionNumberHandler(const char following_character,
+ const unsigned int next_state,
+ unsigned int* const storage);
+
+ /// @brief Handler for states related to new lines.
+ ///
+ /// If the next_state is HTTP_PARSE_OK_ST it indicates that the parsed
+ /// value is a 3rd new line within request HTTP message. In this case the
+ /// handler calls @ref HttpRequest::create to validate the received message
+ /// (excluding body). The handler then reads the "Content-Length" header to
+ /// check if the request contains a body. If the "Content-Length" is greater
+ /// than zero, the parser transitions to HTTP_BODY_ST. If the
+ /// "Content-Length" doesn't exist the parser transitions to
+ /// HTTP_PARSE_OK_ST.
+ ///
+ /// @param next_state A state to which parser should transition.
+ void expectingNewLineHandler(const unsigned int next_state);
+
+ /// @brief Handler for HEADER_LINE_START_ST.
+ void headerLineStartHandler();
+
+ /// @brief Handler for HEADER_LWS_ST.
+ void headerLwsHandler();
+
+ /// @brief Handler for HEADER_NAME_ST.
+ void headerNameHandler();
+
+ /// @brief Handler for SPACE_BEFORE_HEADER_VALUE_ST.
+ void spaceBeforeHeaderValueHandler();
+
+ /// @brief Handler for HEADER_VALUE_ST.
+ void headerValueHandler();
+
+ /// @brief Handler for HTTP_BODY_ST.
+ void bodyHandler();
+
+ //@}
+
+ /// @brief Reference to the request object specified in the constructor.
+ HttpRequest& request_;
+
+ /// @brief Pointer to the internal context of the @ref HttpRequest object.
+ HttpRequestContextPtr context_;
+};
+
+} // namespace http
+} // namespace isc
+
+#endif // HTTP_REQUEST_PARSER_H
+
diff --git a/src/lib/http/response.cc b/src/lib/http/response.cc
new file mode 100644
index 0000000..cdc419a
--- /dev/null
+++ b/src/lib/http/response.cc
@@ -0,0 +1,233 @@
+// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/date_time.h>
+#include <http/response.h>
+#include <boost/date_time/local_time/local_time.hpp>
+#include <boost/date_time/time_facet.hpp>
+#include <sstream>
+
+using namespace boost::local_time;
+using namespace isc::http;
+
+namespace {
+
+/// @brief A map of status codes to status names.
+const std::map<HttpStatusCode, std::string> status_code_to_description = {
+ { HttpStatusCode::OK, "OK" },
+ { HttpStatusCode::CREATED, "Created" },
+ { HttpStatusCode::ACCEPTED, "Accepted" },
+ { HttpStatusCode::NO_CONTENT, "No Content" },
+ { HttpStatusCode::MULTIPLE_CHOICES, "Multiple Choices" },
+ { HttpStatusCode::MOVED_PERMANENTLY, "Moved Permanently" },
+ { HttpStatusCode::MOVED_TEMPORARILY, "Moved Temporarily" },
+ { HttpStatusCode::NOT_MODIFIED, "Not Modified" },
+ { HttpStatusCode::BAD_REQUEST, "Bad Request" },
+ { HttpStatusCode::UNAUTHORIZED, "Unauthorized" },
+ { HttpStatusCode::FORBIDDEN, "Forbidden" },
+ { HttpStatusCode::NOT_FOUND, "Not Found" },
+ { HttpStatusCode::REQUEST_TIMEOUT, "Request Timeout" },
+ { HttpStatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error" },
+ { HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented" },
+ { HttpStatusCode::BAD_GATEWAY, "Bad Gateway" },
+ { HttpStatusCode::SERVICE_UNAVAILABLE, "Service Unavailable" }
+};
+
+/// @brief New line (CRLF).
+const std::string crlf = "\r\n";
+
+}
+
+namespace isc {
+namespace http {
+
+HttpResponse::HttpResponse()
+ : HttpMessage(INBOUND), context_(new HttpResponseContext()) {
+}
+
+HttpResponse::HttpResponse(const HttpVersion& version,
+ const HttpStatusCode& status_code,
+ const CallSetGenericBody& generic_body)
+ : HttpMessage(OUTBOUND), context_(new HttpResponseContext()) {
+ context_->http_version_major_ = version.major_;
+ context_->http_version_minor_ = version.minor_;
+ context_->status_code_ = static_cast<unsigned int>(status_code);
+
+ if (generic_body.set_) {
+ // This currently does nothing, but it is useful to have it here as
+ // an example how to implement it in the derived classes.
+ setGenericBody(status_code);
+ }
+}
+
+void
+HttpResponse::create() {
+ try {
+ http_version_.major_ = context_->http_version_major_;
+ http_version_.minor_ = context_->http_version_minor_;
+
+ // Check if the HTTP version is allowed for this request.
+ if (!inRequiredSet(http_version_, required_versions_)) {
+ isc_throw(BadValue, "use of HTTP version "
+ << http_version_.major_ << "."
+ << http_version_.minor_
+ << " not allowed");
+ }
+
+ // Copy headers from the context.
+ for (auto header = context_->headers_.begin();
+ header != context_->headers_.end();
+ ++header) {
+ HttpHeaderPtr hdr(new HttpHeader(header->name_, header->value_));
+ headers_[hdr->getLowerCaseName()] = hdr;
+ }
+
+ if (getDirection() == HttpMessage::OUTBOUND) {
+ HttpHeaderPtr length_header(new HttpHeader("Content-Length", boost::lexical_cast<std::string>
+ (context_->body_.length())));
+ headers_["content-length"] = length_header;
+
+ HttpHeaderPtr date_header(new HttpHeader("Date", getDateHeaderValue()));;
+ headers_["date"] = date_header;
+ }
+
+ // Iterate over required headers and check that they exist
+ // in the HTTP response.
+ for (auto req_header = required_headers_.begin();
+ req_header != required_headers_.end();
+ ++req_header) {
+ auto header = headers_.find(req_header->first);
+ if (header == headers_.end()) {
+ isc_throw(BadValue, "required header " << req_header->first
+ << " not found in the HTTP response");
+ } else if (!req_header->second->getValue().empty() &&
+ !header->second->isValueEqual(req_header->second->getValue())) {
+ // If specific value is required for the header, check
+ // that the value in the HTTP response matches it.
+ isc_throw(BadValue, "required header's " << header->first
+ << " value is " << req_header->second->getValue()
+ << ", but " << header->second->getValue() << " was found");
+ }
+ }
+
+ } catch (const std::exception& ex) {
+ // Reset the state of the object if we failed at any point.
+ reset();
+ isc_throw(HttpResponseError, ex.what());
+ }
+
+ // All ok.
+ created_ = true;
+}
+
+void
+HttpResponse::finalize() {
+ if (!created_) {
+ create();
+ }
+
+ finalized_ = true;
+}
+
+void
+HttpResponse::reset() {
+ created_ = false;
+ finalized_ = false;
+ headers_.clear();
+}
+
+HttpStatusCode
+HttpResponse::getStatusCode() const {
+ checkCreated();
+ return (static_cast<HttpStatusCode>(context_->status_code_));
+}
+
+std::string
+HttpResponse::getStatusPhrase() const {
+ checkCreated();
+ return (context_->phrase_);
+}
+
+std::string
+HttpResponse::getBody() const {
+ checkFinalized();
+ return (context_->body_);
+}
+
+bool
+HttpResponse::isClientError(const HttpStatusCode& status_code) {
+ // Client errors have status codes of 4XX.
+ uint16_t c = statusCodeToNumber(status_code);
+ return ((c >= 400) && (c < 500));
+}
+
+bool
+HttpResponse::isServerError(const HttpStatusCode& status_code) {
+ // Server errors have status codes of 5XX.
+ uint16_t c = statusCodeToNumber(status_code);
+ return ((c >= 500) && (c < 600));
+}
+
+std::string
+HttpResponse::statusCodeToString(const HttpStatusCode& status_code) {
+ auto status_code_it = status_code_to_description.find(status_code);
+ if (status_code_it == status_code_to_description.end()) {
+ isc_throw(HttpResponseError, "internal server error: no HTTP status"
+ " description for the given status code "
+ << static_cast<uint16_t>(status_code));
+ }
+ return (status_code_it->second);
+}
+
+uint16_t
+HttpResponse::statusCodeToNumber(const HttpStatusCode& status_code) {
+ return (static_cast<uint16_t>(status_code));
+}
+
+std::string
+HttpResponse::getDateHeaderValue() const {
+ // This returns current time in the recommended format.
+ HttpDateTime date_time;
+ return (date_time.rfc1123Format());
+}
+
+std::string
+HttpResponse::toBriefString() const {
+ checkFinalized();
+
+ std::ostringstream s;
+ // HTTP version number and status code.
+ s << "HTTP/" << http_version_.major_ << "." << http_version_.minor_;
+ s << " " << context_->status_code_;
+ s << " " << statusCodeToString(static_cast<HttpStatusCode>(context_->status_code_));
+ return (s.str());
+}
+
+std::string
+HttpResponse::toString() const {
+
+ std::ostringstream s;
+ // HTTP version number and status code.
+ s << toBriefString() << crlf;
+
+ for (auto header_it = headers_.cbegin(); header_it != headers_.cend();
+ ++header_it) {
+ s << header_it->second->getName() << ": " << header_it->second->getValue()
+ << crlf;
+ }
+
+ s << crlf;
+
+ // Include message body.
+ s << getBody();
+
+ return (s.str());
+}
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/response.h b/src/lib/http/response.h
new file mode 100644
index 0000000..734d92f
--- /dev/null
+++ b/src/lib/http/response.h
@@ -0,0 +1,247 @@
+// Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_H
+#define HTTP_RESPONSE_H
+
+#include <cc/data.h>
+#include <http/header_context.h>
+#include <http/http_message.h>
+#include <http/response_context.h>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace http {
+
+/// @brief Generic exception thrown by @ref HttpResponse class.
+class HttpResponseError : public HttpMessageError {
+public:
+ HttpResponseError(const char* file, size_t line, const char* what) :
+ HttpMessageError(file, line, what) { };
+};
+
+/// @brief HTTP status codes (cf RFC 2068)
+enum class HttpStatusCode : std::uint16_t {
+ OK = 200,
+ CREATED = 201,
+ ACCEPTED = 202,
+ NO_CONTENT = 204,
+ MULTIPLE_CHOICES = 300,
+ MOVED_PERMANENTLY = 301,
+ MOVED_TEMPORARILY = 302,
+ NOT_MODIFIED = 304,
+ BAD_REQUEST = 400,
+ UNAUTHORIZED = 401,
+ FORBIDDEN = 403,
+ NOT_FOUND = 404,
+ REQUEST_TIMEOUT = 408,
+ INTERNAL_SERVER_ERROR = 500,
+ NOT_IMPLEMENTED = 501,
+ BAD_GATEWAY = 502,
+ SERVICE_UNAVAILABLE = 503
+};
+
+/// @brief Encapsulates the boolean value indicating if the @ref HttpResponse
+/// constructor should call its @c setGenericBody method during construction.
+struct CallSetGenericBody {
+
+ /// @brief Constructor.
+ ///
+ /// @param set A boolean value indicating if the method should be called
+ /// or not.
+ explicit CallSetGenericBody(const bool set)
+ : set_(set) {
+ }
+
+ /// @brief Returns encapsulated true.
+ static const CallSetGenericBody& yes() {
+ static CallSetGenericBody yes(true);
+ return (yes);
+ }
+
+ /// @brief Returns encapsulated false.
+ static const CallSetGenericBody& no() {
+ static CallSetGenericBody no(false);
+ return (no);
+ }
+
+ /// @brief A storage for the boolean flag.
+ bool set_;
+};
+
+class HttpResponse;
+
+/// @brief Pointer to the @ref HttpResponse object.
+typedef boost::shared_ptr<HttpResponse> HttpResponsePtr;
+
+/// @brief Pointer to the const @ref HttpResponse object.
+typedef boost::shared_ptr<const HttpResponse> ConstHttpResponsePtr;
+
+/// @brief Represents HTTP response message.
+///
+/// This derivation of the @c HttpMessage class is specialized to represent
+/// HTTP responses. This class provides two constructors for creating an inbound
+/// and outbound response instance respectively. This class is associated with
+/// an instance of the @c HttpResponseContext, which is used to provide response
+/// specific values, such as HTTP status and headers.
+///
+/// The derivations of this class provide specializations and specify the HTTP
+/// versions and headers supported/required in the specific use cases. For example,
+/// the @c HttpResponseJson class derives from the @c HttpResponse and it requires
+/// that response includes a body in the JSON format.
+class HttpResponse : public HttpMessage {
+public:
+
+ /// @brief Constructor for the inbound HTTP response.
+ explicit HttpResponse();
+
+
+ /// @brief Constructor for outbound HTTP response.
+ ///
+ /// Creates basic instance of the object. It sets the HTTP version and the
+ /// status code to be included in the response.
+ ///
+ /// @param version HTTP version.
+ /// @param status_code HTTP status code.
+ /// @param generic_body Indicates if the constructor should call
+ /// @c setGenericBody to create a generic content for the given
+ /// status code. This should be set to "no" when the constructor is
+ /// called by the derived class which provides its own implementation
+ /// of the @c setGenericBody method.
+ explicit HttpResponse(const HttpVersion& version,
+ const HttpStatusCode& status_code,
+ const CallSetGenericBody& generic_body =
+ CallSetGenericBody::yes());
+
+ /// @brief Returns pointer to the @ref HttpResponseContext.
+ ///
+ /// The context holds intermediate data for creating a response. The response
+ /// parser stores parsed raw data in the context. When parsing is finished,
+ /// the data are validated and committed into the @c HttpResponse.
+ ///
+ /// @return Pointer to the underlying @ref HttpResponseContext.
+ const HttpResponseContextPtr& context() const {
+ return (context_);
+ }
+
+ /// @brief Commits information held in the context into the response.
+ ///
+ /// This function reads HTTP version, status code and headers from the
+ /// context and validates their values. For the outbound messages, it
+ /// automatically appends Content-Length and Date headers to the response.
+ /// The Content-Length is set to the body size. The Date is set to the
+ /// current date and time.
+ virtual void create();
+
+ /// @brief Completes creation of the HTTP response.
+ ///
+ /// This method marks the response as finalized. The outbound response may now
+ /// be sent over the TCP socket. The information from the inbound message may
+ /// be read, including the response body.
+ virtual void finalize();
+
+ /// @brief Reset the state of the object.
+ virtual void reset();
+
+ /// @brief Returns HTTP status code.
+ HttpStatusCode getStatusCode() const;
+
+ /// @brief Returns HTTP status phrase.
+ std::string getStatusPhrase() const;
+
+ /// @brief Returns HTTP response body as string.
+ virtual std::string getBody() const;
+
+ /// @brief Retrieves a single JSON element.
+ ///
+ /// The element must be at top level of the JSON structure.
+ ///
+ /// @param element_name Element name.
+ ///
+ /// @return Pointer to the specified element or NULL if such element
+ /// doesn't exist.
+ /// @throw HttpResponseJsonError if an error occurred.
+ data::ConstElementPtr getJsonElement(const std::string& element_name) const;
+
+ /// @brief Checks if the status code indicates client error.
+ ///
+ /// @param status_code HTTP status code.
+ /// @return true if the status code indicates client error.
+ static bool isClientError(const HttpStatusCode& status_code);
+
+ /// @brief Checks if the status code indicates server error.
+ ///
+ /// @param status_code HTTP status code.
+ /// @return true if the status code indicates server error.
+ static bool isServerError(const HttpStatusCode& status_code);
+
+ /// @brief Convenience method converting status code to numeric value.
+ ///
+ /// @param status_code Status code represented as enum.
+ /// @return Numeric representation of the status code.
+ static uint16_t statusCodeToNumber(const HttpStatusCode& status_code);
+
+ /// @brief Converts status code to string.
+ ///
+ /// @param status_code HTTP status code.
+ /// @return Textual representation of the status code.
+ static std::string statusCodeToString(const HttpStatusCode& status_code);
+
+ /// @brief Returns HTTP version and HTTP status as a string.
+ std::string toBriefString() const;
+
+ /// @brief Returns HTTP response as string.
+ ///
+ /// This method is called to generate the outbound HTTP response. Make
+ /// sure to call @c finalize prior to calling this method.
+ virtual std::string toString() const;
+
+ /// @brief Returns current time formatted as required by RFC 1123.
+ ///
+ /// This method is virtual so as it can be overridden in unit tests
+ /// to return a "predictable" value of time, e.g. constant value.
+ ///
+ /// @return Current time formatted as required by RFC 1123.
+ virtual std::string getDateHeaderValue() const;
+
+private:
+
+ /// @brief Sets generic body for the given status code.
+ ///
+ /// Most of the classes derived from @ref HttpResponse will expect
+ /// a certain content type. Depending on the content type used they
+ /// will use different body formats for error messages. For example,
+ /// a response using text/html will use HTML within the response
+ /// body. The application/json will use JSON body etc. There is a
+ /// need to implement class specific way of generating the body
+ /// for error messages. Thus, each derivation of this class is
+ /// required to implement class specific @ref setGenericBody function
+ /// which should be called in the class constructor.
+ ///
+ /// This is also the case for this class, though the implementation
+ /// of @c setGenericBody is currently no-op.
+ ///
+ /// Note that this class can't be declared virtual because it is
+ /// meant to be called from the class constructor.
+ ///
+ /// @param status_code Status code for which the body should be
+ /// generated.
+ void setGenericBody(const HttpStatusCode& /*status_code*/) { };
+
+protected:
+
+ /// @brief Pointer to the @ref HttpResponseContext holding parsed
+ /// data.
+ HttpResponseContextPtr context_;
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/response_context.h b/src/lib/http/response_context.h
new file mode 100644
index 0000000..a821477
--- /dev/null
+++ b/src/lib/http/response_context.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_CONTEXT_H
+#define HTTP_RESPONSE_CONTEXT_H
+
+#include <http/header_context.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace http {
+
+/// @brief HTTP response context.
+///
+/// This context is used by the @c HttpResponseParser to store parsed
+/// data. This data is later used to create an instance of the
+/// @c HttpResponse or its derivation.
+struct HttpResponseContext {
+ /// @brief HTTP major version number.
+ unsigned int http_version_major_;
+ /// @brief HTTP minor version number.
+ unsigned int http_version_minor_;
+ /// @brief HTTP status code.
+ unsigned int status_code_;
+ /// @brief HTTP status phrase.
+ std::string phrase_;
+ /// @brief Collection of HTTP headers.
+ std::vector<HttpHeaderContext> headers_;
+ /// @brief HTTP request body.
+ std::string body_;
+};
+
+/// @brief Pointer to the @ref HttpResponseContext.
+typedef boost::shared_ptr<HttpResponseContext> HttpResponseContextPtr;
+
+} // end of namespace http
+} // end of namespace isc
+
+#endif // endif HTTP_RESPONSE_CONTEXT_H
diff --git a/src/lib/http/response_creator.cc b/src/lib/http/response_creator.cc
new file mode 100644
index 0000000..a060f6a
--- /dev/null
+++ b/src/lib/http/response_creator.cc
@@ -0,0 +1,33 @@
+// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/response_creator.h>
+
+namespace isc {
+namespace http {
+
+HttpResponsePtr
+HttpResponseCreator::createHttpResponse(HttpRequestPtr request) {
+ // This should never happen. This method must only be called with a
+ // non null request, so we consider it unlikely internal server error.
+ if (!request) {
+ isc_throw(HttpResponseError, "internal server error: HTTP request is null");
+ }
+
+ // If not finalized, the request parsing failed. Generate HTTP 400.
+ if (!request->isFinalized()) {
+ return (createStockHttpResponse(request, HttpStatusCode::BAD_REQUEST));
+ }
+
+ // Message has been successfully parsed. Create implementation specific
+ // response to this request.
+ return (createDynamicHttpResponse(request));
+}
+
+}
+}
diff --git a/src/lib/http/response_creator.h b/src/lib/http/response_creator.h
new file mode 100644
index 0000000..ac212fe
--- /dev/null
+++ b/src/lib/http/response_creator.h
@@ -0,0 +1,113 @@
+// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_CREATOR_H
+#define HTTP_RESPONSE_CREATOR_H
+
+#include <http/request.h>
+#include <http/response.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+class HttpResponseCreator;
+
+/// @brief Pointer to the @ref HttpResponseCreator object.
+typedef boost::shared_ptr<HttpResponseCreator> HttpResponseCreatorPtr;
+
+/// @brief Specifies an interface for classes creating HTTP responses
+/// from HTTP requests.
+///
+/// HTTP is designed to carry various content types. Most commonly
+/// this is text/html. In Kea, the application/json content type is used
+/// to carry control commands in JSON format. The libkea-http library is
+/// meant to be generic and provide means for transferring different types
+/// of content, depending on the use case.
+///
+/// This abstract class specifies a common interface for generating HTTP
+/// responses from HTTP requests using specific content type and being
+/// used in some specific context. Kea modules providing HTTP services need to
+/// implement their specific derivations of the @ref HttpResponseCreator
+/// class. These derivations use classes derived from @ref HttpRequest as
+/// an input and classes derived from @ref HttpResponse as an output of
+/// @c createHttpResponse method.
+class HttpResponseCreator {
+public:
+
+ /// @brief Destructor.
+ ///
+ /// Classes with virtual functions need virtual destructors.
+ virtual ~HttpResponseCreator() { };
+
+ /// @brief Create HTTP response from HTTP request received.
+ ///
+ /// This class implements a generic logic for creating a HTTP response.
+ /// Derived classes do not override this method. They merely implement
+ /// the methods it calls.
+ ///
+ /// The request processing may generally fail at one of the two stages:
+ /// parsing or interpretation of the parsed request. During the former
+ /// stage the request's syntax is checked, i.e. HTTP version, URI,
+ /// headers etc. During the latter stage the HTTP server checks if the
+ /// request is valid within the specific context, e.g. valid HTTP version
+ /// used, expected content type etc.
+ ///
+ /// In the @ref HttpRequest terms, the request has gone through the
+ /// first stage if it is "finalized" (see @ref HttpRequest::finalize).
+ /// This method accepts instances of both finalized and not finalized
+ /// requests. If the request isn't finalized it indicates that
+ /// the request parsing has failed. In such case, this method calls
+ /// @c createStockBadRequest to generate a response with HTTP 400 status
+ /// code. If the request is finalized, this method calls
+ /// @c createDynamicHttpResponse to generate implementation specific
+ /// response to the received request.
+ ///
+ /// This method is marked virtual final to prevent derived classes from
+ /// overriding this method. Instead, the derived classes must implement
+ /// protected methods which this method calls.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @return Pointer to the object encapsulating generated HTTP response.
+ /// @throw HttpResponseError if request is a NULL pointer.
+ virtual HttpResponsePtr
+ createHttpResponse(HttpRequestPtr request) final;
+
+ /// @brief Create a new request.
+ ///
+ /// This method creates an instance of the @ref HttpRequest or derived
+ /// class. The type of the object is compatible with the instance of
+ /// the @ref HttpResponseCreator implementation which creates it, i.e.
+ /// can be used as an argument in the call to @ref createHttpResponse.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const = 0;
+
+ /// @brief Creates implementation specific HTTP response.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @param status_code Status code of the response.
+ /// @return Pointer to an object representing HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const = 0;
+
+protected:
+
+ /// @brief Creates implementation specific HTTP response.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @return Pointer to an object representing HTTP response.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) = 0;
+
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/response_creator_factory.h b/src/lib/http/response_creator_factory.h
new file mode 100644
index 0000000..c2fc917
--- /dev/null
+++ b/src/lib/http/response_creator_factory.h
@@ -0,0 +1,59 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_CREATOR_FACTORY_H
+#define HTTP_RESPONSE_CREATOR_FACTORY_H
+
+#include <http/response_creator.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+/// @brief Specifies the interface for implementing custom factory classes
+/// used to create instances of @ref HttpResponseCreator.
+///
+/// The @ref HttpResponseCreator defines an interface for the classes used
+/// to generate HTTP responses. Such classes are defined outside of this
+/// library and they are specific to the needs of the particular module.
+/// In some cases it may be desired to create new instance of the
+/// @ref HttpResponseCreator implementation for every request processed.
+/// The @ref HttpResponseCreatorFactory is an interface to the "factory"
+/// class which generates canned @ref HttpResponseCreator instances. The
+/// pointer to the factory class is passed to the @ref HttpListener and
+/// the listener propagates it down to other classes. These classes call
+/// @ref HttpResponseCreatorFactory::create to retrieve an instance of the
+/// appropriate @ref HttpResponseCreator, which is in turn used to generate
+/// HTTP response.
+///
+/// Note that an implementation of the @ref HttpResponseCreatorFactory::create
+/// may always return the same instance of the @ref HttpResponseCreator
+/// if creating new instance for each request is not required or undesired.
+class HttpResponseCreatorFactory {
+public:
+
+ /// @brief Virtual destructor.
+ virtual ~HttpResponseCreatorFactory() { }
+
+ /// @brief Returns an instance of the @ref HttpResponseCreator.
+ ///
+ /// The implementation may create new instance every time this method
+ /// is called, or it may always return the same instance.
+ ///
+ /// @return Pointer to the instance of the @ref HttpResponseCreator to
+ /// be used to generate HTTP response.
+ virtual HttpResponseCreatorPtr create() const = 0;
+
+};
+
+/// @brief Pointer to the @ref HttpResponseCreatorFactory.
+typedef boost::shared_ptr<HttpResponseCreatorFactory>
+HttpResponseCreatorFactoryPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/response_json.cc b/src/lib/http/response_json.cc
new file mode 100644
index 0000000..7543d76
--- /dev/null
+++ b/src/lib/http/response_json.cc
@@ -0,0 +1,122 @@
+// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/response_json.h>
+#include <map>
+
+using namespace isc::data;
+
+namespace isc {
+namespace http {
+
+HttpResponseJson::HttpResponseJson()
+ : HttpResponse() {
+ context()->headers_.push_back(HttpHeaderContext("Content-Type", "application/json"));
+}
+
+
+HttpResponseJson::HttpResponseJson(const HttpVersion& version,
+ const HttpStatusCode& status_code,
+ const CallSetGenericBody& generic_body)
+ : HttpResponse(version, status_code, CallSetGenericBody::no()) {
+ context()->headers_.push_back(HttpHeaderContext("Content-Type", "application/json"));
+ // This class provides its own implementation of the setGenericBody.
+ // We call it here unless the derived class calls this constructor
+ // from its own constructor and indicates that we shouldn't set the
+ // generic content in the body.
+ if (generic_body.set_) {
+ setGenericBody(status_code);
+ }
+}
+
+void
+HttpResponseJson::setGenericBody(const HttpStatusCode& status_code) {
+ // Only generate the content for the client or server errors. For
+ // other status codes (status OK in particular) the content should
+ // be created using setBodyAsJson or setBody.
+ if (isClientError(status_code) || isServerError(status_code)) {
+ std::map<std::string, ConstElementPtr> map_elements;
+ map_elements["result"] =
+ ConstElementPtr(new IntElement(statusCodeToNumber(status_code)));
+ map_elements["text"] =
+ ConstElementPtr(new StringElement(statusCodeToString(status_code)));
+ auto body = Element::createMap();
+ body->setValue(map_elements);
+ setBodyAsJson(body);
+ }
+}
+
+void
+HttpResponseJson::finalize() {
+ if (!created_) {
+ create();
+ }
+
+ // Parse JSON body and store.
+ parseBodyAsJson();
+ finalized_ = true;
+}
+
+void
+HttpResponseJson::reset() {
+ HttpResponse::reset();
+ json_.reset();
+}
+
+ConstElementPtr
+HttpResponseJson::getBodyAsJson() const {
+ checkFinalized();
+ return (json_);
+}
+
+void
+HttpResponseJson::setBodyAsJson(const ConstElementPtr& json_body) {
+ if (json_body) {
+ context()->body_ = json_body->str();
+
+ } else {
+ context()->body_.clear();
+ }
+
+ json_ = json_body;
+}
+
+ConstElementPtr
+HttpResponseJson::getJsonElement(const std::string& element_name) const {
+ try {
+ ConstElementPtr body = getBodyAsJson();
+ if (body) {
+ const std::map<std::string, ConstElementPtr>& map_value = body->mapValue();
+ auto map_element = map_value.find(element_name);
+ if (map_element != map_value.end()) {
+ return (map_element->second);
+ }
+ }
+
+ } catch (const std::exception& ex) {
+ isc_throw(HttpResponseJsonError, "unable to get JSON element "
+ << element_name << ": " << ex.what());
+ }
+ return (ConstElementPtr());
+}
+
+void
+HttpResponseJson::parseBodyAsJson() {
+ try {
+ // Only parse the body if it hasn't been parsed yet.
+ if (!json_ && !context_->body_.empty()) {
+ json_ = Element::fromJSON(context_->body_);
+ }
+ } catch (const std::exception& ex) {
+ isc_throw(HttpResponseJsonError, "unable to parse the body of the HTTP"
+ " response: " << ex.what());
+ }
+}
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/response_json.h b/src/lib/http/response_json.h
new file mode 100644
index 0000000..d8ed06e
--- /dev/null
+++ b/src/lib/http/response_json.h
@@ -0,0 +1,114 @@
+// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_JSON_H
+#define HTTP_RESPONSE_JSON_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <http/response.h>
+
+namespace isc {
+namespace http {
+
+/// @brief Exception thrown when body of the HTTP message is not JSON.
+class HttpResponseJsonError : public HttpResponseError {
+public:
+ HttpResponseJsonError(const char* file, size_t line, const char* what) :
+ HttpResponseError(file, line, what) { };
+};
+
+class HttpResponseJson;
+
+/// @brief Pointer to the @ref HttpResponseJson object.
+typedef boost::shared_ptr<HttpResponseJson> HttpResponseJsonPtr;
+
+/// @brief Represents HTTP response with JSON content.
+///
+/// This is a specialization of the @ref HttpResponse class which
+/// includes "Content-Type" equal to "application/json". It also provides
+/// methods to create JSON content within HTTP responses.
+class HttpResponseJson : public HttpResponse {
+public:
+
+ /// @brief Constructor for the inbound HTTP response.
+ explicit HttpResponseJson();
+
+ /// @brief Constructor for the outbound HTTP response.
+ ///
+ /// @param version HTTP version.
+ /// @param status_code HTTP status code.
+ /// @param generic_body Indicates if the constructor should call
+ /// @c setGenericBody to create a generic content for the given
+ /// status code. This should be set to "no" when the constructor is
+ /// called by the derived class which provides its own implementation
+ /// of the @c setGenericBody method.
+ explicit HttpResponseJson(const HttpVersion& version,
+ const HttpStatusCode& status_code,
+ const CallSetGenericBody& generic_body =
+ CallSetGenericBody::yes());
+
+ /// @brief Completes creation of the HTTP response.
+ ///
+ /// This method marks the response as finalized. The JSON structure is
+ /// created and can be used to retrieve the parsed data. If this is the
+ /// outbound message, it can be transmitted over the wire as the body
+ /// for the message is now committed.
+ virtual void finalize();
+
+ /// @brief Reset the state of the object.
+ virtual void reset();
+
+ /// @brief Retrieves JSON body.
+ ///
+ /// @return Pointer to the root element of the JSON structure.
+ /// @throw HttpRequestJsonError if an error occurred.
+ data::ConstElementPtr getBodyAsJson() const;
+
+ /// @brief Generates JSON content from the data structures represented
+ /// as @ref data::ConstElementPtr.
+ ///
+ /// @param json_body A data structure representing JSON content.
+ void setBodyAsJson(const data::ConstElementPtr& json_body);
+
+ /// @brief Retrieves a single JSON element.
+ ///
+ /// The element must be at top level of the JSON structure.
+ ///
+ /// @param element_name Element name.
+ ///
+ /// @return Pointer to the specified element or NULL if such element
+ /// doesn't exist.
+ /// @throw HttpRequestJsonError if an error occurred.
+ data::ConstElementPtr getJsonElement(const std::string& element_name) const;
+
+private:
+
+ /// @brief Sets generic body for the given status code.
+ ///
+ /// This method generates JSON content for the HTTP client and server
+ /// errors. The generated JSON structure is a map containing "result"
+ /// value holding HTTP status code (e.g. 400) and the "text" string
+ /// holding a status code description.
+ ///
+ /// @param status_code Status code for which the body should be
+ /// generated.
+ void setGenericBody(const HttpStatusCode& status_code);
+
+protected:
+
+ /// @brief Interprets body as JSON, which can be later retrieved using
+ /// data element objects.
+ void parseBodyAsJson();
+
+ /// @brief Pointer to the parsed JSON body.
+ data::ConstElementPtr json_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/response_parser.cc b/src/lib/http/response_parser.cc
new file mode 100644
index 0000000..b2ab797
--- /dev/null
+++ b/src/lib/http/response_parser.cc
@@ -0,0 +1,461 @@
+// Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/response_parser.h>
+#include <functional>
+
+using namespace isc::util;
+
+namespace isc {
+namespace http {
+
+const int HttpResponseParser::RECEIVE_START_ST;
+const int HttpResponseParser::HTTP_VERSION_H_ST;
+const int HttpResponseParser::HTTP_VERSION_T1_ST;
+const int HttpResponseParser::HTTP_VERSION_T2_ST;
+const int HttpResponseParser::HTTP_VERSION_P_ST;
+const int HttpResponseParser::HTTP_VERSION_SLASH_ST;
+const int HttpResponseParser::HTTP_VERSION_MAJOR_START_ST;
+const int HttpResponseParser::HTTP_VERSION_MAJOR_ST;
+const int HttpResponseParser::HTTP_VERSION_MINOR_START_ST;
+const int HttpResponseParser::HTTP_VERSION_MINOR_ST;
+const int HttpResponseParser::HTTP_STATUS_CODE_START_ST;
+const int HttpResponseParser::HTTP_STATUS_CODE_ST;
+const int HttpResponseParser::HTTP_PHRASE_START_ST;
+const int HttpResponseParser::HTTP_PHRASE_ST;
+const int HttpResponseParser::EXPECTING_NEW_LINE1_ST;
+const int HttpResponseParser::HEADER_LINE_START_ST;
+const int HttpResponseParser::HEADER_LWS_ST;
+const int HttpResponseParser::HEADER_NAME_ST;
+const int HttpResponseParser::SPACE_BEFORE_HEADER_VALUE_ST;
+const int HttpResponseParser::HEADER_VALUE_ST;
+const int HttpResponseParser::EXPECTING_NEW_LINE2_ST;
+const int HttpResponseParser::EXPECTING_NEW_LINE3_ST;
+const int HttpResponseParser::HTTP_BODY_ST;
+
+HttpResponseParser::HttpResponseParser(HttpResponse& response)
+ : HttpMessageParserBase(response), response_(response),
+ context_(response.context()) {
+}
+
+void
+HttpResponseParser::initModel() {
+ // Initialize dictionaries of events and states.
+ initDictionaries();
+
+ // Set the current state to starting state and enter the run loop.
+ setState(RECEIVE_START_ST);
+
+ // Parsing starts from here.
+ postNextEvent(START_EVT);
+}
+
+void
+HttpResponseParser::defineStates() {
+ // Call parent class implementation first.
+ HttpMessageParserBase::defineStates();
+
+ // Define HTTP parser specific states.
+ defineState(RECEIVE_START_ST, "RECEIVE_START_ST",
+ std::bind(&HttpResponseParser::receiveStartHandler, this));
+
+ defineState(HTTP_VERSION_T1_ST, "HTTP_VERSION_T1_ST",
+ std::bind(&HttpResponseParser::versionHTTPHandler, this, 'T',
+ HTTP_VERSION_T2_ST));
+
+ defineState(HTTP_VERSION_T2_ST, "HTTP_VERSION_T2_ST",
+ std::bind(&HttpResponseParser::versionHTTPHandler, this, 'T',
+ HTTP_VERSION_P_ST));
+
+ defineState(HTTP_VERSION_P_ST, "HTTP_VERSION_P_ST",
+ std::bind(&HttpResponseParser::versionHTTPHandler, this, 'P',
+ HTTP_VERSION_SLASH_ST));
+
+ defineState(HTTP_VERSION_SLASH_ST, "HTTP_VERSION_SLASH_ST",
+ std::bind(&HttpResponseParser::versionHTTPHandler, this, '/',
+ HTTP_VERSION_MAJOR_ST));
+
+ defineState(HTTP_VERSION_MAJOR_START_ST, "HTTP_VERSION_MAJOR_START_ST",
+ std::bind(&HttpResponseParser::numberStartHandler, this,
+ HTTP_VERSION_MAJOR_ST,
+ "HTTP version",
+ &context_->http_version_major_));
+
+ defineState(HTTP_VERSION_MAJOR_ST, "HTTP_VERSION_MAJOR_ST",
+ std::bind(&HttpResponseParser::numberHandler, this,
+ '.', HTTP_VERSION_MINOR_START_ST,
+ "HTTP version",
+ &context_->http_version_major_));
+
+ defineState(HTTP_VERSION_MINOR_START_ST, "HTTP_VERSION_MINOR_START_ST",
+ std::bind(&HttpResponseParser::numberStartHandler, this,
+ HTTP_VERSION_MINOR_ST,
+ "HTTP version",
+ &context_->http_version_minor_));
+
+ defineState(HTTP_VERSION_MINOR_ST, "HTTP_VERSION_MINOR_ST",
+ std::bind(&HttpResponseParser::numberHandler, this,
+ ' ', HTTP_STATUS_CODE_START_ST,
+ "HTTP version",
+ &context_->http_version_minor_));
+
+ defineState(HTTP_STATUS_CODE_START_ST, "HTTP_STATUS_CODE_START_ST",
+ std::bind(&HttpResponseParser::numberStartHandler, this,
+ HTTP_STATUS_CODE_ST,
+ "HTTP status code",
+ &context_->status_code_));
+
+ defineState(HTTP_STATUS_CODE_ST, "HTTP_STATUS_CODE_ST",
+ std::bind(&HttpResponseParser::numberHandler, this,
+ ' ', HTTP_PHRASE_START_ST,
+ "HTTP status code",
+ &context_->status_code_));
+
+ defineState(HTTP_PHRASE_START_ST, "HTTP_PHRASE_START_ST",
+ std::bind(&HttpResponseParser::phraseStartHandler, this));
+
+ defineState(HTTP_PHRASE_ST, "HTTP_PHRASE_ST",
+ std::bind(&HttpResponseParser::phraseHandler, this));
+
+ defineState(EXPECTING_NEW_LINE1_ST, "EXPECTING_NEW_LINE1_ST",
+ std::bind(&HttpResponseParser::expectingNewLineHandler, this,
+ HEADER_LINE_START_ST));
+
+ defineState(HEADER_LINE_START_ST, "HEADER_LINE_START_ST",
+ std::bind(&HttpResponseParser::headerLineStartHandler, this));
+
+ defineState(HEADER_LWS_ST, "HEADER_LWS_ST",
+ std::bind(&HttpResponseParser::headerLwsHandler, this));
+
+ defineState(HEADER_NAME_ST, "HEADER_NAME_ST",
+ std::bind(&HttpResponseParser::headerNameHandler, this));
+
+ defineState(SPACE_BEFORE_HEADER_VALUE_ST, "SPACE_BEFORE_HEADER_VALUE_ST",
+ std::bind(&HttpResponseParser::spaceBeforeHeaderValueHandler, this));
+
+ defineState(HEADER_VALUE_ST, "HEADER_VALUE_ST",
+ std::bind(&HttpResponseParser::headerValueHandler, this));
+
+ defineState(EXPECTING_NEW_LINE2_ST, "EXPECTING_NEW_LINE2",
+ std::bind(&HttpResponseParser::expectingNewLineHandler, this,
+ HEADER_LINE_START_ST));
+
+ defineState(EXPECTING_NEW_LINE3_ST, "EXPECTING_NEW_LINE3_ST",
+ std::bind(&HttpResponseParser::expectingNewLineHandler, this,
+ HTTP_PARSE_OK_ST));
+
+ defineState(HTTP_BODY_ST, "HTTP_BODY_ST",
+ std::bind(&HttpResponseParser::bodyHandler, this));
+}
+
+void
+HttpResponseParser::receiveStartHandler() {
+ std::string bytes;
+ getNextFromBuffer(bytes);
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch(getNextEvent()) {
+ case START_EVT:
+ if (bytes[0] == 'H') {
+ transition(HTTP_VERSION_T1_ST, DATA_READ_OK_EVT);
+
+ } else {
+ parseFailure("unexpected first character " + std::string(1, bytes[0]) +
+ ": expected \'H\'");
+ }
+ break;
+
+ default:
+ invalidEventError("receiveStartHandler", getNextEvent());
+ }
+ }
+}
+
+void
+HttpResponseParser::versionHTTPHandler(const char expected_letter,
+ const unsigned int next_state) {
+ stateWithReadHandler("versionHTTPHandler",
+ [this, expected_letter, next_state](const char c) {
+ // We're handling one of the letters: 'H', 'T' or 'P'.
+ if (c == expected_letter) {
+ // The HTTP version is specified as "HTTP/X.Y". If the current
+ // character is a slash we're starting to parse major HTTP version
+ // number. Let's reset the version numbers.
+ if (c == '/') {
+ context_->http_version_major_ = 0;
+ context_->http_version_minor_ = 0;
+ }
+ // In all cases, let's transition to next specified state.
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else {
+ // Unexpected character found. Parsing fails.
+ parseFailure("unexpected character " + std::string(1, c) +
+ " in HTTP version string");
+ }
+ });
+}
+
+void
+HttpResponseParser::numberStartHandler(const unsigned int next_state,
+ const std::string& number_name,
+ unsigned int* storage) {
+ stateWithReadHandler("numberStartHandler",
+ [this, next_state, number_name, storage](const char c) mutable {
+ // HTTP version number must be a digit.
+ if (isdigit(c)) {
+ // Update the version number using new digit being parsed.
+ *storage = *storage * 10 + c - '0';
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else {
+ parseFailure("expected digit in " + number_name + ", found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpResponseParser::numberHandler(const char following_character,
+ const unsigned int next_state,
+ const std::string& number_name,
+ unsigned int* const storage) {
+ stateWithReadHandler("numberHandler",
+ [this, following_character, number_name, next_state, storage](const char c)
+ mutable {
+ // We're getting to the end of the version number, let's transition
+ // to next state.
+ if (c == following_character) {
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else if (isdigit(c)) {
+ // Current character is a digit, so update the version number.
+ *storage = *storage * 10 + c - '0';
+
+ } else {
+ parseFailure("expected digit in " + number_name + ", found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpResponseParser::phraseStartHandler() {
+ stateWithReadHandler("phraseStartHandler", [this](const char c) {
+ if (!isChar(c) || isCtl(c)) {
+ parseFailure("invalid first character " + std::string(1, c) +
+ " in HTTP phrase");
+ } else {
+ context_->phrase_.push_back(c);
+ transition(HTTP_PHRASE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::phraseHandler() {
+ stateWithReadHandler("phraseHandler", [this](const char c) {
+ if (c == '\r') {
+ transition(EXPECTING_NEW_LINE1_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " in HTTP phrase");
+
+ } else {
+ context_->phrase_.push_back(c);
+ transition(HTTP_PHRASE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::expectingNewLineHandler(const unsigned int next_state) {
+ stateWithReadHandler("expectingNewLineHandler", [this, next_state](const char c) {
+ // Only a new line character is allowed in this state.
+ if (c == '\n') {
+ // If next state is HTTP_PARSE_OK_ST it means that we're
+ // parsing 3rd new line in the HTTP request message. This
+ // terminates the HTTP request (if there is no body) or marks the
+ // beginning of the body.
+ if (next_state == HTTP_PARSE_OK_ST) {
+ // Whether there is a body in this message or not, we should
+ // parse the HTTP headers to validate it and to check if there
+ // is "Content-Length" specified. The "Content-Length" is
+ // required for parsing body.
+ response_.create();
+ try {
+ // This will throw exception if there is no Content-Length.
+ uint64_t content_length =
+ response_.getHeaderValueAsUint64("Content-Length");
+ if (content_length > 0) {
+ // There is body in this request, so let's parse it.
+ transition(HTTP_BODY_ST, DATA_READ_OK_EVT);
+ } else {
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ } catch (const std::exception& ex) {
+ // There is no body in this message. If the body is required
+ // parsing fails.
+ if (response_.requiresBody()) {
+ parseFailure("HTTP message lacks a body");
+
+ } else {
+ // Body not required so simply terminate parsing.
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ }
+
+ } else {
+ // This is 1st or 2nd new line, so let's transition to the
+ // next state required by this handler.
+ transition(next_state, DATA_READ_OK_EVT);
+ }
+ } else {
+ parseFailure("expecting new line after CR, found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpResponseParser::headerLineStartHandler() {
+ stateWithReadHandler("headerLineStartHandler", [this](const char c) {
+ // If we're parsing HTTP headers and we found CR it marks the
+ // end of headers section.
+ if (c == '\r') {
+ transition(EXPECTING_NEW_LINE3_ST, DATA_READ_OK_EVT);
+
+ } else if (!context_->headers_.empty() && ((c == ' ') || (c == '\t'))) {
+ // New line in headers section followed by space or tab is an LWS,
+ // a line break within header value.
+ transition(HEADER_LWS_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " in header name");
+
+ } else {
+ // Update header name with the parsed letter.
+ context_->headers_.push_back(HttpHeaderContext());
+ context_->headers_.back().name_.push_back(c);
+ transition(HEADER_NAME_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::headerLwsHandler() {
+ stateWithReadHandler("headerLwsHandler", [this](const char c) {
+ if (c == '\r') {
+ // Found CR during parsing a header value. Next value
+ // should be new line.
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if ((c == ' ') || (c == '\t')) {
+ // Space and tab is used to mark LWS. Simply swallow
+ // this character.
+ transition(getCurrState(), DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header " +
+ context_->headers_.back().name_);
+
+ } else {
+ // We're parsing header value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::headerNameHandler() {
+ stateWithReadHandler("headerNameHandler", [this](const char c) {
+ // Colon follows header name and it has its own state.
+ if (c == ':') {
+ transition(SPACE_BEFORE_HEADER_VALUE_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " found in the HTTP header name");
+
+ } else {
+ // Parsing a header name, so update it.
+ context_->headers_.back().name_.push_back(c);
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::spaceBeforeHeaderValueHandler() {
+ stateWithReadHandler("spaceBeforeHeaderValueHandler", [this](const char c) {
+ if (c == ' ') {
+ // Remove leading whitespace from the header value.
+ transition(getCurrState(), DATA_READ_OK_EVT);
+
+ } else if (c == '\r') {
+ // If CR found during parsing header value, it marks the end
+ // of this value.
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header "
+ + context_->headers_.back().name_);
+
+ } else {
+ // Still parsing the value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::headerValueHandler() {
+ stateWithReadHandler("headerValueHandler", [this](const char c) {
+ // If CR found during parsing header value, it marks the end
+ // of this value.
+ if (c == '\r') {
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header "
+ + context_->headers_.back().name_);
+
+ } else {
+ // Still parsing the value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::bodyHandler() {
+ stateWithMultiReadHandler("bodyHandler", [this](const std::string& body) {
+ // We don't validate the body at this stage. Simply record the
+ // number of characters specified within "Content-Length".
+ context_->body_ += body;
+ size_t content_length = response_.getHeaderValueAsUint64("Content-Length");
+ if (context_->body_.length() < content_length) {
+ transition(HTTP_BODY_ST, DATA_READ_OK_EVT);
+
+ } else {
+ // If there was some extraneous data, ignore it.
+ if (context_->body_.length() > content_length) {
+ context_->body_.resize(content_length);
+ }
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ });
+}
+
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/response_parser.h b/src/lib/http/response_parser.h
new file mode 100644
index 0000000..b4ddf1d
--- /dev/null
+++ b/src/lib/http/response_parser.h
@@ -0,0 +1,243 @@
+// Copyright (C) 2017-2018,2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_PARSER_H
+#define HTTP_RESPONSE_PARSER_H
+
+#include <http/http_message_parser_base.h>
+#include <http/response.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+class HttpResponseParser;
+
+/// @brief Pointer to the @ref HttpResponseParser.
+typedef boost::shared_ptr<HttpResponseParser> HttpResponseParserPtr;
+
+/// @brief A generic parser for HTTP responses.
+///
+/// This class implements a parser for HTTP responses. The parser derives
+/// from the @ref HttpMessageParserBase class and implements its own state
+/// machine on top of it. The states of the parser reflect various parts of
+/// the HTTP message being parsed, e.g. parsing HTTP version, status code,
+/// message body etc. The descriptions of all parser states are provided
+/// below together with the constants defining these states.
+///
+/// The response parser validates the syntax of the received message as it
+/// progresses with parsing the data. Though, it doesn't interpret the
+/// received data until the whole message is parsed. In most cases we want
+/// to apply some restrictions on the message content, e.g. responses to
+/// control commands include JSON body. The parser doesn't verify if the
+/// message meets such restrictions until the whole message is parsed,
+/// i.e. stored in the @ref HttpResponseContext object. This object is
+/// associated with @ref HttpResponse object (or its derivation). When
+/// the parsing is completed, the @ref HttpResponse::create method is
+/// called to retrieve and interpret the data from the context. In
+/// particular, the @ref HttpResponse or its derivation checks if the
+/// received message meets the desired restrictions.
+class HttpResponseParser : public HttpMessageParserBase {
+public:
+
+ /// @name States supported by the HttpResponseParser.
+ ///
+ //@{
+
+ /// @brief State indicating a beginning of parsing.
+ static const int RECEIVE_START_ST = SM_DERIVED_STATE_MIN + 101;
+
+ /// @brief Parsing letter "H" of "HTTP".
+ static const int HTTP_VERSION_H_ST = SM_DERIVED_STATE_MIN + 102;
+
+ /// @brief Parsing first occurrence of "T" in "HTTP".
+ static const int HTTP_VERSION_T1_ST = SM_DERIVED_STATE_MIN + 103;
+
+ /// @brief Parsing second occurrence of "T" in "HTTP".
+ static const int HTTP_VERSION_T2_ST = SM_DERIVED_STATE_MIN + 104;
+
+ /// @brief Parsing letter "P" in "HTTP".
+ static const int HTTP_VERSION_P_ST = SM_DERIVED_STATE_MIN + 105;
+
+ /// @brief Parsing slash character in "HTTP/Y.X"
+ static const int HTTP_VERSION_SLASH_ST = SM_DERIVED_STATE_MIN + 106;
+
+ /// @brief Starting to parse major HTTP version number.
+ static const int HTTP_VERSION_MAJOR_START_ST = SM_DERIVED_STATE_MIN + 107;
+
+ /// @brief Parsing major HTTP version number.
+ static const int HTTP_VERSION_MAJOR_ST = SM_DERIVED_STATE_MIN + 108;
+
+ /// @brief Starting to parse minor HTTP version number.
+ static const int HTTP_VERSION_MINOR_START_ST = SM_DERIVED_STATE_MIN + 109;
+
+ /// @brief Parsing minor HTTP version number.
+ static const int HTTP_VERSION_MINOR_ST = SM_DERIVED_STATE_MIN + 110;
+
+ /// @brief Starting to parse HTTP status code.
+ static const int HTTP_STATUS_CODE_START_ST = SM_DERIVED_STATE_MIN + 111;
+
+ /// @brief Parsing HTTP status code.
+ static const int HTTP_STATUS_CODE_ST = SM_DERIVED_STATE_MIN + 112;
+
+ /// @brief Starting to parse HTTP status phrase.
+ static const int HTTP_PHRASE_START_ST = SM_DERIVED_STATE_MIN + 113;
+
+ /// @brief Parsing HTTP status phrase.
+ static const int HTTP_PHRASE_ST = SM_DERIVED_STATE_MIN + 114;
+
+ /// @brief Parsing first new line (after HTTP status phrase).
+ static const int EXPECTING_NEW_LINE1_ST = SM_DERIVED_STATE_MIN + 115;
+
+ static const int HEADER_LINE_START_ST = SM_DERIVED_STATE_MIN + 116;
+
+ /// @brief Parsing LWS (Linear White Space), i.e. new line with a space
+ /// or tab character while parsing a HTTP header.
+ static const int HEADER_LWS_ST = SM_DERIVED_STATE_MIN + 117;
+
+ /// @brief Parsing header name.
+ static const int HEADER_NAME_ST = SM_DERIVED_STATE_MIN + 118;
+
+ /// @brief Parsing space before header value.
+ static const int SPACE_BEFORE_HEADER_VALUE_ST = SM_DERIVED_STATE_MIN + 119;
+
+ /// @brief Parsing header value.
+ static const int HEADER_VALUE_ST = SM_DERIVED_STATE_MIN + 120;
+
+ /// @brief Expecting new line after parsing header value.
+ static const int EXPECTING_NEW_LINE2_ST = SM_DERIVED_STATE_MIN + 121;
+
+ /// @brief Expecting second new line marking end of HTTP headers.
+ static const int EXPECTING_NEW_LINE3_ST = SM_DERIVED_STATE_MIN + 122;
+
+ /// @brief Parsing body of a HTTP message.
+ static const int HTTP_BODY_ST = SM_DERIVED_STATE_MIN + 123;
+
+ //@}
+
+ /// @brief Constructor.
+ ///
+ /// Creates new instance of the parser.
+ ///
+ /// @param response Reference to the @ref HttpResponse object or its
+ /// derivation that should be used to validate the parsed response and
+ /// to be used as a container for the parsed response.
+ explicit HttpResponseParser(HttpResponse& response);
+
+ /// @brief Initialize the state model for parsing.
+ ///
+ /// This method must be called before parsing the response, i.e. before
+ /// calling @ref HttpResponseParser::poll. It initializes dictionaries of
+ /// states and events, and sets the initial model state to RECEIVE_START_ST.
+ void initModel();
+
+private:
+
+ /// @brief Defines states of the parser.
+ virtual void defineStates();
+
+ /// @name State handlers.
+ ///
+ //@{
+
+ /// @brief Handler for RECEIVE_START_ST.
+ void receiveStartHandler();
+
+ /// @brief Handler for states parsing "HTTP" string within the first line
+ /// of the HTTP request.
+ ///
+ /// @param expected_letter One of the 'H', 'T', 'P'.
+ /// @param next_state A state to which the parser should transition after
+ /// parsing the character.
+ void versionHTTPHandler(const char expected_letter,
+ const unsigned int next_state);
+
+ /// @brief Handler for states in which parser begins to read numeric values.
+ ///
+ /// This handler calculates version number using the following equation:
+ /// @code
+ /// storage = storage * 10 + c - '0';
+ /// @endcode
+ ///
+ /// @param next_state State to which the parser should transition.
+ /// @param number_name Name of the parsed numeric value, e.g. HTTP version or
+ /// HTTP status code (used for error logging).
+ /// @param [out] storage Reference to a number holding current product of
+ /// parsing major or minor version number.
+ void numberStartHandler(const unsigned int next_state,
+ const std::string& number_name,
+ unsigned int* const storage);
+
+ /// @brief Handler for states in which pareser reads numeric values.
+ ///
+ /// This handler calculates version number using the following equation:
+ /// @code
+ /// storage = storage * 10 + c - '0';
+ /// @endcode
+ ///
+ /// @param following_character Character following the version number, i.e.
+ /// '.' for major version, \r for minor version.
+ /// @param next_state State to which the parser should transition.
+ /// @param number_name Name of the parsed numeric value, e.g. HTTP version or
+ /// HTTP status code (used for error logging).
+ /// @param [out] storage Pointer to a number holding current product of
+ /// parsing major or minor version number.
+ void numberHandler(const char following_character,
+ const unsigned int next_state,
+ const std::string& number_name,
+ unsigned int* const storage);
+
+ /// @brief Handler for HTTP_PHRASE_START_ST.
+ void phraseStartHandler();
+
+ /// @brief Handler for HTTP_PHRASE_ST.
+ void phraseHandler();
+
+ /// @brief Handler for states related to new lines.
+ ///
+ /// If the next_state is HTTP_PARSE_OK_ST it indicates that the parsed
+ /// value is a 3rd new line within request HTTP message. In this case the
+ /// handler calls @ref HttpRequest::create to validate the received message
+ /// (excluding body). The handler then reads the "Content-Length" header to
+ /// check if the request contains a body. If the "Content-Length" is greater
+ /// than zero, the parser transitions to HTTP_BODY_ST. If the
+ /// "Content-Length" doesn't exist the parser transitions to
+ /// HTTP_PARSE_OK_ST.
+ ///
+ /// @param next_state A state to which parser should transition.
+ void expectingNewLineHandler(const unsigned int next_state);
+
+ /// @brief Handler for HEADER_LINE_START_ST.
+ void headerLineStartHandler();
+
+ /// @brief Handler for HEADER_LWS_ST.
+ void headerLwsHandler();
+
+ /// @brief Handler for HEADER_NAME_ST.
+ void headerNameHandler();
+
+ /// @brief Handler for SPACE_BEFORE_HEADER_VALUE_ST.
+ void spaceBeforeHeaderValueHandler();
+
+ /// @brief Handler for HEADER_VALUE_ST.
+ void headerValueHandler();
+
+ /// @brief Handler for HTTP_BODY_ST.
+ void bodyHandler();
+
+ //@}
+
+ /// @brief Reference to the response object specified in the constructor.
+ HttpResponse& response_;
+
+ /// @brief Pointer to the internal context of the @ref HttpResponse object.
+ HttpResponseContextPtr context_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/tests/Makefile.am b/src/lib/http/tests/Makefile.am
new file mode 100644
index 0000000..c440fad
--- /dev/null
+++ b/src/lib/http/tests/Makefile.am
@@ -0,0 +1,78 @@
+SUBDIRS = .
+
+# Add to the tarball:
+EXTRA_DIST = testdata/empty
+EXTRA_DIST += testdata/hiddenp
+EXTRA_DIST += testdata/hiddens
+EXTRA_DIST += testdata/hiddenu
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+TEST_CA_DIR = $(srcdir)/../../asiolink/testutils/ca
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(TEST_CA_DIR)\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+AM_CPPFLAGS += -DDATA_DIR=\"$(abs_srcdir)/testdata\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += libhttp_unittests
+
+libhttp_unittests_SOURCES = basic_auth_unittests.cc
+libhttp_unittests_SOURCES += basic_auth_config_unittests.cc
+libhttp_unittests_SOURCES += connection_pool_unittests.cc
+libhttp_unittests_SOURCES += date_time_unittests.cc
+libhttp_unittests_SOURCES += http_header_unittests.cc
+libhttp_unittests_SOURCES += post_request_unittests.cc
+libhttp_unittests_SOURCES += post_request_json_unittests.cc
+libhttp_unittests_SOURCES += request_parser_unittests.cc
+libhttp_unittests_SOURCES += request_test.h
+libhttp_unittests_SOURCES += response_creator_unittests.cc
+libhttp_unittests_SOURCES += response_parser_unittests.cc
+libhttp_unittests_SOURCES += response_test.h
+libhttp_unittests_SOURCES += request_unittests.cc
+libhttp_unittests_SOURCES += response_unittests.cc
+libhttp_unittests_SOURCES += response_json_unittests.cc
+libhttp_unittests_SOURCES += run_unittests.cc
+libhttp_unittests_SOURCES += server_client_unittests.cc
+if HAVE_OPENSSL
+libhttp_unittests_SOURCES += tls_server_unittests.cc
+libhttp_unittests_SOURCES += tls_client_unittests.cc
+endif
+if HAVE_BOTAN_BOOST
+libhttp_unittests_SOURCES += tls_server_unittests.cc
+libhttp_unittests_SOURCES += tls_client_unittests.cc
+endif
+libhttp_unittests_SOURCES += url_unittests.cc
+libhttp_unittests_SOURCES += test_http_client.h
+libhttp_unittests_SOURCES += client_mt_unittests.cc
+libhttp_unittests_SOURCES += http_thread_pool_unittests.cc
+
+libhttp_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libhttp_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+libhttp_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+libhttp_unittests_LDADD = $(top_builddir)/src/lib/http/libkea-http.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libhttp_unittests_LDADD += $(LOG4CPLUS_LIBS)
+libhttp_unittests_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS) $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/http/tests/Makefile.in b/src/lib/http/tests/Makefile.in
new file mode 100644
index 0000000..a1a5875
--- /dev/null
+++ b/src/lib/http/tests/Makefile.in
@@ -0,0 +1,1390 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+TESTS = $(am__EXEEXT_1)
+@HAVE_GTEST_TRUE@am__append_1 = libhttp_unittests
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@am__append_2 = \
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@ tls_server_unittests.cc \
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@ tls_client_unittests.cc
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@am__append_3 = tls_server_unittests.cc \
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@ tls_client_unittests.cc
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/http/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@HAVE_GTEST_TRUE@am__EXEEXT_1 = libhttp_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__libhttp_unittests_SOURCES_DIST = basic_auth_unittests.cc \
+ basic_auth_config_unittests.cc connection_pool_unittests.cc \
+ date_time_unittests.cc http_header_unittests.cc \
+ post_request_unittests.cc post_request_json_unittests.cc \
+ request_parser_unittests.cc request_test.h \
+ response_creator_unittests.cc response_parser_unittests.cc \
+ response_test.h request_unittests.cc response_unittests.cc \
+ response_json_unittests.cc run_unittests.cc \
+ server_client_unittests.cc tls_server_unittests.cc \
+ tls_client_unittests.cc url_unittests.cc test_http_client.h \
+ client_mt_unittests.cc http_thread_pool_unittests.cc
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@am__objects_1 = libhttp_unittests-tls_server_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@ libhttp_unittests-tls_client_unittests.$(OBJEXT)
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@am__objects_2 = libhttp_unittests-tls_server_unittests.$(OBJEXT) \
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@ libhttp_unittests-tls_client_unittests.$(OBJEXT)
+@HAVE_GTEST_TRUE@am_libhttp_unittests_OBJECTS = libhttp_unittests-basic_auth_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-basic_auth_config_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-connection_pool_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-date_time_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-http_header_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-post_request_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-post_request_json_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-request_parser_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-response_creator_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-response_parser_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-request_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-response_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-response_json_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-server_client_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ $(am__objects_1) $(am__objects_2) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-url_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-client_mt_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-http_thread_pool_unittests.$(OBJEXT)
+libhttp_unittests_OBJECTS = $(am_libhttp_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libhttp_unittests_DEPENDENCIES = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libhttp_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) \
+ $(libhttp_unittests_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = \
+ ./$(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-basic_auth_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-client_mt_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-connection_pool_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-date_time_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-http_header_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-http_thread_pool_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-post_request_json_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-post_request_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-request_parser_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-request_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-response_creator_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-response_json_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-response_parser_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-response_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-run_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-server_client_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-tls_client_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-tls_server_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-url_unittests.Po
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libhttp_unittests_SOURCES)
+DIST_SOURCES = $(am__libhttp_unittests_SOURCES_DIST)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+
+# Add to the tarball:
+EXTRA_DIST = testdata/empty testdata/hiddenp testdata/hiddens \
+ testdata/hiddenu
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES) \
+ -DTEST_DATA_BUILDDIR=\"$(TEST_CA_DIR)\" \
+ -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" \
+ -DDATA_DIR=\"$(abs_srcdir)/testdata\"
+TEST_CA_DIR = $(srcdir)/../../asiolink/testutils/ca
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+@HAVE_GTEST_TRUE@libhttp_unittests_SOURCES = basic_auth_unittests.cc \
+@HAVE_GTEST_TRUE@ basic_auth_config_unittests.cc \
+@HAVE_GTEST_TRUE@ connection_pool_unittests.cc \
+@HAVE_GTEST_TRUE@ date_time_unittests.cc \
+@HAVE_GTEST_TRUE@ http_header_unittests.cc \
+@HAVE_GTEST_TRUE@ post_request_unittests.cc \
+@HAVE_GTEST_TRUE@ post_request_json_unittests.cc \
+@HAVE_GTEST_TRUE@ request_parser_unittests.cc request_test.h \
+@HAVE_GTEST_TRUE@ response_creator_unittests.cc \
+@HAVE_GTEST_TRUE@ response_parser_unittests.cc response_test.h \
+@HAVE_GTEST_TRUE@ request_unittests.cc response_unittests.cc \
+@HAVE_GTEST_TRUE@ response_json_unittests.cc run_unittests.cc \
+@HAVE_GTEST_TRUE@ server_client_unittests.cc $(am__append_2) \
+@HAVE_GTEST_TRUE@ $(am__append_3) url_unittests.cc \
+@HAVE_GTEST_TRUE@ test_http_client.h client_mt_unittests.cc \
+@HAVE_GTEST_TRUE@ http_thread_pool_unittests.cc
+@HAVE_GTEST_TRUE@libhttp_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libhttp_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libhttp_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@libhttp_unittests_LDADD = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) \
+@HAVE_GTEST_TRUE@ $(CRYPTO_LIBS) $(GTEST_LDADD)
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/http/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/http/tests/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+libhttp_unittests$(EXEEXT): $(libhttp_unittests_OBJECTS) $(libhttp_unittests_DEPENDENCIES) $(EXTRA_libhttp_unittests_DEPENDENCIES)
+ @rm -f libhttp_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libhttp_unittests_LINK) $(libhttp_unittests_OBJECTS) $(libhttp_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-basic_auth_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-client_mt_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-connection_pool_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-date_time_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-http_header_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-http_thread_pool_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-post_request_json_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-post_request_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-request_parser_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-request_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-response_creator_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-response_json_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-response_parser_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-response_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-run_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-server_client_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-tls_client_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-tls_server_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-url_unittests.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+libhttp_unittests-basic_auth_unittests.o: basic_auth_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-basic_auth_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-basic_auth_unittests.Tpo -c -o libhttp_unittests-basic_auth_unittests.o `test -f 'basic_auth_unittests.cc' || echo '$(srcdir)/'`basic_auth_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-basic_auth_unittests.Tpo $(DEPDIR)/libhttp_unittests-basic_auth_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_auth_unittests.cc' object='libhttp_unittests-basic_auth_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-basic_auth_unittests.o `test -f 'basic_auth_unittests.cc' || echo '$(srcdir)/'`basic_auth_unittests.cc
+
+libhttp_unittests-basic_auth_unittests.obj: basic_auth_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-basic_auth_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-basic_auth_unittests.Tpo -c -o libhttp_unittests-basic_auth_unittests.obj `if test -f 'basic_auth_unittests.cc'; then $(CYGPATH_W) 'basic_auth_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/basic_auth_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-basic_auth_unittests.Tpo $(DEPDIR)/libhttp_unittests-basic_auth_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_auth_unittests.cc' object='libhttp_unittests-basic_auth_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-basic_auth_unittests.obj `if test -f 'basic_auth_unittests.cc'; then $(CYGPATH_W) 'basic_auth_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/basic_auth_unittests.cc'; fi`
+
+libhttp_unittests-basic_auth_config_unittests.o: basic_auth_config_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-basic_auth_config_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Tpo -c -o libhttp_unittests-basic_auth_config_unittests.o `test -f 'basic_auth_config_unittests.cc' || echo '$(srcdir)/'`basic_auth_config_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Tpo $(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_auth_config_unittests.cc' object='libhttp_unittests-basic_auth_config_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-basic_auth_config_unittests.o `test -f 'basic_auth_config_unittests.cc' || echo '$(srcdir)/'`basic_auth_config_unittests.cc
+
+libhttp_unittests-basic_auth_config_unittests.obj: basic_auth_config_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-basic_auth_config_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Tpo -c -o libhttp_unittests-basic_auth_config_unittests.obj `if test -f 'basic_auth_config_unittests.cc'; then $(CYGPATH_W) 'basic_auth_config_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/basic_auth_config_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Tpo $(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_auth_config_unittests.cc' object='libhttp_unittests-basic_auth_config_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-basic_auth_config_unittests.obj `if test -f 'basic_auth_config_unittests.cc'; then $(CYGPATH_W) 'basic_auth_config_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/basic_auth_config_unittests.cc'; fi`
+
+libhttp_unittests-connection_pool_unittests.o: connection_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-connection_pool_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-connection_pool_unittests.Tpo -c -o libhttp_unittests-connection_pool_unittests.o `test -f 'connection_pool_unittests.cc' || echo '$(srcdir)/'`connection_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-connection_pool_unittests.Tpo $(DEPDIR)/libhttp_unittests-connection_pool_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='connection_pool_unittests.cc' object='libhttp_unittests-connection_pool_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-connection_pool_unittests.o `test -f 'connection_pool_unittests.cc' || echo '$(srcdir)/'`connection_pool_unittests.cc
+
+libhttp_unittests-connection_pool_unittests.obj: connection_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-connection_pool_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-connection_pool_unittests.Tpo -c -o libhttp_unittests-connection_pool_unittests.obj `if test -f 'connection_pool_unittests.cc'; then $(CYGPATH_W) 'connection_pool_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/connection_pool_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-connection_pool_unittests.Tpo $(DEPDIR)/libhttp_unittests-connection_pool_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='connection_pool_unittests.cc' object='libhttp_unittests-connection_pool_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-connection_pool_unittests.obj `if test -f 'connection_pool_unittests.cc'; then $(CYGPATH_W) 'connection_pool_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/connection_pool_unittests.cc'; fi`
+
+libhttp_unittests-date_time_unittests.o: date_time_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-date_time_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-date_time_unittests.Tpo -c -o libhttp_unittests-date_time_unittests.o `test -f 'date_time_unittests.cc' || echo '$(srcdir)/'`date_time_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-date_time_unittests.Tpo $(DEPDIR)/libhttp_unittests-date_time_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='date_time_unittests.cc' object='libhttp_unittests-date_time_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-date_time_unittests.o `test -f 'date_time_unittests.cc' || echo '$(srcdir)/'`date_time_unittests.cc
+
+libhttp_unittests-date_time_unittests.obj: date_time_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-date_time_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-date_time_unittests.Tpo -c -o libhttp_unittests-date_time_unittests.obj `if test -f 'date_time_unittests.cc'; then $(CYGPATH_W) 'date_time_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/date_time_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-date_time_unittests.Tpo $(DEPDIR)/libhttp_unittests-date_time_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='date_time_unittests.cc' object='libhttp_unittests-date_time_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-date_time_unittests.obj `if test -f 'date_time_unittests.cc'; then $(CYGPATH_W) 'date_time_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/date_time_unittests.cc'; fi`
+
+libhttp_unittests-http_header_unittests.o: http_header_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-http_header_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-http_header_unittests.Tpo -c -o libhttp_unittests-http_header_unittests.o `test -f 'http_header_unittests.cc' || echo '$(srcdir)/'`http_header_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-http_header_unittests.Tpo $(DEPDIR)/libhttp_unittests-http_header_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_header_unittests.cc' object='libhttp_unittests-http_header_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-http_header_unittests.o `test -f 'http_header_unittests.cc' || echo '$(srcdir)/'`http_header_unittests.cc
+
+libhttp_unittests-http_header_unittests.obj: http_header_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-http_header_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-http_header_unittests.Tpo -c -o libhttp_unittests-http_header_unittests.obj `if test -f 'http_header_unittests.cc'; then $(CYGPATH_W) 'http_header_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/http_header_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-http_header_unittests.Tpo $(DEPDIR)/libhttp_unittests-http_header_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_header_unittests.cc' object='libhttp_unittests-http_header_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-http_header_unittests.obj `if test -f 'http_header_unittests.cc'; then $(CYGPATH_W) 'http_header_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/http_header_unittests.cc'; fi`
+
+libhttp_unittests-post_request_unittests.o: post_request_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-post_request_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-post_request_unittests.Tpo -c -o libhttp_unittests-post_request_unittests.o `test -f 'post_request_unittests.cc' || echo '$(srcdir)/'`post_request_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-post_request_unittests.Tpo $(DEPDIR)/libhttp_unittests-post_request_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='post_request_unittests.cc' object='libhttp_unittests-post_request_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-post_request_unittests.o `test -f 'post_request_unittests.cc' || echo '$(srcdir)/'`post_request_unittests.cc
+
+libhttp_unittests-post_request_unittests.obj: post_request_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-post_request_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-post_request_unittests.Tpo -c -o libhttp_unittests-post_request_unittests.obj `if test -f 'post_request_unittests.cc'; then $(CYGPATH_W) 'post_request_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/post_request_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-post_request_unittests.Tpo $(DEPDIR)/libhttp_unittests-post_request_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='post_request_unittests.cc' object='libhttp_unittests-post_request_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-post_request_unittests.obj `if test -f 'post_request_unittests.cc'; then $(CYGPATH_W) 'post_request_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/post_request_unittests.cc'; fi`
+
+libhttp_unittests-post_request_json_unittests.o: post_request_json_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-post_request_json_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-post_request_json_unittests.Tpo -c -o libhttp_unittests-post_request_json_unittests.o `test -f 'post_request_json_unittests.cc' || echo '$(srcdir)/'`post_request_json_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-post_request_json_unittests.Tpo $(DEPDIR)/libhttp_unittests-post_request_json_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='post_request_json_unittests.cc' object='libhttp_unittests-post_request_json_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-post_request_json_unittests.o `test -f 'post_request_json_unittests.cc' || echo '$(srcdir)/'`post_request_json_unittests.cc
+
+libhttp_unittests-post_request_json_unittests.obj: post_request_json_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-post_request_json_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-post_request_json_unittests.Tpo -c -o libhttp_unittests-post_request_json_unittests.obj `if test -f 'post_request_json_unittests.cc'; then $(CYGPATH_W) 'post_request_json_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/post_request_json_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-post_request_json_unittests.Tpo $(DEPDIR)/libhttp_unittests-post_request_json_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='post_request_json_unittests.cc' object='libhttp_unittests-post_request_json_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-post_request_json_unittests.obj `if test -f 'post_request_json_unittests.cc'; then $(CYGPATH_W) 'post_request_json_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/post_request_json_unittests.cc'; fi`
+
+libhttp_unittests-request_parser_unittests.o: request_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-request_parser_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-request_parser_unittests.Tpo -c -o libhttp_unittests-request_parser_unittests.o `test -f 'request_parser_unittests.cc' || echo '$(srcdir)/'`request_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-request_parser_unittests.Tpo $(DEPDIR)/libhttp_unittests-request_parser_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='request_parser_unittests.cc' object='libhttp_unittests-request_parser_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-request_parser_unittests.o `test -f 'request_parser_unittests.cc' || echo '$(srcdir)/'`request_parser_unittests.cc
+
+libhttp_unittests-request_parser_unittests.obj: request_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-request_parser_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-request_parser_unittests.Tpo -c -o libhttp_unittests-request_parser_unittests.obj `if test -f 'request_parser_unittests.cc'; then $(CYGPATH_W) 'request_parser_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/request_parser_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-request_parser_unittests.Tpo $(DEPDIR)/libhttp_unittests-request_parser_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='request_parser_unittests.cc' object='libhttp_unittests-request_parser_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-request_parser_unittests.obj `if test -f 'request_parser_unittests.cc'; then $(CYGPATH_W) 'request_parser_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/request_parser_unittests.cc'; fi`
+
+libhttp_unittests-response_creator_unittests.o: response_creator_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_creator_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_creator_unittests.Tpo -c -o libhttp_unittests-response_creator_unittests.o `test -f 'response_creator_unittests.cc' || echo '$(srcdir)/'`response_creator_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_creator_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_creator_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_creator_unittests.cc' object='libhttp_unittests-response_creator_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_creator_unittests.o `test -f 'response_creator_unittests.cc' || echo '$(srcdir)/'`response_creator_unittests.cc
+
+libhttp_unittests-response_creator_unittests.obj: response_creator_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_creator_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_creator_unittests.Tpo -c -o libhttp_unittests-response_creator_unittests.obj `if test -f 'response_creator_unittests.cc'; then $(CYGPATH_W) 'response_creator_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_creator_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_creator_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_creator_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_creator_unittests.cc' object='libhttp_unittests-response_creator_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_creator_unittests.obj `if test -f 'response_creator_unittests.cc'; then $(CYGPATH_W) 'response_creator_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_creator_unittests.cc'; fi`
+
+libhttp_unittests-response_parser_unittests.o: response_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_parser_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_parser_unittests.Tpo -c -o libhttp_unittests-response_parser_unittests.o `test -f 'response_parser_unittests.cc' || echo '$(srcdir)/'`response_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_parser_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_parser_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_parser_unittests.cc' object='libhttp_unittests-response_parser_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_parser_unittests.o `test -f 'response_parser_unittests.cc' || echo '$(srcdir)/'`response_parser_unittests.cc
+
+libhttp_unittests-response_parser_unittests.obj: response_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_parser_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_parser_unittests.Tpo -c -o libhttp_unittests-response_parser_unittests.obj `if test -f 'response_parser_unittests.cc'; then $(CYGPATH_W) 'response_parser_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_parser_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_parser_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_parser_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_parser_unittests.cc' object='libhttp_unittests-response_parser_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_parser_unittests.obj `if test -f 'response_parser_unittests.cc'; then $(CYGPATH_W) 'response_parser_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_parser_unittests.cc'; fi`
+
+libhttp_unittests-request_unittests.o: request_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-request_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-request_unittests.Tpo -c -o libhttp_unittests-request_unittests.o `test -f 'request_unittests.cc' || echo '$(srcdir)/'`request_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-request_unittests.Tpo $(DEPDIR)/libhttp_unittests-request_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='request_unittests.cc' object='libhttp_unittests-request_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-request_unittests.o `test -f 'request_unittests.cc' || echo '$(srcdir)/'`request_unittests.cc
+
+libhttp_unittests-request_unittests.obj: request_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-request_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-request_unittests.Tpo -c -o libhttp_unittests-request_unittests.obj `if test -f 'request_unittests.cc'; then $(CYGPATH_W) 'request_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/request_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-request_unittests.Tpo $(DEPDIR)/libhttp_unittests-request_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='request_unittests.cc' object='libhttp_unittests-request_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-request_unittests.obj `if test -f 'request_unittests.cc'; then $(CYGPATH_W) 'request_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/request_unittests.cc'; fi`
+
+libhttp_unittests-response_unittests.o: response_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_unittests.Tpo -c -o libhttp_unittests-response_unittests.o `test -f 'response_unittests.cc' || echo '$(srcdir)/'`response_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_unittests.cc' object='libhttp_unittests-response_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_unittests.o `test -f 'response_unittests.cc' || echo '$(srcdir)/'`response_unittests.cc
+
+libhttp_unittests-response_unittests.obj: response_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_unittests.Tpo -c -o libhttp_unittests-response_unittests.obj `if test -f 'response_unittests.cc'; then $(CYGPATH_W) 'response_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_unittests.cc' object='libhttp_unittests-response_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_unittests.obj `if test -f 'response_unittests.cc'; then $(CYGPATH_W) 'response_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_unittests.cc'; fi`
+
+libhttp_unittests-response_json_unittests.o: response_json_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_json_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_json_unittests.Tpo -c -o libhttp_unittests-response_json_unittests.o `test -f 'response_json_unittests.cc' || echo '$(srcdir)/'`response_json_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_json_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_json_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_json_unittests.cc' object='libhttp_unittests-response_json_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_json_unittests.o `test -f 'response_json_unittests.cc' || echo '$(srcdir)/'`response_json_unittests.cc
+
+libhttp_unittests-response_json_unittests.obj: response_json_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_json_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_json_unittests.Tpo -c -o libhttp_unittests-response_json_unittests.obj `if test -f 'response_json_unittests.cc'; then $(CYGPATH_W) 'response_json_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_json_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_json_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_json_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_json_unittests.cc' object='libhttp_unittests-response_json_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_json_unittests.obj `if test -f 'response_json_unittests.cc'; then $(CYGPATH_W) 'response_json_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_json_unittests.cc'; fi`
+
+libhttp_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-run_unittests.Tpo -c -o libhttp_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-run_unittests.Tpo $(DEPDIR)/libhttp_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libhttp_unittests-run_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libhttp_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-run_unittests.Tpo -c -o libhttp_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-run_unittests.Tpo $(DEPDIR)/libhttp_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libhttp_unittests-run_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+
+libhttp_unittests-server_client_unittests.o: server_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-server_client_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-server_client_unittests.Tpo -c -o libhttp_unittests-server_client_unittests.o `test -f 'server_client_unittests.cc' || echo '$(srcdir)/'`server_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-server_client_unittests.Tpo $(DEPDIR)/libhttp_unittests-server_client_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_client_unittests.cc' object='libhttp_unittests-server_client_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-server_client_unittests.o `test -f 'server_client_unittests.cc' || echo '$(srcdir)/'`server_client_unittests.cc
+
+libhttp_unittests-server_client_unittests.obj: server_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-server_client_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-server_client_unittests.Tpo -c -o libhttp_unittests-server_client_unittests.obj `if test -f 'server_client_unittests.cc'; then $(CYGPATH_W) 'server_client_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/server_client_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-server_client_unittests.Tpo $(DEPDIR)/libhttp_unittests-server_client_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_client_unittests.cc' object='libhttp_unittests-server_client_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-server_client_unittests.obj `if test -f 'server_client_unittests.cc'; then $(CYGPATH_W) 'server_client_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/server_client_unittests.cc'; fi`
+
+libhttp_unittests-tls_server_unittests.o: tls_server_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-tls_server_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-tls_server_unittests.Tpo -c -o libhttp_unittests-tls_server_unittests.o `test -f 'tls_server_unittests.cc' || echo '$(srcdir)/'`tls_server_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-tls_server_unittests.Tpo $(DEPDIR)/libhttp_unittests-tls_server_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_server_unittests.cc' object='libhttp_unittests-tls_server_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-tls_server_unittests.o `test -f 'tls_server_unittests.cc' || echo '$(srcdir)/'`tls_server_unittests.cc
+
+libhttp_unittests-tls_server_unittests.obj: tls_server_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-tls_server_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-tls_server_unittests.Tpo -c -o libhttp_unittests-tls_server_unittests.obj `if test -f 'tls_server_unittests.cc'; then $(CYGPATH_W) 'tls_server_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/tls_server_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-tls_server_unittests.Tpo $(DEPDIR)/libhttp_unittests-tls_server_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_server_unittests.cc' object='libhttp_unittests-tls_server_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-tls_server_unittests.obj `if test -f 'tls_server_unittests.cc'; then $(CYGPATH_W) 'tls_server_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/tls_server_unittests.cc'; fi`
+
+libhttp_unittests-tls_client_unittests.o: tls_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-tls_client_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-tls_client_unittests.Tpo -c -o libhttp_unittests-tls_client_unittests.o `test -f 'tls_client_unittests.cc' || echo '$(srcdir)/'`tls_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-tls_client_unittests.Tpo $(DEPDIR)/libhttp_unittests-tls_client_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_client_unittests.cc' object='libhttp_unittests-tls_client_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-tls_client_unittests.o `test -f 'tls_client_unittests.cc' || echo '$(srcdir)/'`tls_client_unittests.cc
+
+libhttp_unittests-tls_client_unittests.obj: tls_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-tls_client_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-tls_client_unittests.Tpo -c -o libhttp_unittests-tls_client_unittests.obj `if test -f 'tls_client_unittests.cc'; then $(CYGPATH_W) 'tls_client_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/tls_client_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-tls_client_unittests.Tpo $(DEPDIR)/libhttp_unittests-tls_client_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_client_unittests.cc' object='libhttp_unittests-tls_client_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-tls_client_unittests.obj `if test -f 'tls_client_unittests.cc'; then $(CYGPATH_W) 'tls_client_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/tls_client_unittests.cc'; fi`
+
+libhttp_unittests-url_unittests.o: url_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-url_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-url_unittests.Tpo -c -o libhttp_unittests-url_unittests.o `test -f 'url_unittests.cc' || echo '$(srcdir)/'`url_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-url_unittests.Tpo $(DEPDIR)/libhttp_unittests-url_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='url_unittests.cc' object='libhttp_unittests-url_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-url_unittests.o `test -f 'url_unittests.cc' || echo '$(srcdir)/'`url_unittests.cc
+
+libhttp_unittests-url_unittests.obj: url_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-url_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-url_unittests.Tpo -c -o libhttp_unittests-url_unittests.obj `if test -f 'url_unittests.cc'; then $(CYGPATH_W) 'url_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/url_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-url_unittests.Tpo $(DEPDIR)/libhttp_unittests-url_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='url_unittests.cc' object='libhttp_unittests-url_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-url_unittests.obj `if test -f 'url_unittests.cc'; then $(CYGPATH_W) 'url_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/url_unittests.cc'; fi`
+
+libhttp_unittests-client_mt_unittests.o: client_mt_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-client_mt_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-client_mt_unittests.Tpo -c -o libhttp_unittests-client_mt_unittests.o `test -f 'client_mt_unittests.cc' || echo '$(srcdir)/'`client_mt_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-client_mt_unittests.Tpo $(DEPDIR)/libhttp_unittests-client_mt_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_mt_unittests.cc' object='libhttp_unittests-client_mt_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-client_mt_unittests.o `test -f 'client_mt_unittests.cc' || echo '$(srcdir)/'`client_mt_unittests.cc
+
+libhttp_unittests-client_mt_unittests.obj: client_mt_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-client_mt_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-client_mt_unittests.Tpo -c -o libhttp_unittests-client_mt_unittests.obj `if test -f 'client_mt_unittests.cc'; then $(CYGPATH_W) 'client_mt_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/client_mt_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-client_mt_unittests.Tpo $(DEPDIR)/libhttp_unittests-client_mt_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_mt_unittests.cc' object='libhttp_unittests-client_mt_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-client_mt_unittests.obj `if test -f 'client_mt_unittests.cc'; then $(CYGPATH_W) 'client_mt_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/client_mt_unittests.cc'; fi`
+
+libhttp_unittests-http_thread_pool_unittests.o: http_thread_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-http_thread_pool_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-http_thread_pool_unittests.Tpo -c -o libhttp_unittests-http_thread_pool_unittests.o `test -f 'http_thread_pool_unittests.cc' || echo '$(srcdir)/'`http_thread_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-http_thread_pool_unittests.Tpo $(DEPDIR)/libhttp_unittests-http_thread_pool_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_thread_pool_unittests.cc' object='libhttp_unittests-http_thread_pool_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-http_thread_pool_unittests.o `test -f 'http_thread_pool_unittests.cc' || echo '$(srcdir)/'`http_thread_pool_unittests.cc
+
+libhttp_unittests-http_thread_pool_unittests.obj: http_thread_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-http_thread_pool_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-http_thread_pool_unittests.Tpo -c -o libhttp_unittests-http_thread_pool_unittests.obj `if test -f 'http_thread_pool_unittests.cc'; then $(CYGPATH_W) 'http_thread_pool_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/http_thread_pool_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-http_thread_pool_unittests.Tpo $(DEPDIR)/libhttp_unittests-http_thread_pool_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_thread_pool_unittests.cc' object='libhttp_unittests-http_thread_pool_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-http_thread_pool_unittests.obj `if test -f 'http_thread_pool_unittests.cc'; then $(CYGPATH_W) 'http_thread_pool_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/http_thread_pool_unittests.cc'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+check-TESTS: $(TESTS)
+ @failed=0; all=0; xfail=0; xpass=0; skip=0; \
+ srcdir=$(srcdir); export srcdir; \
+ list=' $(TESTS) '; \
+ $(am__tty_colors); \
+ if test -n "$$list"; then \
+ for tst in $$list; do \
+ if test -f ./$$tst; then dir=./; \
+ elif test -f $$tst; then dir=; \
+ else dir="$(srcdir)/"; fi; \
+ if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xpass=`expr $$xpass + 1`; \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=XPASS; \
+ ;; \
+ *) \
+ col=$$grn; res=PASS; \
+ ;; \
+ esac; \
+ elif test $$? -ne 77; then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xfail=`expr $$xfail + 1`; \
+ col=$$lgn; res=XFAIL; \
+ ;; \
+ *) \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=FAIL; \
+ ;; \
+ esac; \
+ else \
+ skip=`expr $$skip + 1`; \
+ col=$$blu; res=SKIP; \
+ fi; \
+ echo "$${col}$$res$${std}: $$tst"; \
+ done; \
+ if test "$$all" -eq 1; then \
+ tests="test"; \
+ All=""; \
+ else \
+ tests="tests"; \
+ All="All "; \
+ fi; \
+ if test "$$failed" -eq 0; then \
+ if test "$$xfail" -eq 0; then \
+ banner="$$All$$all $$tests passed"; \
+ else \
+ if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
+ banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
+ fi; \
+ else \
+ if test "$$xpass" -eq 0; then \
+ banner="$$failed of $$all $$tests failed"; \
+ else \
+ if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
+ banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+ fi; \
+ fi; \
+ dashes="$$banner"; \
+ skipped=""; \
+ if test "$$skip" -ne 0; then \
+ if test "$$skip" -eq 1; then \
+ skipped="($$skip test was not run)"; \
+ else \
+ skipped="($$skip tests were not run)"; \
+ fi; \
+ test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$skipped"; \
+ fi; \
+ report=""; \
+ if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
+ report="Please report to $(PACKAGE_BUGREPORT)"; \
+ test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$report"; \
+ fi; \
+ dashes=`echo "$$dashes" | sed s/./=/g`; \
+ if test "$$failed" -eq 0; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ fi; \
+ echo "$${col}$$dashes$${std}"; \
+ echo "$${col}$$banner$${std}"; \
+ test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
+ test -z "$$report" || echo "$${col}$$report$${std}"; \
+ echo "$${col}$$dashes$${std}"; \
+ test "$$failed" -eq 0; \
+ else :; fi
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile $(PROGRAMS)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-basic_auth_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-client_mt_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-connection_pool_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-date_time_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-http_header_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-http_thread_pool_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-post_request_json_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-post_request_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-request_parser_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-request_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_creator_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_json_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_parser_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-server_client_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-tls_client_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-tls_server_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-url_unittests.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-basic_auth_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-client_mt_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-connection_pool_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-date_time_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-http_header_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-http_thread_pool_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-post_request_json_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-post_request_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-request_parser_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-request_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_creator_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_json_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_parser_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-server_client_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-tls_client_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-tls_server_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-url_unittests.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-TESTS check-am clean clean-generic \
+ clean-libtool clean-noinstPROGRAMS cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/http/tests/basic_auth_config_unittests.cc b/src/lib/http/tests/basic_auth_config_unittests.cc
new file mode 100644
index 0000000..5df2170
--- /dev/null
+++ b/src/lib/http/tests/basic_auth_config_unittests.cc
@@ -0,0 +1,540 @@
+// Copyright (C) 2020-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <http/basic_auth_config.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::http;
+using namespace isc::test;
+using namespace std;
+
+namespace {
+
+string data_dir(DATA_DIR);
+
+// Test that basic auth client works as expected.
+TEST(BasicHttpAuthClientTest, basic) {
+ // Create a client.
+ ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+ BasicHttpAuthClient client("foo", "bar", ctx);
+
+ // Check it.
+ EXPECT_EQ("foo", client.getUser());
+ EXPECT_EQ("", client.getUserFile());
+ EXPECT_EQ("bar", client.getPassword());
+ EXPECT_EQ("", client.getPasswordFile());
+ EXPECT_FALSE(client.getPasswordFileOnly());
+ EXPECT_TRUE(ctx->equals(*client.getContext()));
+
+ // Check toElement.
+ ElementPtr expected = Element::createMap();
+ expected->set("user", Element::create(string("foo")));
+ expected->set("password", Element::create(string("bar")));
+ expected->set("user-context", ctx);
+ runToElementTest<BasicHttpAuthClient>(expected, client);
+}
+
+// Test that basic auth client with files works as expected.
+TEST(BasicHttpAuthClientTest, basicFiles) {
+ // Create a client.
+ ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+ BasicHttpAuthClient client("", "foo", "", "bar", false, ctx);
+
+ // Check it.
+ EXPECT_EQ("", client.getUser());
+ EXPECT_EQ("foo", client.getUserFile());
+ EXPECT_EQ("", client.getPassword());
+ EXPECT_EQ("bar", client.getPasswordFile());
+ EXPECT_FALSE(client.getPasswordFileOnly());
+ EXPECT_TRUE(ctx->equals(*client.getContext()));
+
+ // Check toElement.
+ ElementPtr expected = Element::createMap();
+ expected->set("user-file", Element::create(string("foo")));
+ expected->set("password-file", Element::create(string("bar")));
+ expected->set("user-context", ctx);
+ runToElementTest<BasicHttpAuthClient>(expected, client);
+}
+
+// Test that basic auth client with one file works as expected.
+TEST(BasicHttpAuthClientTest, basicOneFile) {
+ // Create a client.
+ ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+ BasicHttpAuthClient client("", "", "", "foobar", true, ctx);
+
+ // Check it.
+ EXPECT_EQ("", client.getUser());
+ EXPECT_EQ("", client.getUserFile());
+ EXPECT_EQ("", client.getPassword());
+ EXPECT_EQ("foobar", client.getPasswordFile());
+ EXPECT_TRUE(client.getPasswordFileOnly());
+ EXPECT_TRUE(ctx->equals(*client.getContext()));
+
+ // Check toElement.
+ ElementPtr expected = Element::createMap();
+ expected->set("password-file", Element::create(string("foobar")));
+ expected->set("user-context", ctx);
+ runToElementTest<BasicHttpAuthClient>(expected, client);
+}
+
+// Test that basic auth configuration works as expected.
+TEST(BasicHttpAuthConfigTest, basic) {
+ // Create a configuration.
+ BasicHttpAuthConfig config;
+
+ // Initial configuration is empty.
+ EXPECT_TRUE(config.empty());
+ EXPECT_TRUE(config.getRealm().empty());
+ EXPECT_TRUE(config.getDirectory().empty());
+ EXPECT_TRUE(config.getClientList().empty());
+ EXPECT_TRUE(config.getCredentialMap().empty());
+
+ // Set the realm, directory and user context.
+ EXPECT_NO_THROW(config.setRealm("my-realm"));
+ EXPECT_EQ("my-realm", config.getRealm());
+ EXPECT_NO_THROW(config.setDirectory("/tmp"));
+ EXPECT_EQ("/tmp", config.getDirectory());
+ ConstElementPtr horse = Element::fromJSON("{ \"value\": \"a horse\" }");
+ EXPECT_NO_THROW(config.setContext(horse));
+ EXPECT_TRUE(horse->equals(*config.getContext()));
+
+ // Add rejects user id with embedded ':'.
+ EXPECT_THROW(config.add("foo:", "", "bar", ""), BadValue);
+
+ // Add a client.
+ EXPECT_TRUE(config.empty());
+ ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+ EXPECT_NO_THROW(config.add("foo", "", "bar", "", false, ctx));
+ EXPECT_FALSE(config.empty());
+
+ // Check the client.
+ ASSERT_EQ(1, config.getClientList().size());
+ const BasicHttpAuthClient& client = config.getClientList().front();
+ EXPECT_EQ("foo", client.getUser());
+ EXPECT_EQ("bar", client.getPassword());
+ EXPECT_TRUE(ctx->equals(*client.getContext()));
+
+ // Check the credential.
+ ASSERT_NE(0, config.getCredentialMap().count("Zm9vOmJhcg=="));
+ string user;
+ EXPECT_NO_THROW(user = config.getCredentialMap().at("Zm9vOmJhcg=="));
+ EXPECT_EQ("foo", user);
+
+ // Check toElement.
+ ElementPtr expected = Element::createMap();
+ ElementPtr clients = Element::createList();
+ ElementPtr elem = Element::createMap();
+ elem->set("user", Element::create(string("foo")));
+ elem->set("password", Element::create(string("bar")));
+ elem->set("user-context", ctx);
+ clients->add(elem);
+ expected->set("type", Element::create(string("basic")));
+ expected->set("realm", Element::create(string("my-realm")));
+ expected->set("directory", Element::create(string("/tmp")));
+ expected->set("user-context", horse);
+ expected->set("clients", clients);
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+
+ // Add a second client and test it.
+ EXPECT_NO_THROW(config.add("test", "", "123\xa3", ""));
+ ASSERT_EQ(2, config.getClientList().size());
+ EXPECT_EQ("foo", config.getClientList().front().getUser());
+ EXPECT_EQ("test", config.getClientList().back().getUser());
+ ASSERT_NE(0, config.getCredentialMap().count("dGVzdDoxMjPCow=="));
+
+ // Check clear.
+ config.clear();
+ EXPECT_TRUE(config.empty());
+ expected->set("clients", Element::createList());
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+
+ // Add clients again.
+ EXPECT_NO_THROW(config.add("test", "", "123\xa3", ""));
+ EXPECT_NO_THROW(config.add("foo", "", "bar", "", false, ctx));
+
+ // Check that toElement keeps add order.
+ ElementPtr elem0 = Element::createMap();
+ elem0->set("user", Element::create(string("test")));
+ elem0->set("password", Element::create(string("123\xa3")));
+ clients = Element::createList();
+ clients->add(elem0);
+ clients->add(elem);
+ expected->set("clients", clients);
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+}
+
+// Test that basic auth configuration with files works as expected.
+TEST(BasicHttpAuthConfigTest, basicFiles) {
+ // Create a configuration.
+ BasicHttpAuthConfig config;
+
+ // Set the realm, directory and user context.
+ EXPECT_NO_THROW(config.setRealm("my-realm"));
+ EXPECT_EQ("my-realm", config.getRealm());
+ EXPECT_NO_THROW(config.setDirectory(data_dir));
+ EXPECT_EQ(data_dir, config.getDirectory());
+ ConstElementPtr horse = Element::fromJSON("{ \"value\": \"a horse\" }");
+ EXPECT_NO_THROW(config.setContext(horse));
+ EXPECT_TRUE(horse->equals(*config.getContext()));
+
+ // ':' in user id check is done during parsing
+
+ // Add a client.
+ EXPECT_TRUE(config.empty());
+ ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+ EXPECT_NO_THROW(config.add("foo", "", "", "hiddenp", false, ctx));
+ EXPECT_FALSE(config.empty());
+
+ // Check the client.
+ ASSERT_EQ(1, config.getClientList().size());
+ const BasicHttpAuthClient& client = config.getClientList().front();
+ EXPECT_EQ("foo", client.getUser());
+ EXPECT_EQ("", client.getUserFile());
+ EXPECT_EQ("", client.getPassword());
+ EXPECT_EQ("hiddenp", client.getPasswordFile());
+ EXPECT_FALSE(client.getPasswordFileOnly());
+ EXPECT_TRUE(ctx->equals(*client.getContext()));
+
+ // Check toElement.
+ ElementPtr expected = Element::createMap();
+ ElementPtr clients = Element::createList();
+ ElementPtr elem = Element::createMap();
+ elem->set("user", Element::create(string("foo")));
+ elem->set("password-file", Element::create(string("hiddenp")));
+ elem->set("user-context", ctx);
+ clients->add(elem);
+ expected->set("type", Element::create(string("basic")));
+ expected->set("realm", Element::create(string("my-realm")));
+ expected->set("directory", Element::create(data_dir));
+ expected->set("user-context", horse);
+ expected->set("clients", clients);
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+
+ // Add a second client and test it.
+ EXPECT_NO_THROW(config.add("", "hiddenu", "", "hiddenp"));
+ ASSERT_EQ(2, config.getClientList().size());
+ EXPECT_EQ("foo", config.getClientList().front().getUser());
+ EXPECT_EQ("hiddenu", config.getClientList().back().getUserFile());
+
+ // Check clear.
+ config.clear();
+ EXPECT_TRUE(config.empty());
+ expected->set("clients", Element::createList());
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+
+ // Add clients again.
+ EXPECT_NO_THROW(config.add("", "hiddenu", "", "hiddenp"));
+ EXPECT_NO_THROW(config.add("foo", "", "", "hiddenp", false, ctx));
+
+ // Check that toElement keeps add order.
+ ElementPtr elem0 = Element::createMap();
+ elem0->set("user-file", Element::create(string("hiddenu")));
+ elem0->set("password-file", Element::create(string("hiddenp")));
+ clients = Element::createList();
+ clients->add(elem0);
+ clients->add(elem);
+ expected->set("clients", clients);
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+}
+
+// Test that basic auth configuration parses.
+TEST(BasicHttpAuthConfigTest, parse) {
+ BasicHttpAuthConfig config;
+ ElementPtr cfg;
+
+ // No config is accepted.
+ EXPECT_NO_THROW(config.parse(cfg));
+ EXPECT_TRUE(config.empty());
+ EXPECT_TRUE(config.getClientList().empty());
+ EXPECT_TRUE(config.getCredentialMap().empty());
+ ElementPtr expected = Element::createMap();
+ expected->set("type", Element::create(string("basic")));
+ expected->set("realm", Element::create(string("")));
+ expected->set("directory", Element::create(string("")));
+ expected->set("clients", Element::createList());
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+
+ // The config must be a map.
+ cfg = Element::createList();
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "authentication must be a map (:0:0)");
+
+ // The type must be present.
+ cfg = Element::createMap();
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "type is required in authentication (:0:0)");
+
+ // The type must be a string.
+ cfg->set("type", Element::create(true));
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "type must be a string (:0:0)");
+
+ // The type must be basic.
+ cfg->set("type", Element::create(string("foobar")));
+ string errmsg = "only basic HTTP authentication is supported: type is ";
+ errmsg += "'foobar' not 'basic' (:0:0)";
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError, errmsg);
+ cfg->set("type", Element::create(string("basic")));
+ EXPECT_NO_THROW(config.parse(cfg));
+
+ // The realm must be a string.
+ cfg->set("realm", Element::createList());
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "realm must be a string (:0:0)");
+ cfg->set("realm", Element::create(string("my-realm")));
+ EXPECT_NO_THROW(config.parse(cfg));
+
+ // The directory must be a string.
+ cfg->set("directory", Element::createMap());
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "directory must be a string (:0:0)");
+ cfg->set("directory", Element::create(data_dir));
+ EXPECT_NO_THROW(config.parse(cfg));
+
+ // The user context must be a map.
+ ElementPtr ctx = Element::createList();
+ cfg->set("user-context", ctx);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user-context must be a map (:0:0)");
+ ctx = Element::fromJSON("{ \"value\": \"a horse\" }");
+ cfg->set("user-context", ctx);
+ EXPECT_NO_THROW(config.parse(cfg));
+
+ // Clients must be a list.
+ ElementPtr clients_cfg = Element::createMap();
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "clients must be a list (:0:0)");
+
+ // The client config must be a map.
+ clients_cfg = Element::createList();
+ ElementPtr client_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "clients items must be maps (:0:0)");
+
+ // The user parameter is mandatory in client config
+ // without a password file.
+ client_cfg = Element::createMap();
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user is required in clients items (:0:0)");
+
+ // The user parameter must be a string.
+ ElementPtr user_cfg = Element::create(1);
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user must be a string (:0:0)");
+
+ // The user parameter must not be empty.
+ user_cfg = Element::create(string(""));
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user must not be empty (:0:0)");
+
+ // The user parameter must not contain ':'.
+ user_cfg = Element::create(string("foo:bar"));
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user must not contain a ':': 'foo:bar' (:0:0)");
+
+ // The user-file parameter must be a string.
+ ElementPtr user_file_cfg = Element::create(1);
+ client_cfg = Element::createMap();
+ client_cfg->set("user-file", user_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user-file must be a string (:0:0)");
+
+ // The user and user-file parameters are incompatible.
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("user-file", user_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user (:0:0) and user-file (:0:0) are "
+ "mutually exclusive");
+
+ // The user-file parameter must not be empty.
+ user_file_cfg = Element::create(string("empty"));
+ client_cfg = Element::createMap();
+ client_cfg->set("user-file", user_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user must not be empty from user-file "
+ "'empty' (:0:0)");
+
+ // The user-file parameter must not contain ':'.
+ user_file_cfg = Element::create(string("hiddens"));
+ client_cfg = Element::createMap();
+ client_cfg->set("user-file", user_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user must not contain a ':' from user-file "
+ "'hiddens' (:0:0)");
+
+ // Password is not required.
+ user_cfg = Element::create(string("foo"));
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_NO_THROW(config.parse(cfg));
+ ASSERT_EQ(1, config.getClientList().size());
+ EXPECT_EQ("", config.getClientList().front().getPassword());
+ config.clear();
+
+ // The password parameter must be a string.
+ ElementPtr password_cfg = Element::create(1);
+ client_cfg = Element::createMap();
+ client_cfg->set("password", password_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "password must be a string (:0:0)");
+
+ // Empty password is accepted.
+ password_cfg = Element::create(string(""));
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("password", password_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_NO_THROW(config.parse(cfg));
+ ASSERT_EQ(1, config.getClientList().size());
+ EXPECT_EQ("", config.getClientList().front().getPassword());
+ config.clear();
+
+ // The password-file parameter must be a string.
+ ElementPtr password_file_cfg = Element::create(1);
+ client_cfg = Element::createMap();
+ // user is not required when password-file is here.
+ client_cfg->set("password-file", password_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "password-file must be a string (:0:0)");
+
+ // The password and password-file parameters are incompatible.
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("password", password_cfg);
+ client_cfg->set("password-file", password_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "password (:0:0) and password-file (:0:0) are "
+ "mutually exclusive");
+
+ // Empty password-file is accepted.
+ password_file_cfg = Element::create(string("empty"));
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("password-file", password_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_NO_THROW(config.parse(cfg));
+ ASSERT_EQ(1, config.getClientList().size());
+ EXPECT_EQ("", config.getClientList().front().getPassword());
+ config.clear();
+
+ // password-file is enough.
+ password_file_cfg = Element::create(string("hiddens"));
+ client_cfg = Element::createMap();
+ client_cfg->set("password-file", password_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_NO_THROW(config.parse(cfg));
+ ASSERT_EQ(1, config.getClientList().size());
+ EXPECT_EQ("test", config.getClientList().front().getPassword());
+ config.clear();
+
+ // password-file only requires a ':' in the content.
+ password_file_cfg = Element::create(string("hiddenp"));
+ client_cfg = Element::createMap();
+ client_cfg->set("password-file", password_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "can't find the user id part in password-file "
+ "'hiddenp' (:0:0)");
+
+ // User context must be a map.
+ password_cfg = Element::create(string("bar"));
+ ctx = Element::createList();
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("password", password_cfg);
+ client_cfg->set("user-context", ctx);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user-context must be a map (:0:0)");
+
+ // Check a working not empty config.
+ ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("password", password_cfg);
+ client_cfg->set("user-context", ctx);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_NO_THROW(config.parse(cfg));
+ runToElementTest<BasicHttpAuthConfig>(cfg, config);
+
+ // Check a working not empty config with files.
+ config.clear();
+ client_cfg = Element::createMap();
+ user_file_cfg = Element::create(string("hiddenu"));
+ client_cfg->set("user-file", user_file_cfg);
+ client_cfg->set("password-file", password_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_NO_THROW(config.parse(cfg));
+ runToElementTest<BasicHttpAuthConfig>(cfg, config);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/http/tests/basic_auth_unittests.cc b/src/lib/http/tests/basic_auth_unittests.cc
new file mode 100644
index 0000000..1c2693d
--- /dev/null
+++ b/src/lib/http/tests/basic_auth_unittests.cc
@@ -0,0 +1,65 @@
+// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <http/basic_auth.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::http;
+
+namespace {
+
+// Test that user name with a colon is rejected.
+TEST(BasicHttpAuthTest, userColon) {
+ BasicHttpAuthPtr basic_auth;
+ EXPECT_THROW(basic_auth.reset(new BasicHttpAuth("foo:bar", "")), BadValue);
+}
+
+// Test that secret without a colon is rejected.
+TEST(BasicHttpAuthTest, secretNoColon) {
+ BasicHttpAuthPtr basic_auth;
+ EXPECT_THROW(basic_auth.reset(new BasicHttpAuth("foo-bar")), BadValue);
+}
+
+// Test that valid user and password work.
+TEST(BasicHttpAuthTest, user) {
+ BasicHttpAuthPtr basic_auth;
+ EXPECT_NO_THROW(basic_auth.reset(new BasicHttpAuth("foo", "bar")));
+ ASSERT_TRUE(basic_auth);
+ EXPECT_EQ("foo:bar", basic_auth->getSecret());
+ EXPECT_EQ("Zm9vOmJhcg==", basic_auth->getCredential());
+}
+
+// Test that valid secret work.
+TEST(BasicHttpAuthTest, secret) {
+ BasicHttpAuthPtr basic_auth;
+ EXPECT_NO_THROW(basic_auth.reset(new BasicHttpAuth("foo:bar")));
+ ASSERT_TRUE(basic_auth);
+ EXPECT_EQ("foo:bar", basic_auth->getSecret());
+ EXPECT_EQ("Zm9vOmJhcg==", basic_auth->getCredential());
+}
+
+// Test that secret is encoded in UTF-8.
+TEST(BasicHttpAuthTest, utf8) {
+ BasicHttpAuthPtr basic_auth;
+ EXPECT_NO_THROW(basic_auth.reset(new BasicHttpAuth("foo\n", "b\ar")));
+ ASSERT_TRUE(basic_auth);
+ EXPECT_EQ("foo\n:b\ar", basic_auth->getSecret());
+ EXPECT_EQ("Zm9vCjpiB3I=", basic_auth->getCredential());
+}
+
+// Test that a header context for basic HTTP authentication can be created.
+TEST(BasicHttpAuthTest, headerContext) {
+ BasicHttpAuthPtr basic_auth;
+ EXPECT_NO_THROW(basic_auth.reset(new BasicHttpAuth("foo", "bar")));
+ ASSERT_TRUE(basic_auth);
+ BasicAuthHttpHeaderContext ctx(*basic_auth);
+ EXPECT_EQ("Authorization", ctx.name_);
+ EXPECT_EQ("Basic Zm9vOmJhcg==", ctx.value_);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/http/tests/client_mt_unittests.cc b/src/lib/http/tests/client_mt_unittests.cc
new file mode 100644
index 0000000..0d8944a
--- /dev/null
+++ b/src/lib/http/tests/client_mt_unittests.cc
@@ -0,0 +1,1042 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <cc/data.h>
+#include <http/client.h>
+#include <http/listener.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_creator_factory.h>
+#include <http/response_json.h>
+#include <http/url.h>
+#include <util/multi_threading_mgr.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <sstream>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief IP address to which HTTP service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief Port number to which HTTP service is bound.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Container request/response pair handled by a given thread.
+struct ClientRR {
+ /// @brief Thread id of the client thread handling the request as a string.
+ std::string thread_id_;
+
+ /// @brief HTTP request submitted by the client thread.
+ PostHttpRequestJsonPtr request_;
+
+ /// @brief HTTP response received by the client thread.
+ HttpResponseJsonPtr response_;
+};
+
+/// @brief Pointer to a ClientRR instance.
+typedef boost::shared_ptr<ClientRR> ClientRRPtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+///
+/// Creates a response to a request containing body content
+/// as follows:
+///
+/// ```
+/// { "sequence" : nnnn }
+/// ```
+///
+/// The response will include the sequence number of the request
+/// as well as the server port passed into the creator's constructor:
+///
+/// ```
+/// { "sequence": nnnn, "server-port": xxxx }
+/// ```
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+ /// @brief Constructor
+ ///
+ /// @param server_port integer value the server listens upon, it is
+ /// echoed back in responses as "server-port".
+ TestHttpResponseCreator(uint16_t server_port)
+ : server_port_(server_port) { }
+
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+ }
+
+private:
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @param status_code status code to include in the response.
+ ///
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed OK).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // This will generate the response holding JSON content.
+ HttpResponseJsonPtr response(new HttpResponseJson(http_version, status_code));
+ response->finalize();
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// Generates a response which echoes the requests sequence
+ /// number as well as the creator's server port value. Responses
+ /// should appear as follows:
+ ///
+ /// ```
+ /// { "sequence" : nnnn }
+ /// ```
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP OK response with no content.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) {
+ // Request must always be JSON.
+ PostHttpRequestJsonPtr request_json =
+ boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
+ if (!request_json) {
+ return (createStockHttpResponse(request, HttpStatusCode::BAD_REQUEST));
+ }
+
+ // Extract the sequence from the request.
+ ConstElementPtr sequence = request_json->getJsonElement("sequence");
+ if (!sequence) {
+ return (createStockHttpResponse(request, HttpStatusCode::BAD_REQUEST));
+ }
+
+ // Create the response.
+ HttpResponseJsonPtr response(new HttpResponseJson(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ // Construct the body.
+ ElementPtr body = Element::createMap();
+ body->set("server-port", Element::create(server_port_));
+ body->set("sequence", sequence);
+
+ // Echo request body back in the response.
+ response->setBodyAsJson(body);
+
+ response->finalize();
+ return (response);
+ }
+
+ /// @brief Port upon which this creator's server is listening.
+ ///
+ /// The intent is to use the value to determine which server generated
+ /// a given response.
+ uint16_t server_port_;
+};
+
+/// @brief Implementation of the test @ref HttpResponseCreatorFactory.
+///
+/// This factory class creates @ref TestHttpResponseCreator instances.
+class TestHttpResponseCreatorFactory : public HttpResponseCreatorFactory {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param server_port port upon with the server is listening. This
+ /// value will be included in responses such that each response
+ /// can be attributed to a specific server.
+ TestHttpResponseCreatorFactory(uint16_t server_port)
+ : server_port_(server_port) {};
+
+ /// @brief Creates @ref TestHttpResponseCreator instance.
+ virtual HttpResponseCreatorPtr create() const {
+ HttpResponseCreatorPtr response_creator(new TestHttpResponseCreator(server_port_));
+ return (response_creator);
+ }
+
+ /// @brief Port upon which this factory's server is listening.
+ ///
+ /// The intent is to use the value to determine which server generated
+ /// a given response.
+ uint16_t server_port_;
+};
+
+/// @brief Test fixture class for testing threading modes of HTTP client.
+class MultiThreadingHttpClientTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ MultiThreadingHttpClientTest()
+ : io_service_(), client_(), listener_(), factory_(), listeners_(), factories_(),
+ test_timer_(io_service_), num_threads_(0), num_batches_(0), num_listeners_(0),
+ expected_requests_(0), num_in_progress_(0), num_finished_(0), paused_(false),
+ pause_cnt_(0) {
+ test_timer_.setup(std::bind(&MultiThreadingHttpClientTest::timeoutHandler, this, true),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+ MultiThreadingMgr::instance().setMode(true);
+ }
+
+ /// @brief Destructor.
+ ~MultiThreadingHttpClientTest() {
+ // Stop the client.
+ if (client_) {
+ client_->stop();
+ }
+
+ // Stop all listeners.
+ for (const auto& listener : listeners_) {
+ listener->stop();
+ }
+
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Callback function to invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void timeoutHandler(const bool fail_on_timeout) {
+ if (fail_on_timeout) {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ }
+ io_service_.stop();
+ }
+
+ /// @brief Runs the test's IOService until the desired number of requests
+ /// have been carried out or the test fails.
+ void runIOService(size_t request_limit) {
+ while (getRRCount() < request_limit) {
+ // Always call reset() before we call run();
+ io_service_.restart();
+
+ // Run until a client stops the service.
+ io_service_.run();
+ }
+ }
+
+ /// @brief Creates an HTTP request with JSON body.
+ ///
+ /// It includes a JSON parameter with a specified value.
+ ///
+ /// @param parameter_name JSON parameter to be included.
+ /// @param value JSON parameter value.
+ /// @param version HTTP version to be used. Default is HTTP/1.1.
+ template<typename ValueType>
+ PostHttpRequestJsonPtr createRequest(const std::string& parameter_name,
+ const ValueType& value,
+ const HttpVersion& version = HttpVersion(1, 1)) {
+ // Create POST request with JSON body.
+ PostHttpRequestJsonPtr request(new PostHttpRequestJson(HttpRequest::Method::HTTP_POST,
+ "/boo", version));
+ // Body is a map with a specified parameter included.
+ ElementPtr body = Element::createMap();
+ body->set(parameter_name, Element::create(value));
+ request->setBodyAsJson(body);
+ try {
+ request->finalize();
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "failed to create request: " << ex.what();
+ }
+
+ return (request);
+ }
+
+ /// @brief Test that worker threads are not permitted to change thread pool
+ /// state.
+ void testIllegalThreadPoolActions() {
+ ASSERT_THROW(client_->start(), MultiThreadingInvalidOperation);
+ ASSERT_THROW(client_->pause(), MultiThreadingInvalidOperation);
+ ASSERT_THROW(client_->resume(), MultiThreadingInvalidOperation);
+ }
+
+ /// @brief Initiates a single HTTP request.
+ ///
+ /// Constructs an HTTP post whose body is a JSON map containing a
+ /// single integer element, "sequence".
+ ///
+ /// The request completion handler will block each requesting thread
+ /// until the number of in-progress threads reaches the number of
+ /// threads in the pool. At that point, the handler will unblock
+ /// until all threads have finished preparing their response and are
+ /// ready to return. The handler will then notify all pending threads
+ /// and invoke stop() on the test's main IO service thread.
+ ///
+ /// @param sequence value for the integer element, "sequence",
+ /// to send in the request.
+ void startRequest(int sequence, int port_offset = 0) {
+ // Create the URL on which the server can be reached.
+ std::stringstream ss;
+ ss << "http://" << SERVER_ADDRESS << ":" << (SERVER_PORT + port_offset);
+ Url url(ss.str());
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request_json = createRequest("sequence", sequence);
+ HttpResponseJsonPtr response_json = boost::make_shared<HttpResponseJson>();
+ ASSERT_NO_THROW(client_->asyncSendRequest(url, TlsContextPtr(),
+ request_json, response_json,
+ [this, request_json, response_json](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ // Bail on an error.
+ ASSERT_FALSE(ec) << "asyncSendRequest failed, ec: " << ec;
+
+ // Wait here until we have as many in progress as we have threads.
+ {
+ std::unique_lock<std::mutex> lck(test_mutex_);
+ ++num_in_progress_;
+ if (num_threads_ == 0 || num_in_progress_ == num_threads_) {
+ // Everybody has one, let's go.
+ num_finished_ = 0;
+ test_cv_.notify_all();
+ } else {
+ // I'm ready but others aren't wait here.
+ bool ret = test_cv_.wait_for(lck, std::chrono::seconds(10),
+ [&]() { return (num_in_progress_ == num_threads_); });
+ if (!ret) {
+ ADD_FAILURE() << "clients failed to start work";
+ }
+ }
+ }
+
+ // If running on multiple threads, threads should be prohibited from
+ // changing the thread pool state.
+ if (num_threads_) {
+ testIllegalThreadPoolActions();
+ }
+
+ // Get stringified thread-id.
+ std::stringstream ss;
+ ss << std::this_thread::get_id();
+
+ // Create the ClientRR.
+ ClientRRPtr clientRR(new ClientRR());
+ clientRR->thread_id_ = ss.str();
+ clientRR->request_ = request_json;
+ clientRR->response_ = response_json;
+
+ // Wait here until we have as many ready to finish as we have threads.
+ {
+ std::unique_lock<std::mutex> lck(test_mutex_);
+ ++num_finished_;
+ clientRRs_.push_back(clientRR);
+ if (num_threads_ == 0 || num_finished_ == num_threads_) {
+ // We're all done, notify the others and finish.
+ num_in_progress_ = 0;
+ test_cv_.notify_all();
+ // Stop the test's IOService.
+ io_service_.stop();
+ } else {
+ // I'm done but others aren't wait here.
+ bool ret = test_cv_.wait_for(lck, std::chrono::seconds(10),
+ [&]() { return (num_finished_ == num_threads_); });
+ if (!ret) {
+ ADD_FAILURE() << "clients failed to finish work";
+ }
+ }
+ }
+ }));
+ }
+
+ /// @brief Initiates a single HTTP request.
+ ///
+ /// Constructs an HTTP post whose body is a JSON map containing a
+ /// single integer element, "sequence".
+ ///
+ /// The request completion handler simply constructs the response,
+ /// and adds it the list of completed request/responses. If the
+ /// number of completed requests has reached the expected number
+ /// it stops the test IOService.
+ ///
+ /// @param sequence value for the integer element, "sequence",
+ /// to send in the request.
+ void startRequestSimple(int sequence, int port_offset = 0) {
+ // Create the URL on which the server can be reached.
+ std::stringstream ss;
+ ss << "http://" << SERVER_ADDRESS << ":" << (SERVER_PORT + port_offset);
+ Url url(ss.str());
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request_json = createRequest("sequence", sequence);
+ HttpResponseJsonPtr response_json = boost::make_shared<HttpResponseJson>();
+ ASSERT_NO_THROW(client_->asyncSendRequest(url, TlsContextPtr(),
+ request_json, response_json,
+ [this, request_json, response_json](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ // Bail on an error.
+ ASSERT_FALSE(ec) << "asyncSendRequest failed, ec: " << ec;
+
+ // Get stringified thread-id.
+ std::stringstream ss;
+ ss << std::this_thread::get_id();
+
+ // Create the ClientRR.
+ ClientRRPtr clientRR(new ClientRR());
+ clientRR->thread_id_ = ss.str();
+ clientRR->request_ = request_json;
+ clientRR->response_ = response_json;
+
+ {
+ std::unique_lock<std::mutex> lck(test_mutex_);
+ clientRRs_.push_back(clientRR);
+ ++num_finished_;
+ if ((num_finished_ >= expected_requests_) && !io_service_.stopped()) {
+ io_service_.stop();
+ }
+ }
+
+ }));
+ }
+
+ /// @brief Carries out HTTP requests via HttpClient to HTTP listener(s).
+ ///
+ /// This function creates one HttpClient with the given number
+ /// of threads and then the given number of HttpListeners. It then
+ /// initiates the given number of request batches where each batch
+ /// contains one request per thread per listener.
+ ///
+ /// Then it iteratively runs the test's IOService until all
+ /// the requests have been responded to, an error occurs, or the
+ /// test times out.
+ ///
+ /// Each request carries a single integer element, "sequence", which
+ /// uniquely identifies the request. Each response is expected to
+ /// contain this value echoed back along with the listener's server
+ /// port number. Thus each response can be matched to it's request
+ /// and to the listener that handled the request.
+ ///
+ /// After all requests have been conducted, the function verifies
+ /// that:
+ ///
+ /// 1. The number of requests conducted is correct
+ /// 2. The sequence numbers in request-response pairs match
+ /// 3. Each client thread handled the same number of requests
+ /// 4. Each listener handled the same number of requests
+ ///
+ /// @param num_threads number of threads the HttpClient should use.
+ /// A value of 0 puts the HttpClient in single-threaded mode.
+ /// @param num_batches number of batches of requests that should be
+ /// conducted.
+ /// @param num_listeners number of HttpListeners to create. Defaults
+ /// to 1.
+ void threadRequestAndReceive(size_t num_threads, size_t num_batches,
+ size_t num_listeners = 1) {
+ ASSERT_TRUE(num_batches);
+ ASSERT_TRUE(num_listeners);
+ num_threads_ = num_threads;
+ num_batches_ = num_batches;
+ num_listeners_ = num_listeners;
+
+ // Client in ST is, in effect, 1 thread.
+ size_t effective_threads = (num_threads_ == 0 ? 1 : num_threads_);
+
+ // Calculate the expected number of requests.
+ expected_requests_ = (num_batches_ * num_listeners_ * effective_threads);
+
+ for (auto i = 0; i < num_listeners_; ++i) {
+ // Make a factory
+ HttpResponseCreatorFactoryPtr factory(new TestHttpResponseCreatorFactory(SERVER_PORT + i));
+ factories_.push_back(factory);
+
+ // Need to create a Listener on
+ HttpListenerPtr listener(new HttpListener(io_service_,
+ IOAddress(SERVER_ADDRESS), (SERVER_PORT + i),
+ TlsContextPtr(), factory,
+ HttpListener::RequestTimeout(10000),
+ HttpListener::IdleTimeout(10000)));
+ listeners_.push_back(listener);
+
+ // Start the server.
+ ASSERT_NO_THROW(listener->start());
+ }
+
+ // Create an MT client with num_threads
+ ASSERT_NO_THROW_LOG(client_.reset(new HttpClient(io_service_, num_threads)));
+ ASSERT_TRUE(client_);
+
+ if (num_threads_ == 0) {
+ // If we single-threaded client should not have it's own IOService.
+ ASSERT_FALSE(client_->getThreadIOService());
+ } else {
+ // If we multi-threaded client should have it's own IOService.
+ ASSERT_TRUE(client_->getThreadIOService());
+ }
+
+ // Verify the pool size and number of threads are as expected.
+ ASSERT_EQ(client_->getThreadPoolSize(), num_threads);
+ ASSERT_EQ(client_->getThreadCount(), num_threads);
+
+ // Start the requisite number of requests:
+ // batch * listeners * threads.
+ int sequence = 0;
+ for (auto b = 0; b < num_batches; ++b) {
+ for (auto l = 0; l < num_listeners_; ++l) {
+ for (auto t = 0; t < effective_threads; ++t) {
+ startRequest(++sequence, l);
+ }
+ }
+ }
+
+ // Loop until the clients are done, an error occurs, or the time runs out.
+ runIOService(expected_requests_);
+
+ // Client should stop without issue.
+ ASSERT_NO_THROW(client_->stop());
+
+ // Listeners should stop without issue.
+ for (const auto& listener : listeners_) {
+ ASSERT_NO_THROW(listener->stop());
+ }
+
+ // We should have a response for each request.
+ ASSERT_EQ(getRRCount(), expected_requests_);
+
+ // Create a map to track number of responses for each client thread.
+ std::map<std::string, int> responses_per_thread;
+
+ // Create a map to track number of responses for each listener port.
+ std::map<uint16_t, int> responses_per_listener;
+
+ // Get the stringified thread-id of the test's main thread.
+ std::stringstream ss;
+ ss << std::this_thread::get_id();
+ std::string main_thread_id = ss.str();
+
+ // Iterate over the client request/response pairs.
+ for (auto const& clientRR : clientRRs_) {
+ // Make sure it's whole.
+ ASSERT_FALSE(clientRR->thread_id_.empty());
+ ASSERT_TRUE(clientRR->request_);
+ ASSERT_TRUE(clientRR->response_);
+
+ // Request should contain an integer sequence number.
+ int request_sequence;
+ ConstElementPtr sequence = clientRR->request_->getJsonElement("sequence");
+ ASSERT_TRUE(sequence);
+ ASSERT_NO_THROW(request_sequence = sequence->intValue());
+
+ // Response should contain an integer sequence number.
+ int response_sequence;
+ sequence = clientRR->response_->getJsonElement("sequence");
+ ASSERT_TRUE(sequence);
+ ASSERT_NO_THROW(response_sequence = sequence->intValue());
+
+ // Request and Response sequence numbers should match.
+ ASSERT_EQ(request_sequence, response_sequence);
+
+ ConstElementPtr server_port_elem = clientRR->response_->getJsonElement("server-port");
+ ASSERT_TRUE(server_port_elem);
+ uint16_t server_port = server_port_elem->intValue();
+
+ if (num_threads_ == 0) {
+ // For ST mode thread id should always be the main thread.
+ ASSERT_EQ(clientRR->thread_id_, main_thread_id);
+ } else {
+ // For MT mode the thread id should never be the main thread.
+ ASSERT_NE(clientRR->thread_id_, main_thread_id);
+ }
+
+ // Bump the response count for the given client thread-id.
+ auto rit = responses_per_thread.find(clientRR->thread_id_);
+ if (rit != responses_per_thread.end()) {
+ responses_per_thread[clientRR->thread_id_] = rit->second + 1;
+ } else {
+ responses_per_thread[clientRR->thread_id_] = 1;
+ }
+
+ // Bump the response count for the given server port.
+ auto lit = responses_per_listener.find(server_port);
+ if (lit != responses_per_listener.end()) {
+ responses_per_listener[server_port] = lit->second + 1;
+ } else {
+ responses_per_listener[server_port] = 1;
+ }
+ }
+
+ // Make sure that all client threads received responses.
+ ASSERT_EQ(responses_per_thread.size(), effective_threads);
+
+ // Make sure that each client thread received the same number of responses.
+ for (auto const& it : responses_per_thread) {
+ EXPECT_EQ(it.second, (num_batches_ * num_listeners_))
+ << "thread-id: " << it.first
+ << ", responses: " << it.second << std::endl;
+ }
+
+ // Make sure that all listeners generated responses.
+ ASSERT_EQ(responses_per_listener.size(), num_listeners_);
+
+ // Make sure Each listener generated the same number of responses.
+ for (auto const& it : responses_per_listener) {
+ EXPECT_EQ(it.second, (num_batches_ * effective_threads))
+ << "server-port: " << it.first
+ << ", responses: " << it.second << std::endl;
+ }
+ }
+
+ /// @brief Verifies the client can be paused and resumed repeatedly
+ /// while doing multi-threaded work.
+ ///
+ /// @param num_threads number of threads the HttpClient should use.
+ /// Must be greater than zero, this test does not make sense for a
+ /// single threaded client.
+ /// @param num_batches number of batches of requests that should be
+ /// conducted.
+ /// @param num_listeners number of HttpListeners to create.
+ /// @param num_pauses number of pauses to conduct.
+ void workPauseResumeShutdown(size_t num_threads, size_t num_batches,
+ size_t num_listeners, size_t num_pauses) {
+ ASSERT_TRUE(num_threads);
+ ASSERT_TRUE(num_batches);
+ ASSERT_TRUE(num_listeners);
+ num_threads_ = num_threads;
+ num_batches_ = num_batches;
+ num_listeners_ = num_listeners;
+
+ // Calculate the total expected number of requests.
+ size_t total_requests = (num_batches_ * num_listeners_ * num_threads_);
+
+ // Create the listeners.
+ for (auto i = 0; i < num_listeners_; ++i) {
+ // Make a factory
+ HttpResponseCreatorFactoryPtr factory(new TestHttpResponseCreatorFactory(SERVER_PORT + i));
+ factories_.push_back(factory);
+
+ // Need to create a Listener on
+ HttpListenerPtr listener(new HttpListener(io_service_,
+ IOAddress(SERVER_ADDRESS), (SERVER_PORT + i),
+ TlsContextPtr(), factory,
+ HttpListener::RequestTimeout(10000),
+ HttpListener::IdleTimeout(10000)));
+ listeners_.push_back(listener);
+
+ // Start the server.
+ ASSERT_NO_THROW(listener->start());
+ }
+
+ // Create an instant start, MT client with num_threads
+ ASSERT_NO_THROW_LOG(client_.reset(new HttpClient(io_service_, num_threads)));
+ ASSERT_TRUE(client_);
+
+ // Client should be running. Check convenience functions.
+ ASSERT_TRUE(client_->isRunning());
+ ASSERT_FALSE(client_->isPaused());
+ ASSERT_FALSE(client_->isStopped());
+
+ // Verify the pool size and number of threads are as expected.
+ ASSERT_EQ(client_->getThreadPoolSize(), num_threads);
+ ASSERT_EQ(client_->getThreadCount(), num_threads);
+
+ // Start the requisite number of requests:
+ // batch * listeners * threads.
+ int sequence = 0;
+ for (auto b = 0; b < num_batches; ++b) {
+ for (auto l = 0; l < num_listeners_; ++l) {
+ for (auto t = 0; t < num_threads_; ++t) {
+ startRequestSimple(++sequence, l);
+ }
+ }
+ }
+
+ size_t rr_count = 0;
+ while (rr_count < total_requests) {
+ size_t request_limit = (pause_cnt_ < num_pauses ?
+ (rr_count + ((total_requests - rr_count) / num_pauses))
+ : total_requests);
+
+ // Run test IOService until we hit the limit.
+ runIOService(request_limit);
+
+ // If we've done all our pauses we should be through.
+ if (pause_cnt_ == num_pauses) {
+ break;
+ }
+
+ // Pause the client.
+ ASSERT_NO_THROW(client_->pause());
+ ASSERT_TRUE(client_->isPaused());
+ ++pause_cnt_;
+
+ // Check our progress.
+ rr_count = getRRCount();
+ ASSERT_GE(rr_count, request_limit);
+
+ // Resume the client.
+ ASSERT_NO_THROW(client_->resume());
+ ASSERT_TRUE(client_->isRunning());
+ }
+
+ // Client should stop without issue.
+ ASSERT_NO_THROW(client_->stop());
+ ASSERT_TRUE(client_->isStopped());
+
+ // We should have finished all our requests.
+ ASSERT_EQ(getRRCount(), total_requests);
+
+ // Stopping again should be harmless.
+ ASSERT_NO_THROW(client_->stop());
+
+ // Listeners should stop without issue.
+ for (const auto& listener : listeners_) {
+ ASSERT_NO_THROW(listener->stop());
+ }
+
+ // Get the stringified thread-id of the test's main thread.
+ std::stringstream ss;
+ ss << std::this_thread::get_id();
+ std::string main_thread_id = ss.str();
+
+ // Tracks the number for requests fulfilled by main thread.
+ size_t worked_by_main = 0;
+
+ // Iterate over the client request/response pairs.
+ for (auto const& clientRR : clientRRs_) {
+ // Make sure it's whole.
+ ASSERT_FALSE(clientRR->thread_id_.empty());
+ ASSERT_TRUE(clientRR->request_);
+ ASSERT_TRUE(clientRR->response_);
+
+ // Request should contain an integer sequence number.
+ int request_sequence;
+ ConstElementPtr sequence = clientRR->request_->getJsonElement("sequence");
+ ASSERT_TRUE(sequence);
+ ASSERT_NO_THROW(request_sequence = sequence->intValue());
+
+ // Response should contain an integer sequence number.
+ int response_sequence;
+ sequence = clientRR->response_->getJsonElement("sequence");
+ ASSERT_TRUE(sequence);
+ ASSERT_NO_THROW(response_sequence = sequence->intValue());
+
+ // Request and Response sequence numbers should match.
+ ASSERT_EQ(request_sequence, response_sequence);
+
+ ConstElementPtr server_port_elem = clientRR->response_->getJsonElement("server-port");
+ ASSERT_TRUE(server_port_elem);
+
+ // Track how many requests were completed by the main thread.
+ // These can occur when pausing calls IOService::poll.
+ if (clientRR->thread_id_ == main_thread_id) {
+ ++worked_by_main;
+ }
+ }
+
+ // Make sure the majority of the requests were worked by
+ // worker threads. In theory, the number of calls to poll
+ // times the number of threads is the limit for responses
+ // built by the main thread.
+ ASSERT_LE(worked_by_main, num_pauses * num_threads);
+ }
+
+ /// @brief Fetch the number of completed requests.
+ ///
+ /// @return number of completed requests.
+ size_t getRRCount() {
+ std::lock_guard<std::mutex> lck(test_mutex_);
+ return (clientRRs_.size());
+ }
+
+ /// @brief IO service used in the tests.
+ IOService io_service_;
+
+ /// @brief Instance of the client used in the tests.
+ HttpClientPtr client_;
+
+ /// @brief Instance of the listener used in the tests.
+ HttpListenerPtr listener_;
+
+ /// @brief Pointer to the response creator factory.
+ HttpResponseCreatorFactoryPtr factory_;
+
+ /// @brief List of listeners.
+ std::vector<HttpListenerPtr> listeners_;
+
+ /// @brief List of response factories.
+ std::vector<HttpResponseCreatorFactoryPtr> factories_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief Number of threads HttpClient should use.
+ size_t num_threads_;
+
+ /// @brief Number of request batches to conduct.
+ size_t num_batches_;
+
+ /// @brief Number of listeners to start.
+ size_t num_listeners_;
+
+ /// @brief Number of expected requests to carry out.
+ size_t expected_requests_;
+
+ /// @brief Number of requests that are in progress.
+ size_t num_in_progress_;
+
+ /// @brief Number of requests that have been completed.
+ size_t num_finished_;
+
+ /// @brief a List of client request-response pairs.
+ std::vector<ClientRRPtr> clientRRs_;
+
+ /// @brief Mutex for locking.
+ std::mutex test_mutex_;
+
+ /// @brief Condition variable used to make client threads wait
+ /// until number of in-progress requests reaches the number
+ /// of client requests.
+ std::condition_variable test_cv_;
+
+ /// @brief Indicates if client threads are currently "paused".
+ bool paused_;
+
+ /// @brief Number of times client has been paused during the test.
+ size_t pause_cnt_;
+};
+
+// Verifies we can construct and destruct, in both single
+// and multi-threaded modes.
+TEST_F(MultiThreadingHttpClientTest, basics) {
+ MultiThreadingMgr::instance().setMode(false);
+ HttpClientPtr client;
+
+ // Value of 0 for thread_pool_size means single-threaded.
+ ASSERT_NO_THROW_LOG(client.reset(new HttpClient(io_service_, 0)));
+ ASSERT_TRUE(client);
+
+ ASSERT_FALSE(client->getThreadIOService());
+ ASSERT_EQ(client->getThreadPoolSize(), 0);
+ ASSERT_EQ(client->getThreadCount(), 0);
+
+ // Make sure destruction doesn't throw.
+ ASSERT_NO_THROW_LOG(client.reset());
+
+ // Non-zero thread-pool-size means multi-threaded mode, should throw.
+ ASSERT_THROW_MSG(client.reset(new HttpClient(io_service_, 1)), InvalidOperation,
+ "HttpClient thread_pool_size must be zero"
+ "when Kea core multi-threading is disabled");
+ ASSERT_FALSE(client);
+
+ // Enable Kea core multi-threading.
+ MultiThreadingMgr::instance().setMode(true);
+
+ // Multi-threaded construction should work now.
+ ASSERT_NO_THROW_LOG(client.reset(new HttpClient(io_service_, 3)));
+ ASSERT_TRUE(client);
+
+ // Verify that it has an internal IOService and that thread pool size
+ // and thread count match.
+ ASSERT_TRUE(client->getThreadIOService());
+ EXPECT_FALSE(client->getThreadIOService()->stopped());
+ ASSERT_EQ(client->getThreadPoolSize(), 3);
+ ASSERT_EQ(client->getThreadCount(), 3);
+
+ // Check convenience functions.
+ ASSERT_TRUE(client->isRunning());
+ ASSERT_FALSE(client->isPaused());
+ ASSERT_FALSE(client->isStopped());
+
+ // Verify stop doesn't throw.
+ ASSERT_NO_THROW_LOG(client->stop());
+
+ // Verify we're stopped.
+ ASSERT_TRUE(client->getThreadIOService());
+ EXPECT_TRUE(client->getThreadIOService()->stopped());
+ ASSERT_EQ(client->getThreadPoolSize(), 3);
+ ASSERT_EQ(client->getThreadCount(), 0);
+
+ // Check convenience functions.
+ ASSERT_FALSE(client->isRunning());
+ ASSERT_FALSE(client->isPaused());
+ ASSERT_TRUE(client->isStopped());
+
+ // Verify a second call to stop() doesn't throw.
+ ASSERT_NO_THROW_LOG(client->stop());
+
+ // Make sure destruction doesn't throw.
+ ASSERT_NO_THROW_LOG(client.reset());
+
+ // Create another multi-threaded instance.
+ ASSERT_NO_THROW_LOG(client.reset(new HttpClient(io_service_, 3)));
+
+ // Make sure destruction doesn't throw.
+ ASSERT_NO_THROW_LOG(client.reset());
+}
+
+// Verifies we can construct with deferred start.
+TEST_F(MultiThreadingHttpClientTest, deferredStart) {
+ MultiThreadingMgr::instance().setMode(true);
+ HttpClientPtr client;
+ size_t thread_pool_size = 3;
+
+ // Create MT client with deferred start.
+ ASSERT_NO_THROW_LOG(client.reset(new HttpClient(io_service_, thread_pool_size, true)));
+ ASSERT_TRUE(client);
+
+ // Client should be STOPPED, with no threads.
+ ASSERT_TRUE(client->getThreadIOService());
+ ASSERT_EQ(client->getThreadPoolSize(), thread_pool_size);
+ ASSERT_EQ(client->getThreadCount(), 0);
+
+ // Check convenience functions.
+ ASSERT_FALSE(client->isRunning());
+ ASSERT_FALSE(client->isPaused());
+ ASSERT_TRUE(client->isStopped());
+
+ // We should be able to start it.
+ ASSERT_NO_THROW(client->start());
+
+ // Verify we have threads and run state is RUNNING.
+ ASSERT_EQ(client->getThreadCount(), 3);
+ ASSERT_TRUE(client->getThreadIOService());
+ ASSERT_FALSE(client->getThreadIOService()->stopped());
+
+ // Check convenience functions.
+ ASSERT_TRUE(client->isRunning());
+ ASSERT_FALSE(client->isPaused());
+ ASSERT_FALSE(client->isStopped());
+
+ // Second call to start should be harmless.
+ ASSERT_NO_THROW_LOG(client->start());
+
+ // Verify we didn't break it.
+ ASSERT_EQ(client->getThreadCount(), 3);
+ ASSERT_TRUE(client->isRunning());
+
+ // Make sure destruction doesn't throw.
+ ASSERT_NO_THROW_LOG(client.reset());
+}
+
+// Verifies we can restart after stop.
+TEST_F(MultiThreadingHttpClientTest, restartAfterStop) {
+ MultiThreadingMgr::instance().setMode(true);
+ HttpClientPtr client;
+ size_t thread_pool_size = 3;
+
+ // Create MT client with instant start.
+ ASSERT_NO_THROW_LOG(client.reset(new HttpClient(io_service_, thread_pool_size)));
+ ASSERT_TRUE(client);
+
+ // Verify we're started.
+ ASSERT_EQ(client->getThreadCount(), 3);
+ ASSERT_TRUE(client->getThreadIOService());
+ ASSERT_FALSE(client->getThreadIOService()->stopped());
+ ASSERT_TRUE(client->isRunning());
+
+ // Stop should succeed.
+ ASSERT_NO_THROW_LOG(client->stop());
+
+ // Verify we're stopped.
+ ASSERT_EQ(client->getThreadCount(), 0);
+ ASSERT_TRUE(client->getThreadIOService());
+ ASSERT_TRUE(client->getThreadIOService()->stopped());
+ ASSERT_TRUE(client->isStopped());
+
+ // Starting again should succeed.
+ ASSERT_NO_THROW_LOG(client->start());
+
+ // Verify we didn't break it.
+ ASSERT_EQ(client->getThreadCount(), 3);
+ ASSERT_TRUE(client->getThreadIOService());
+ ASSERT_FALSE(client->getThreadIOService()->stopped());
+ ASSERT_TRUE(client->isRunning());
+
+ // Make sure destruction doesn't throw.
+ ASSERT_NO_THROW_LOG(client.reset());
+}
+
+// Now we'll run some permutations of the number of client threads,
+// requests, and listeners.
+
+// Single-threaded, three batches, one listener.
+TEST_F(MultiThreadingHttpClientTest, zeroByThreeByOne) {
+ size_t num_threads = 0; // Zero threads = ST mode.
+ size_t num_batches = 3;
+ threadRequestAndReceive(num_threads, num_batches);
+}
+
+// Single-threaded, three batches, three listeners.
+TEST_F(MultiThreadingHttpClientTest, zeroByThreeByThree) {
+ size_t num_threads = 0; // Zero threads = ST mode.
+ size_t num_batches = 3;
+ size_t num_listeners = 3;
+ threadRequestAndReceive(num_threads, num_batches, num_listeners);
+}
+
+// Multi-threaded with one thread, three batches, one listener
+TEST_F(MultiThreadingHttpClientTest, oneByThreeByOne) {
+ size_t num_threads = 1;
+ size_t num_batches = 3;
+ threadRequestAndReceive(num_threads, num_batches);
+}
+
+// Multi-threaded with three threads, three batches, one listener
+TEST_F(MultiThreadingHttpClientTest, threeByThreeByOne) {
+ size_t num_threads = 3;
+ size_t num_batches = 3;
+ threadRequestAndReceive(num_threads, num_batches);
+}
+
+// Multi-threaded with three threads, nine batches, one listener
+TEST_F(MultiThreadingHttpClientTest, threeByNineByOne) {
+ size_t num_threads = 3;
+ size_t num_batches = 9;
+ threadRequestAndReceive(num_threads, num_batches);
+}
+
+// Multi-threaded with two threads, four batches, two listeners
+TEST_F(MultiThreadingHttpClientTest, twoByFourByTwo) {
+ size_t num_threads = 2;
+ size_t num_batches = 4;
+ size_t num_listeners = 2;
+ threadRequestAndReceive(num_threads, num_batches, num_listeners);
+}
+
+// Multi-threaded with four threads, four batches, two listeners
+TEST_F(MultiThreadingHttpClientTest, fourByFourByTwo) {
+ size_t num_threads = 4;
+ size_t num_batches = 4;
+ size_t num_listeners = 2;
+ threadRequestAndReceive(num_threads, num_batches, num_listeners);
+}
+
+// Verifies that we can cleanly pause, resume, and shutdown while doing
+// multi-threaded work.
+TEST_F(MultiThreadingHttpClientTest, workPauseResumeShutdown) {
+ size_t num_threads = 4;
+ size_t num_batches = 4;
+ size_t num_listeners = 4;
+ size_t num_pauses = 3;
+ workPauseResumeShutdown(num_threads, num_batches, num_listeners, num_pauses);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/http/tests/connection_pool_unittests.cc b/src/lib/http/tests/connection_pool_unittests.cc
new file mode 100644
index 0000000..b8337c3
--- /dev/null
+++ b/src/lib/http/tests/connection_pool_unittests.cc
@@ -0,0 +1,270 @@
+// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <http/http_acceptor.h>
+#include <http/connection.h>
+#include <http/connection_pool.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <algorithm>
+
+using namespace isc::asiolink;
+using namespace isc::http;
+using namespace isc::http::test;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Request timeout used in tests.
+const long CONN_REQUEST_TIMEOUT = 1000;
+
+/// @brief Idle connection timeout used in tests.
+const long CONN_IDLE_TIMEOUT = 1000;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+ }
+
+private:
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed ok).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // This will generate the response holding JSON content.
+ ResponsePtr response(new Response(http_version, status_code));
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP OK response with no content.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) {
+ // The simplest thing is to create a response with no content.
+ // We don't need content to test our class.
+ ResponsePtr response(new Response(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ return (response);
+ }
+};
+
+/// @brief Derivation of @ref HttpConnectionPool exposing protected member.
+class TestHttpConnectionPool : public HttpConnectionPool {
+public:
+
+ using HttpConnectionPool::connections_;
+
+ /// @brief Checks if specified connection belongs to the pool.
+ bool hasConnection(const HttpConnectionPtr& conn) const {
+ return (std::find(connections_.begin(), connections_.end(), conn)
+ != connections_.end());
+ }
+
+};
+
+/// @brief Test fixture class for @ref HttpConnectionPool.
+class HttpConnectionPoolTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ HttpConnectionPoolTest()
+ : io_service_(),
+ acceptor_(new HttpAcceptor(io_service_)),
+ connection_pool_(),
+ response_creator_(new TestHttpResponseCreator()) {
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destructor.
+ ~HttpConnectionPoolTest() {
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Verifies that connections can be added to the pool and removed.
+ void startStopTest() {
+ // Create two distinct connections.
+ HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_,
+ TlsContextPtr(),
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ CONN_REQUEST_TIMEOUT,
+ CONN_IDLE_TIMEOUT));
+
+ HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_,
+ TlsContextPtr(),
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ CONN_REQUEST_TIMEOUT,
+ CONN_IDLE_TIMEOUT));
+ // The pool should be initially empty.
+ TestHttpConnectionPool pool;
+ ASSERT_TRUE(pool.connections_.empty());
+
+ // Start first connection and check that it has been added to the pool.
+ ASSERT_NO_THROW(pool.start(conn1));
+ ASSERT_EQ(1, pool.connections_.size());
+ ASSERT_EQ(1, pool.hasConnection(conn1));
+
+ // Start second connection and check that it also has been added.
+ ASSERT_NO_THROW(pool.start(conn2));
+ ASSERT_EQ(2, pool.connections_.size());
+ ASSERT_EQ(1, pool.hasConnection(conn2));
+
+ // Stop first connection.
+ ASSERT_NO_THROW(pool.stop(conn1));
+ ASSERT_EQ(1, pool.connections_.size());
+ // Check that it has been removed but the second connection is still
+ // there.
+ ASSERT_EQ(0, pool.hasConnection(conn1));
+ ASSERT_EQ(1, pool.hasConnection(conn2));
+
+ // Remove second connection and verify.
+ ASSERT_NO_THROW(pool.stop(conn2));
+ EXPECT_TRUE(pool.connections_.empty());
+ }
+
+ /// @brief Verifies that all connections can be remove with a single call.
+ void stopAllTest() {
+ // Create two distinct connections.
+ HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_,
+ TlsContextPtr(),
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ CONN_REQUEST_TIMEOUT,
+ CONN_IDLE_TIMEOUT));
+
+ HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_,
+ TlsContextPtr(),
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ CONN_REQUEST_TIMEOUT,
+ CONN_IDLE_TIMEOUT));
+ TestHttpConnectionPool pool;
+ ASSERT_NO_THROW(pool.start(conn1));
+ ASSERT_NO_THROW(pool.start(conn2));
+
+ // There are two distinct connections in the pool.
+ ASSERT_EQ(2, pool.connections_.size());
+
+ // This should remove all connections.
+ ASSERT_NO_THROW(pool.stopAll());
+ EXPECT_TRUE(pool.connections_.empty());
+ }
+
+ /// @brief Verifies that stopping a non-existing connection is no-op.
+ void stopInvalidTest() {
+ HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_,
+ TlsContextPtr(),
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ CONN_REQUEST_TIMEOUT,
+ CONN_IDLE_TIMEOUT));
+
+ HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_,
+ TlsContextPtr(),
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ CONN_REQUEST_TIMEOUT,
+ CONN_IDLE_TIMEOUT));
+ TestHttpConnectionPool pool;
+ ASSERT_NO_THROW(pool.start(conn1));
+ ASSERT_NO_THROW(pool.stop(conn2));
+ ASSERT_EQ(1, pool.connections_.size());
+ ASSERT_EQ(1, pool.hasConnection(conn1));
+ }
+
+ IOService io_service_; ///< IO service.
+ HttpAcceptorPtr acceptor_; ///< Test acceptor.
+ HttpConnectionPool connection_pool_; ///< Test connection pool.
+ HttpResponseCreatorPtr response_creator_; ///< Test response creator.
+
+};
+
+// Verifies that connections can be added to the pool and removed.
+// with MultiThreading disabled.
+TEST_F(HttpConnectionPoolTest, startStopTest) {
+ ASSERT_FALSE(MultiThreadingMgr::instance().getMode());
+ startStopTest();
+}
+
+// Verifies that connections can be added to the pool and removed
+// with MultiThreading enabled.
+TEST_F(HttpConnectionPoolTest, startStopTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ startStopTest();
+}
+
+// Check that all connections can be remove with a single call.
+// with MultiThreading disabled.
+TEST_F(HttpConnectionPoolTest, stopAll) {
+ ASSERT_FALSE(MultiThreadingMgr::instance().getMode());
+ stopAllTest();
+}
+
+// Check that all connections can be remove with a single call
+// with MultiThreading enabled.
+TEST_F(HttpConnectionPoolTest, stopAllMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_TRUE(MultiThreadingMgr::instance().getMode());
+ stopAllTest();
+}
+
+// Check that stopping non-existing connection is no-op.
+// with MultiThreading disabled.
+TEST_F(HttpConnectionPoolTest, stopInvalid) {
+ ASSERT_FALSE(MultiThreadingMgr::instance().getMode());
+ stopInvalidTest();
+}
+
+// Check that stopping non-existing connection is no-op.
+// with MultiThreading enabled.
+TEST_F(HttpConnectionPoolTest, stopInvalidMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_TRUE(MultiThreadingMgr::instance().getMode());
+ stopInvalidTest();
+}
+
+}
diff --git a/src/lib/http/tests/date_time_unittests.cc b/src/lib/http/tests/date_time_unittests.cc
new file mode 100644
index 0000000..59d75b1
--- /dev/null
+++ b/src/lib/http/tests/date_time_unittests.cc
@@ -0,0 +1,190 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <http/date_time.h>
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+using namespace boost::gregorian;
+using namespace boost::posix_time;
+using namespace isc::http;
+
+namespace {
+
+/// @brief Test fixture class for @ref HttpDateTime.
+class HttpDateTimeTest : public ::testing::Test {
+public:
+
+ /// @brief Checks time value against expected values.
+ ///
+ /// This method uses value of @ref date_time_ for the test.
+ ///
+ /// @param exp_day_of_week Expected day of week.
+ /// @param exp_day Expected day of month.
+ /// @param exp_month Expected month.
+ /// @param exp_year Expected year.
+ /// @param exp_hours Expected hour value.
+ /// @param exp_minutes Expected minutes value.
+ /// @param exp_seconds Expected seconds value.
+ void testDateTime(const unsigned short exp_day_of_week,
+ const unsigned short exp_day,
+ const unsigned short exp_month,
+ const unsigned short exp_year,
+ const long exp_hours,
+ const long exp_minutes,
+ const long exp_seconds) {
+ // Retrieve @c boost::posix_time::ptime value.
+ ptime as_ptime = date_time_.getPtime();
+ // Date is contained within this object.
+ date date_part = as_ptime.date();
+
+ // Verify weekday.
+ greg_weekday day_of_week = date_part.day_of_week();
+ EXPECT_EQ(exp_day_of_week, day_of_week.as_number());
+
+ // Verify day of month.
+ greg_day day = date_part.day();
+ EXPECT_EQ(exp_day, day.as_number());
+
+ // Verify month.
+ greg_month month = date_part.month();
+ EXPECT_EQ(exp_month, month.as_number());
+
+ // Verify year.
+ greg_year year = date_part.year();
+ EXPECT_EQ(exp_year, static_cast<unsigned short>(year));
+
+ // Retrieve time of the day and verify hour, minute and second.
+ time_duration time_of_day = as_ptime.time_of_day();
+ EXPECT_EQ(exp_hours, time_of_day.hours());
+ EXPECT_EQ(exp_minutes, time_of_day.minutes());
+ EXPECT_EQ(exp_seconds, time_of_day.seconds());
+ }
+
+ /// @brief Date/time value which should be set by the tests.
+ HttpDateTime date_time_;
+
+};
+
+// Test formatting as specified in RFC 1123.
+TEST_F(HttpDateTimeTest, rfc1123Format) {
+ date gdate(greg_year(2002), greg_month(1), greg_day(20));
+ time_duration tm(23, 59, 59, 0);
+ ptime t = ptime(gdate, tm);
+ HttpDateTime date_time(t);
+ std::string formatted;
+ ASSERT_NO_THROW(formatted = date_time.rfc1123Format());
+ EXPECT_EQ("Sun, 20 Jan 2002 23:59:59 GMT", formatted);
+}
+
+// Test formatting as specified in RFC 850.
+TEST_F(HttpDateTimeTest, rfc850Format) {
+ date gdate(greg_year(1994), greg_month(8), greg_day(6));
+ time_duration tm(11, 12, 13, 0);
+ ptime t = ptime(gdate, tm);
+
+ HttpDateTime date_time(t);
+ std::string formatted;
+ ASSERT_NO_THROW(formatted = date_time.rfc850Format());
+ EXPECT_EQ("Saturday, 06-Aug-94 11:12:13 GMT", formatted);
+}
+
+// Test formatting as output of asctime().
+TEST_F(HttpDateTimeTest, asctimeFormat) {
+ date gdate(greg_year(1999), greg_month(11), greg_day(2));
+ time_duration tm(03, 57, 12, 0);
+ ptime t = ptime(gdate, tm);
+
+ HttpDateTime date_time(t);
+ std::string formatted;
+ ASSERT_NO_THROW(formatted = date_time.asctimeFormat());
+ EXPECT_EQ("Tue Nov 2 03:57:12 1999", formatted);
+}
+
+// Test parsing time in RFC 1123 format.
+TEST_F(HttpDateTimeTest, fromRfc1123) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromRfc1123("Wed, 21 Dec 2016 18:53:45 GMT")
+ );
+ testDateTime(3, 21, 12, 2016, 18, 53, 45);
+ EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dex 2016 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 43 Dec 2016 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dec 16 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dec 2016 18:53:45"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dec 2016 1853:45 GMT"),
+ HttpTimeConversionError);
+}
+
+// Test parsing time in RFC 850 format.
+TEST_F(HttpDateTimeTest, fromRfc850) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromRfc850("Wednesday, 21-Dec-16 18:53:45 GMT");
+ );
+ testDateTime(3, 21, 12, 2016, 18, 53, 45);
+ EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 55-Dec-16 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 21-Dex-16 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 21-Dec-2016 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 21-Dec-16 1853:45 GMT"),
+ HttpTimeConversionError);
+}
+
+// Test parsing time in asctime() format.
+TEST_F(HttpDateTimeTest, fromRfcAsctime) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromAsctime("Wed Dec 21 08:49:37 2016");
+ );
+ testDateTime(3, 21, 12, 2016, 8, 49, 37);
+ EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dex 21 08:49:37 2016"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dec 55 08:49:37 2016"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dec 21 08:49:37 16"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dec 21 08:4937 2016"),
+ HttpTimeConversionError);
+}
+
+// Test parsing time in RFC 1123 format using HttpDateTime::fromAny().
+TEST_F(HttpDateTimeTest, fromAnyRfc1123) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromAny("Thu, 05 Jan 2017 09:15:06 GMT");
+ );
+ testDateTime(4, 5, 1, 2017, 9, 15, 06);
+}
+
+// Test parsing time in RFC 850 format using HttpDateTime::fromAny().
+TEST_F(HttpDateTimeTest, fromAnyRfc850) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromAny("Saturday, 18-Feb-17 01:02:10 GMT");
+ );
+ testDateTime(6, 18, 2, 2017, 1, 2, 10);
+}
+
+// Test parsing time in asctime() format using HttpDateTime::fromAny().
+TEST_F(HttpDateTimeTest, fromAnyAsctime) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromAny("Wed Mar 1 15:45:07 2017 GMT");
+ );
+ testDateTime(3, 1, 3, 2017, 15, 45, 7);
+}
+
+// Test that HttpDateTime::fromAny throws exception if unsupported format is
+// used.
+TEST_F(HttpDateTimeTest, fromAnyInvalidFormat) {
+ EXPECT_THROW(HttpDateTime::fromAsctime("20020131T235959"),
+ HttpTimeConversionError);
+}
+
+}
diff --git a/src/lib/http/tests/http_header_unittests.cc b/src/lib/http/tests/http_header_unittests.cc
new file mode 100644
index 0000000..df9d5bb
--- /dev/null
+++ b/src/lib/http/tests/http_header_unittests.cc
@@ -0,0 +1,54 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <exceptions/exceptions.h>
+#include <http/http_header.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::http;
+
+namespace {
+
+// Test that HTTP header can be created.
+TEST(HttpHeader, create) {
+ HttpHeader hdr("Content-Type", "application/json");
+ EXPECT_EQ("Content-Type", hdr.getName());
+ EXPECT_EQ("application/json", hdr.getValue());
+}
+
+// Test that the numeric value can be retrieved from a header and that
+// an exception is thrown if the header value is not a valid number.
+TEST(HttpHeader, getUint64Value) {
+ HttpHeader hdr64("Content-Length", "64");
+ EXPECT_EQ(64, hdr64.getUint64Value());
+
+ HttpHeader hdr_foo("Content-Length", "foo");
+ EXPECT_THROW(hdr_foo.getUint64Value(), isc::BadValue);
+}
+
+// Test that header name can be retrieved in lower case.
+TEST(HttpHeader, getLowerCaseName) {
+ HttpHeader hdr("ConnectioN", "Keep-Alive");
+ EXPECT_EQ("connection", hdr.getLowerCaseName());
+}
+
+// Test that header value can be retrieved in lower case.
+TEST(HttpHeader, getLowerCaseValue) {
+ HttpHeader hdr("Connection", "Keep-Alive");
+ EXPECT_EQ("keep-alive", hdr.getLowerCaseValue());
+}
+
+// Test that header value comparison is case insensitive.
+TEST(HttpHeader, equalsCaseInsensitive) {
+ HttpHeader hdr("Connection", "KeEp-ALIve");
+ EXPECT_TRUE(hdr.isValueEqual("keep-alive"));
+ EXPECT_TRUE(hdr.isValueEqual("KEEP-ALIVE"));
+ EXPECT_TRUE(hdr.isValueEqual("kEeP-AlIvE"));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/http/tests/http_thread_pool_unittests.cc b/src/lib/http/tests/http_thread_pool_unittests.cc
new file mode 100644
index 0000000..aa08ada
--- /dev/null
+++ b/src/lib/http/tests/http_thread_pool_unittests.cc
@@ -0,0 +1,265 @@
+// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <exceptions/exceptions.h>
+#include <http/http_thread_pool.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::http;
+
+namespace {
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Simple test fixture for testing HttpThreadPool.
+class HttpThreadPoolTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ HttpThreadPoolTest()
+ : io_service_(new IOService()) {
+ }
+
+ /// @brief Destructor.
+ virtual ~HttpThreadPoolTest() {
+ io_service_->stop();
+ }
+
+ /// @brief IOService instance used by thread pools.
+ IOServicePtr io_service_;
+};
+
+// URL contains scheme and hostname.
+TEST_F(HttpThreadPoolTest, invalidConstruction) {
+ HttpThreadPoolPtr pool;
+
+ // Constructing with pool size of 0 should fail.
+ ASSERT_THROW_MSG(pool.reset(new HttpThreadPool(io_service_, 0)), BadValue,
+ "pool_size must be non 0");
+}
+
+// Verifies that a pool can be created without starting it.
+TEST_F(HttpThreadPoolTest, deferredStartConstruction) {
+ HttpThreadPoolPtr pool;
+
+ ASSERT_NO_THROW_LOG(pool.reset(new HttpThreadPool(io_service_, 3, true)));
+
+ // State should be stopped.
+ // Pool size should be 3.
+ // IOService should be there.
+ // IOService is new, so it should not be stopped.
+ // No threads in the pool.
+ ASSERT_TRUE(pool->isStopped());
+ EXPECT_EQ(pool->getPoolSize(), 3);
+ ASSERT_TRUE(pool->getIOService());
+ EXPECT_FALSE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 0);
+
+ // Destructor should not throw.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+// Verifies that a pool can be started within the constructor.
+TEST_F(HttpThreadPoolTest, startDuringConstruction) {
+ HttpThreadPoolPtr pool;
+
+ ASSERT_NO_THROW_LOG(pool.reset(new HttpThreadPool(io_service_, 3)));
+
+ // State should be running.
+ // Pool size should be 3.
+ // IOService should be there.
+ // IOService is new, so it should not be stopped.
+ // Should have 3 threads in the pool.
+ ASSERT_TRUE(pool->isRunning());
+ EXPECT_EQ(pool->getPoolSize(), 3);
+ ASSERT_TRUE(pool->getIOService());
+ EXPECT_FALSE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 3);
+
+ // Destructor should not throw.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+// Verifies that pool can move from STOPPED to RUNNING.
+TEST_F(HttpThreadPoolTest, stoppedToRunning) {
+ HttpThreadPoolPtr pool;
+
+ // Create a stopped pool.
+ ASSERT_NO_THROW_LOG(pool.reset(new HttpThreadPool(io_service_, 3, true)));
+ ASSERT_TRUE(pool->isStopped());
+
+ // Call run from STOPPED.
+ ASSERT_NO_THROW_LOG(pool->run());
+
+ // State should be RUNNING, IOService should not be stopped, we should
+ // have 3 threads in the pool.
+ ASSERT_TRUE(pool->isRunning());
+ EXPECT_FALSE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 3);
+
+ // Calling run again should be harmless.
+ ASSERT_NO_THROW_LOG(pool->run());
+
+ // State should be RUNNING, IOService should not be stopped, we should
+ // have 3 threads in the pool.
+ ASSERT_TRUE(pool->isRunning());
+ EXPECT_FALSE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 3);
+
+ // Destroying the pool should be fine.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+// Verifies that pool can move from RUNNING to STOPPED.
+TEST_F(HttpThreadPoolTest, runningToStopped) {
+ HttpThreadPoolPtr pool;
+
+ // Create a running pool.
+ ASSERT_NO_THROW_LOG(pool.reset(new HttpThreadPool(io_service_, 3, false)));
+ ASSERT_TRUE(pool->isRunning());
+
+ // Call stop.
+ ASSERT_NO_THROW_LOG(pool->stop());
+
+ // State should be STOPPED, IOService should be stopped, we should
+ // have 0 threads in the pool.
+ ASSERT_TRUE(pool->isStopped());
+ EXPECT_TRUE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 0);
+
+ // Calling stop again should be harmless.
+ ASSERT_NO_THROW_LOG(pool->stop());
+
+ // State should be STOPPED, IOService should be stopped, we should
+ // have 0 threads in the pool.
+ ASSERT_TRUE(pool->isStopped());
+ EXPECT_TRUE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 0);
+
+ // Destroying the pool should be fine.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+// Verifies that pool can move from RUNNING to PAUSED.
+TEST_F(HttpThreadPoolTest, runningToPaused) {
+ HttpThreadPoolPtr pool;
+
+ // Create a running pool.
+ ASSERT_NO_THROW_LOG(pool.reset(new HttpThreadPool(io_service_, 3, false)));
+ ASSERT_TRUE(pool->isRunning());
+
+ // Call pause from RUNNING.
+ ASSERT_NO_THROW_LOG(pool->pause());
+
+ // State should be PAUSED, IOService should be stopped, we should
+ // have 3 threads in the pool.
+ ASSERT_TRUE(pool->isPaused());
+ EXPECT_TRUE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 3);
+
+ // Calling pause again should be harmless.
+ ASSERT_NO_THROW_LOG(pool->pause());
+
+ // State should be PAUSED, IOService should be stopped, we should
+ // have 3 threads in the pool.
+ ASSERT_TRUE(pool->isPaused());
+ EXPECT_TRUE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 3);
+
+ // Destroying the pool should be fine.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+// Verifies that pool can move from PAUSED to RUNNING.
+TEST_F(HttpThreadPoolTest, pausedToRunning) {
+ HttpThreadPoolPtr pool;
+
+ // Create a running pool.
+ ASSERT_NO_THROW_LOG(pool.reset(new HttpThreadPool(io_service_, 3, false)));
+ ASSERT_TRUE(pool->isRunning());
+
+ // Call pause from RUNNING.
+ ASSERT_NO_THROW_LOG(pool->pause());
+ ASSERT_TRUE(pool->isPaused());
+
+ // Call run.
+ ASSERT_NO_THROW_LOG(pool->run());
+
+ // State should be RUNNING, IOService should not be stopped, we should
+ // have 3 threads in the pool.
+ ASSERT_TRUE(pool->isRunning());
+ EXPECT_FALSE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 3);
+
+ // Destroying the pool should be fine.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+// Verifies that pool can move from PAUSED to STOPPED.
+TEST_F(HttpThreadPoolTest, pausedToStopped) {
+ HttpThreadPoolPtr pool;
+
+ // Create a running pool.
+ ASSERT_NO_THROW_LOG(pool.reset(new HttpThreadPool(io_service_, 3, false)));
+ ASSERT_TRUE(pool->isRunning());
+
+ // Call pause from RUNNING.
+ ASSERT_NO_THROW_LOG(pool->pause());
+ ASSERT_TRUE(pool->isPaused());
+
+ // Call stop.
+ ASSERT_NO_THROW_LOG(pool->stop());
+
+ // State should be STOPPED, IOService should be stopped, we should
+ // have 0 threads in the pool.
+ ASSERT_TRUE(pool->isStopped());
+ EXPECT_TRUE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 0);
+
+ // Destroying the pool should be fine.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+// Verifies that attempting to pause a STOPPED pool has no effect.
+TEST_F(HttpThreadPoolTest, stoppedToPaused) {
+ HttpThreadPoolPtr pool;
+
+ // Create a stopped pool.
+ ASSERT_NO_THROW_LOG(pool.reset(new HttpThreadPool(io_service_, 3, true)));
+ ASSERT_TRUE(pool->isStopped());
+
+ // State should be STOPPED, IOService won't be stopped because it was
+ // never started. We should have 0 threads in the pool.
+ ASSERT_TRUE(pool->isStopped());
+ EXPECT_FALSE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 0);
+
+ // Call pause from STOPPED.
+ ASSERT_NO_THROW_LOG(pool->pause());
+
+ // Should have no effect.
+ ASSERT_TRUE(pool->isStopped());
+
+ // State should be STOPPED, IOService won't be stopped because it was
+ // never started. We should have 0 threads in the pool.
+ ASSERT_TRUE(pool->isStopped());
+ EXPECT_FALSE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 0);
+
+ // Destroying the pool should be fine.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+}
diff --git a/src/lib/http/tests/post_request_json_unittests.cc b/src/lib/http/tests/post_request_json_unittests.cc
new file mode 100644
index 0000000..bb717cd
--- /dev/null
+++ b/src/lib/http/tests/post_request_json_unittests.cc
@@ -0,0 +1,197 @@
+// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/data.h>
+#include <http/http_types.h>
+#include <http/post_request_json.h>
+#include <http/tests/request_test.h>
+#include <gtest/gtest.h>
+#include <map>
+#include <sstream>
+
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Test fixture class for @ref PostHttpRequestJson.
+class PostHttpRequestJsonTest :
+ public HttpRequestTestBase<PostHttpRequestJson> {
+public:
+
+ /// @brief Constructor.
+ PostHttpRequestJsonTest()
+ : HttpRequestTestBase<PostHttpRequestJson>(),
+ json_body_("{ \"service\": \"dhcp4\", \"param1\": \"foo\" }") {
+ }
+
+ /// @brief Sets new JSON body for the HTTP request context.
+ ///
+ /// If the body parameter is empty, it will use the value of
+ /// @ref json_body_ member. Otherwise, it will assign the body
+ /// provided as parameter.
+ ///
+ /// @param body new body value.
+ void setBody(const std::string& body = "") {
+ request_->context()->body_ = body.empty() ? json_body_ : body;
+ }
+
+ /// @brief Default value of the JSON body.
+ std::string json_body_;
+};
+
+// This test verifies that PostHttpRequestJson class only accepts
+// POST messages.
+TEST_F(PostHttpRequestJsonTest, requiredPost) {
+ // Use a GET method that is not supported.
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ // Now use POST. It should be accepted.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ EXPECT_NO_THROW(request_->create());
+}
+
+// This test verifies that PostHttpRequest requires "Content-Length"
+// header equal to "application/json".
+TEST_F(PostHttpRequestJsonTest, requireContentTypeJson) {
+ // Specify "Content-Type" other than "application/json".
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "text/html");
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ // This time specify correct "Content-Type". It should pass.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ EXPECT_NO_THROW(request_->create());
+}
+
+// This test verifies that PostHttpRequest requires "Content-Length"
+// header.
+TEST_F(PostHttpRequestJsonTest, requireContentLength) {
+ // "Content-Length" is not specified initially. It should fail.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Type", "text/html");
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ // Specify "Content-Length". It should pass.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+}
+
+// This test verifies that JSON body can be retrieved from the
+// HTTP request.
+TEST_F(PostHttpRequestJsonTest, getBodyAsJson) {
+ // Create HTTP POST request with JSON body.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+ setBody();
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Try to retrieve pointer to the root element of the JSON body.
+ ConstElementPtr json = request_->getBodyAsJson();
+ ASSERT_TRUE(json);
+
+ // Iterate over JSON values and store them in a simple map.
+ std::map<std::string, std::string> config_values;
+ for (auto config_element = json->mapValue().begin();
+ config_element != json->mapValue().end();
+ ++config_element) {
+ ASSERT_FALSE(config_element->first.empty());
+ ASSERT_TRUE(config_element->second);
+ config_values[config_element->first] = config_element->second->stringValue();
+ }
+
+ // Verify the values.
+ EXPECT_EQ("dhcp4", config_values["service"]);
+ EXPECT_EQ("foo", config_values["param1"]);
+}
+
+// This test verifies that an attempt to parse/retrieve malformed
+// JSON structure will cause an exception.
+TEST_F(PostHttpRequestJsonTest, getBodyAsJsonMalformed) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+ // No colon before 123.
+ setBody("{ \"command\" 123 }" );
+
+ EXPECT_THROW(request_->finalize(), HttpRequestJsonError);
+}
+
+// This test verifies that NULL pointer is returned when trying to
+// retrieve root element of the empty JSON structure.
+TEST_F(PostHttpRequestJsonTest, getEmptyJsonBody) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ ConstElementPtr json = request_->getBodyAsJson();
+ EXPECT_FALSE(json);
+}
+
+// This test verifies that the specific JSON element can be retrieved.
+TEST_F(PostHttpRequestJsonTest, getJsonElement) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+ setBody();
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ ConstElementPtr element;
+ ASSERT_NO_THROW(element = request_->getJsonElement("service"));
+ ASSERT_TRUE(element);
+ EXPECT_EQ("dhcp4", element->stringValue());
+
+ // An attempt to retrieve non-existing element should return NULL.
+ EXPECT_FALSE(request_->getJsonElement("bar"));
+}
+
+// This test verifies that it is possible to create client side request
+// containing JSON body.
+TEST_F(PostHttpRequestJsonTest, clientRequest) {
+ request_->setDirection(HttpMessage::OUTBOUND);
+
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Type", "application/json");
+
+ ElementPtr json = Element::fromJSON(json_body_);
+ request_->setBodyAsJson(json);
+
+ // Commit and validate the data.
+ ASSERT_NO_THROW(request_->finalize());
+
+ std::ostringstream expected_request_text;
+ expected_request_text << "POST /isc/org HTTP/1.0\r\n"
+ "Content-Length: " << json->str().size() << "\r\n"
+ "Content-Type: application/json\r\n"
+ "\r\n"
+ << json->str();
+
+ EXPECT_EQ(expected_request_text.str(), request_->toString());
+}
+
+}
diff --git a/src/lib/http/tests/post_request_unittests.cc b/src/lib/http/tests/post_request_unittests.cc
new file mode 100644
index 0000000..18f2fda
--- /dev/null
+++ b/src/lib/http/tests/post_request_unittests.cc
@@ -0,0 +1,83 @@
+// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/post_request.h>
+#include <http/tests/request_test.h>
+#include <gtest/gtest.h>
+
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Test fixture class for @ref PostHttpRequest.
+class PostHttpRequestTest : public HttpRequestTestBase<PostHttpRequest> {
+public:
+
+ /// @brief Constructor.
+ PostHttpRequestTest()
+ : HttpRequestTestBase<PostHttpRequest>(),
+ json_body_("{ \"service\": \"dhcp4\", \"param1\": \"foo\" }") {
+ }
+
+ /// @brief Default value of the JSON body.
+ std::string json_body_;
+};
+
+// This test verifies that PostHttpRequest class only accepts POST
+// messages.
+TEST_F(PostHttpRequestTest, requirePost) {
+ // Use a GET method that is not supported.
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ // Now use POST. It should be accepted.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ EXPECT_NO_THROW(request_->create());
+}
+
+// This test verifies that PostHttpRequest requires "Content-Length"
+// header.
+TEST_F(PostHttpRequestTest, requireContentType) {
+ // No "Content-Type". It should fail.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ // There is "Content-Type". It should pass.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "text/html");
+
+ EXPECT_NO_THROW(request_->create());
+
+}
+
+// This test verifies that PostHttpRequest requires "Content-Type"
+// header.
+TEST_F(PostHttpRequestTest, requireContentLength) {
+ // No "Content-Length". It should fail.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Type", "text/html");
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ // There is "Content-Length". It should pass.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+}
+
+}
diff --git a/src/lib/http/tests/request_parser_unittests.cc b/src/lib/http/tests/request_parser_unittests.cc
new file mode 100644
index 0000000..0756711
--- /dev/null
+++ b/src/lib/http/tests/request_parser_unittests.cc
@@ -0,0 +1,387 @@
+// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/data.h>
+#include <http/http_types.h>
+#include <http/request_parser.h>
+#include <http/post_request_json.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace isc::data;
+using namespace isc::http;
+
+namespace {
+
+/// @brief Test fixture class for @ref HttpRequestParser.
+class HttpRequestParserTest : public ::testing::Test {
+public:
+
+ /// @brief Creates HTTP request string.
+ ///
+ /// @param preamble A string including HTTP request's first line
+ /// and all headers except "Content-Length".
+ /// @param payload A string containing HTTP request payload.
+ std::string createRequestString(const std::string& preamble,
+ const std::string& payload) {
+ std::ostringstream s;
+ s << preamble;
+ s << "Content-Length: " << payload.length() << "\r\n\r\n"
+ << payload;
+ return (s.str());
+ }
+
+ /// @brief Parses the HTTP request and checks that parsing was
+ /// successful.
+ ///
+ /// @param http_req HTTP request string.
+ void doParse(const std::string& http_req) {
+ HttpRequestParser parser(request_);
+ ASSERT_NO_THROW(parser.initModel());
+
+ parser.postBuffer(&http_req[0], http_req.size());
+ ASSERT_NO_THROW(parser.poll());
+
+ ASSERT_FALSE(parser.needData());
+ ASSERT_TRUE(parser.httpParseOk());
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+ }
+
+ /// @brief Tests that parsing fails when malformed HTTP request
+ /// is received.
+ ///
+ /// @param http_req HTTP request string.
+ void testInvalidHttpRequest(const std::string& http_req) {
+ HttpRequestParser parser(request_);
+ ASSERT_NO_THROW(parser.initModel());
+
+ parser.postBuffer(&http_req[0], http_req.size());
+ ASSERT_NO_THROW(parser.poll());
+
+ EXPECT_FALSE(parser.needData());
+ EXPECT_FALSE(parser.httpParseOk());
+ EXPECT_FALSE(parser.getErrorMessage().empty());
+ }
+
+ /// @brief Instance of the HttpRequest used by the unit tests.
+ HttpRequest request_;
+};
+
+// Test test verifies that an HTTP request including JSON body is parsed
+// successfully.
+TEST_F(HttpRequestParserTest, postHttpRequestWithJson) {
+ std::string http_req = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n";
+ std::string json = "{ \"service\": \"dhcp4\", \"command\": \"shutdown\" }";
+
+ http_req = createRequestString(http_req, json);
+
+ // Create HTTP request which accepts POST method and JSON as a body.
+ PostHttpRequestJson request;
+
+ // Create a parser and make it use the request we created.
+ HttpRequestParser parser(request);
+ ASSERT_NO_THROW(parser.initModel());
+
+ // Simulate receiving HTTP request in chunks.
+ for (size_t i = 0; i < http_req.size(); i += http_req.size() / 10) {
+ bool done = false;
+ // Get the size of the data chunk.
+ size_t chunk = http_req.size() / 10;
+ // When we're near the end of the data stream, the chunk length may
+ // vary.
+ if (i + chunk > http_req.size()) {
+ chunk = http_req.size() - i;
+ done = true;
+ }
+ // Feed the parser with a data chunk and parse it.
+ parser.postBuffer(&http_req[i], chunk);
+ parser.poll();
+ if (!done) {
+ ASSERT_TRUE(parser.needData());
+ }
+ }
+
+ // Parser should have parsed the request and should expect no more data.
+ ASSERT_FALSE(parser.needData());
+ // Parsing should be successful.
+ ASSERT_TRUE(parser.httpParseOk());
+ // There should be no error message.
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+
+ // Verify parsed headers etc.
+ EXPECT_EQ(HttpRequest::Method::HTTP_POST, request.getMethod());
+ EXPECT_EQ("/foo/bar", request.getUri());
+ EXPECT_EQ("application/json", request.getHeaderValue("Content-Type"));
+ EXPECT_EQ(json.length(), request.getHeaderValueAsUint64("Content-Length"));
+ EXPECT_EQ(1, request.getHttpVersion().major_);
+ EXPECT_EQ(0, request.getHttpVersion().minor_);
+
+ // Try to retrieve values carried in JSON payload.
+ ConstElementPtr json_element;
+ ASSERT_NO_THROW(json_element = request.getJsonElement("service"));
+ EXPECT_EQ("dhcp4", json_element->stringValue());
+
+ ASSERT_NO_THROW(json_element = request.getJsonElement("command"));
+ EXPECT_EQ("shutdown", json_element->stringValue());
+}
+
+// This test verifies that extraneous data in the request will not cause
+// an error if "Content-Length" value refers to the length of the valid
+// part of the request.
+TEST_F(HttpRequestParserTest, extraneousDataInRequest) {
+ std::string http_req = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n";
+ std::string json = "{ \"service\": \"dhcp4\", \"command\": \"shutdown\" }";
+
+ // Create valid request;
+ http_req = createRequestString(http_req, json);
+
+ // Add some garbage at the end.
+ http_req += "some stuff which, if parsed, will cause errors";
+
+ // Create HTTP request which accepts POST method and JSON as a body.
+ PostHttpRequestJson request;
+
+ // Create a parser and make it use the request we created.
+ HttpRequestParser parser(request);
+ ASSERT_NO_THROW(parser.initModel());
+
+ // Feed the parser with the request containing some garbage at the end.
+ parser.postBuffer(&http_req[0], http_req.size());
+ ASSERT_NO_THROW(parser.poll());
+
+ // The parser should only parse the valid part of the request as indicated
+ // by the Content-Length.
+ ASSERT_FALSE(parser.needData());
+ ASSERT_TRUE(parser.httpParseOk());
+ // There should be no error message.
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+
+ // Do another poll() to see if the parser will parse the garbage. We
+ // expect that it doesn't.
+ ASSERT_NO_THROW(parser.poll());
+ EXPECT_FALSE(parser.needData());
+ EXPECT_TRUE(parser.httpParseOk());
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+}
+
+
+// This test verifies that LWS is parsed correctly. The LWS marks line breaks
+// in the HTTP header values.
+TEST_F(HttpRequestParserTest, getLWS) {
+ // "User-Agent" header contains line breaks with whitespaces in the new
+ // lines to mark continuation of the header value.
+ std::string http_req = "GET /foo/bar HTTP/1.1\r\n"
+ "Content-Type: text/html\r\n"
+ "User-Agent: Kea/1.2 Command \r\n"
+ " Control \r\n"
+ "\tClient\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ // Verify parsed values.
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ("Kea/1.2 Command Control Client",
+ request_.getHeaderValue("User-Agent"));
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(1, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that the HTTP request with no headers is
+// parsed correctly.
+TEST_F(HttpRequestParserTest, noHeaders) {
+ std::string http_req = "GET /foo/bar HTTP/1.1\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ // Verify the values.
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(1, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that the HTTP method can be specified in lower
+// case.
+TEST_F(HttpRequestParserTest, getLowerCase) {
+ std::string http_req = "get /foo/bar HTTP/1.1\r\n"
+ "Content-Type: text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(1, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that headers are case insensitive.
+TEST_F(HttpRequestParserTest, headersCaseInsensitive) {
+ std::string http_req = "get /foo/bar HTTP/1.1\r\n"
+ "Content-type: text/html\r\n"
+ "connection: keep-Alive\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeader("Content-Type")->getValue());
+ EXPECT_EQ("keep-alive", request_.getHeader("Connection")->getLowerCaseValue());
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(1, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that other value of the HTTP version can be
+// specified in the request.
+TEST_F(HttpRequestParserTest, http20) {
+ std::string http_req = "get /foo/bar HTTP/2.0\r\n"
+ "Content-Type: text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(2, request_.getHttpVersion().major_);
+ EXPECT_EQ(0, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that the header with no whitespace between the
+// colon and header value is accepted.
+TEST_F(HttpRequestParserTest, noHeaderWhitespace) {
+ std::string http_req = "get /foo/bar HTTP/1.0\r\n"
+ "Content-Type:text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(0, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that the header value preceded with multiple
+// whitespaces is accepted.
+TEST_F(HttpRequestParserTest, multipleLeadingHeaderWhitespaces) {
+ std::string http_req = "get /foo/bar HTTP/1.0\r\n"
+ "Content-Type: text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(0, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that error is reported when unsupported HTTP
+// method is used.
+TEST_F(HttpRequestParserTest, unsupportedMethod) {
+ std::string http_req = "POSTX /foo/bar HTTP/2.0\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when URI contains
+// an invalid character.
+TEST_F(HttpRequestParserTest, invalidUri) {
+ std::string http_req = "POST /foo/\r HTTP/2.0\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that the request containing a typo in the
+// HTTP version string causes parsing error.
+TEST_F(HttpRequestParserTest, invalidHTTPString) {
+ std::string http_req = "POST /foo/ HTLP/2.0\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when the HTTP version
+// string doesn't contain a slash character.
+TEST_F(HttpRequestParserTest, invalidHttpVersionNoSlash) {
+ std::string http_req = "POST /foo/ HTTP 1.1\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when HTTP version string
+// doesn't contain the minor version number.
+TEST_F(HttpRequestParserTest, invalidHttpNoMinorVersion) {
+ std::string http_req = "POST /foo/ HTTP/1\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when HTTP header name
+// contains an invalid character.
+TEST_F(HttpRequestParserTest, invalidHeaderName) {
+ std::string http_req = "POST /foo/ HTTP/1.1\r\n"
+ "Content-;: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when HTTP header value
+// is not preceded with the colon character.
+TEST_F(HttpRequestParserTest, noColonInHttpHeader) {
+ std::string http_req = "POST /foo/ HTTP/1.1\r\n"
+ "Content-Type text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that the input buffer of the HTTP request can be
+// retrieved as text formatted for logging.
+TEST_F(HttpRequestParserTest, getBufferAsString) {
+ std::string http_req = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n";
+
+ // Create HTTP request.
+ PostHttpRequestJson request;
+
+ // Create a parser and make it use the request we created.
+ HttpRequestParser parser(request);
+ ASSERT_NO_THROW(parser.initModel());
+
+ // Insert data into the request.
+ ASSERT_NO_THROW(parser.postBuffer(&http_req[0], http_req.size()));
+
+ // limit = 0 means no limit
+ EXPECT_EQ(http_req, parser.getBufferAsString(0));
+
+ // large enough limit should not cause the truncation.
+ EXPECT_EQ(http_req, parser.getBufferAsString(1024));
+
+ // Only 3 characters requested. The request should be truncated.
+ EXPECT_EQ("POS.........\n(truncating HTTP message larger than 3 characters)\n",
+ parser.getBufferAsString(3));
+}
+
+TEST_F(HttpRequestParserTest, parseEmptyRequest) {
+ std::string http_req = "POST / HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n";
+ std::string json = "";
+
+ http_req = createRequestString(http_req, json);
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_POST, request_.getMethod());
+ EXPECT_EQ("/", request_.getUri());
+ EXPECT_EQ("", request_.getBody());
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(1, request_.getHttpVersion().minor_);
+}
+
+}
diff --git a/src/lib/http/tests/request_test.h b/src/lib/http/tests/request_test.h
new file mode 100644
index 0000000..f73b31f
--- /dev/null
+++ b/src/lib/http/tests/request_test.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_REQUEST_TEST_H
+#define HTTP_REQUEST_TEST_H
+
+#include <http/http_types.h>
+#include <http/request.h>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <string>
+#include <utility>
+
+namespace isc {
+namespace http {
+namespace test {
+
+/// @brief Base test fixture class for testing @ref HttpRequest class and its
+/// derivations.
+///
+/// @tparam HttpRequestType Class under test.
+template<typename HttpRequestType>
+class HttpRequestTestBase : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates HTTP request to be used in unit tests.
+ HttpRequestTestBase()
+ : request_(new HttpRequestType()) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Does nothing.
+ virtual ~HttpRequestTestBase() {
+ }
+
+ /// @brief Initializes HTTP request context with basic information.
+ ///
+ /// It sets:
+ /// - HTTP method,
+ /// - URI,
+ /// - HTTP version number.
+ ///
+ /// @param method HTTP method as string.
+ /// @param uri URI.
+ /// @param version A pair of values of which the first is the major HTTP
+ /// version and the second is the minor HTTP version.
+ void setContextBasics(const std::string& method, const std::string& uri,
+ const HttpVersion& version) {
+ request_->context()->method_ = method;
+ request_->context()->uri_ = uri;
+ request_->context()->http_version_major_ = version.major_;
+ request_->context()->http_version_minor_ = version.minor_;
+ }
+
+ /// @brief Adds HTTP header to the context.
+ ///
+ /// @param header_name HTTP header name.
+ /// @param header_value HTTP header value. This value will be converted to
+ /// a string using @c boost::lexical_cast.
+ /// @tparam ValueType Header value type.
+ template<typename ValueType>
+ void addHeaderToContext(const std::string& header_name,
+ const ValueType& header_value) {
+ request_->context()->headers_.push_back(HttpHeaderContext(header_name, header_value));
+ }
+
+ /// @brief Instance of the @ref HttpRequest or its derivation.
+ boost::shared_ptr<HttpRequestType> request_;
+};
+
+} // namespace test
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/tests/request_unittests.cc b/src/lib/http/tests/request_unittests.cc
new file mode 100644
index 0000000..f902400
--- /dev/null
+++ b/src/lib/http/tests/request_unittests.cc
@@ -0,0 +1,422 @@
+// Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/request.h>
+#include <http/date_time.h>
+#include <http/http_header.h>
+#include <http/http_types.h>
+#include <http/tests/request_test.h>
+#include <boost/lexical_cast.hpp>
+#include <gtest/gtest.h>
+#include <utility>
+
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Test fixture class for @c HttpRequest class.
+class HttpRequestTest : public HttpRequestTestBase<HttpRequest> {
+public:
+
+ /// @brief Tests connection persistence for the given HTTP version
+ /// and header value.
+ ///
+ /// This method creates a dummy HTTP request and sets the specified
+ /// version and header. Next, it returns the value if @c isPersistent
+ /// method for this request. The unit test verifies this value for
+ /// correctness.
+ ///
+ /// @param http_version HTTP version.
+ /// @param http_header HTTP header to be included in the request. If
+ /// the header has an empty value, it is not included.
+ ///
+ /// @return true if request indicates that connection is to be
+ /// persistent.
+ bool isPersistent(const HttpVersion& http_version,
+ const HttpHeader& http_header = HttpHeader("Connection")) {
+ try {
+ // We need to add some JSON body.
+ std::string json_body = "{ \"param1\": \"foo\" }";
+
+ // Set method, path, version and content length.
+ setContextBasics("POST", "/isc/org", http_version);
+ addHeaderToContext("Content-Length", json_body.length());
+
+ // If additional header has been specified (typically "Connection"),
+ // include it.
+ if (!http_header.getValue().empty()) {
+ addHeaderToContext(http_header.getName(), http_header.getValue());
+ }
+ // Attach JSON body.
+ request_->context()->body_ = json_body;
+ request_->create();
+
+ } catch (...) {
+ ADD_FAILURE() << "failed to create HTTP request while testing"
+ " connection persistence";
+ }
+
+ return (request_->isPersistent());
+ }
+
+};
+
+// This test verifies that a minimal request can be created.
+TEST_F(HttpRequestTest, minimal) {
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 1));
+ ASSERT_NO_THROW(request_->create());
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_->getMethod());
+ EXPECT_EQ("/isc/org", request_->getUri());
+ EXPECT_EQ(1, request_->getHttpVersion().major_);
+ EXPECT_EQ(1, request_->getHttpVersion().minor_);
+ EXPECT_TRUE(request_->getRemote().empty());
+ request_->setRemote("127.0.0.1");
+ EXPECT_EQ("127.0.0.1", request_->getRemote());
+
+ EXPECT_THROW(request_->getHeaderValue("Content-Length"),
+ HttpMessageNonExistingHeader);
+}
+
+// This test verifies that empty Host header is included in the
+// request if it is not explicitly specified.
+TEST_F(HttpRequestTest, hostHeaderDefault) {
+ ASSERT_NO_THROW(request_.reset(new HttpRequest(HttpRequest::Method::HTTP_GET,
+ "/isc/org",
+ HttpVersion(1, 0))));
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_->getMethod());
+ EXPECT_EQ("/isc/org", request_->getUri());
+ EXPECT_EQ(1, request_->getHttpVersion().major_);
+ EXPECT_EQ(0, request_->getHttpVersion().minor_);
+
+ std::string host_hdr;
+ ASSERT_NO_THROW(host_hdr = request_->getHeaderValue("Host"));
+ EXPECT_TRUE(host_hdr.empty());
+}
+
+// This test verifies that it is possible to explicitly specify a
+// Host header value while creating a request.
+TEST_F(HttpRequestTest, hostHeaderCustom) {
+ ASSERT_NO_THROW(request_.reset(new HttpRequest(HttpRequest::Method::HTTP_GET,
+ "/isc/org",
+ HttpVersion(1, 1),
+ HostHttpHeader("www.example.org"))));
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_->getMethod());
+ EXPECT_EQ("/isc/org", request_->getUri());
+ EXPECT_EQ(1, request_->getHttpVersion().major_);
+ EXPECT_EQ(1, request_->getHttpVersion().minor_);
+
+ std::string host_hdr;
+ ASSERT_NO_THROW(host_hdr = request_->getHeaderValue("Host"));
+ EXPECT_EQ("www.example.org", host_hdr);
+}
+
+// This test verifies that headers can be included in a request.
+TEST_F(HttpRequestTest, includeHeaders) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", "1024");
+ addHeaderToContext("Content-Type", "application/json");
+ ASSERT_NO_THROW(request_->create());
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_POST, request_->getMethod());
+ EXPECT_EQ("/isc/org", request_->getUri());
+ EXPECT_EQ(1, request_->getHttpVersion().major_);
+ EXPECT_EQ(0, request_->getHttpVersion().minor_);
+
+ std::string content_type;
+ ASSERT_NO_THROW(content_type = request_->getHeaderValue("Content-Type"));
+ EXPECT_EQ("application/json", content_type);
+
+ uint64_t content_length;
+ ASSERT_NO_THROW(
+ content_length = request_->getHeaderValueAsUint64("Content-Length")
+ );
+ EXPECT_EQ(1024, content_length);
+}
+
+// This test verifies that it is possible to specify required
+// methods for the request and that an error is thrown if the
+// selected method doesn't match.
+TEST_F(HttpRequestTest, requiredMethods) {
+ request_->requireHttpMethod(HttpRequest::Method::HTTP_GET);
+ request_->requireHttpMethod(HttpRequest::Method::HTTP_POST);
+
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 1));
+
+ ASSERT_NO_THROW(request_->create());
+
+ request_->context()->method_ = "POST";
+ ASSERT_NO_THROW(request_->create());
+
+ request_->context()->method_ = "PUT";
+ EXPECT_THROW(request_->create(), HttpRequestError);
+}
+
+// This test verifies that it is possible to specify required
+// HTTP version for the request and that an error is thrown if
+// the selected HTTP version doesn't match.
+TEST_F(HttpRequestTest, requiredHttpVersion) {
+ request_->requireHttpVersion(HttpVersion(1, 0));
+ request_->requireHttpVersion(HttpVersion(1, 1));
+
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ EXPECT_NO_THROW(request_->create());
+
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 1));
+ EXPECT_NO_THROW(request_->create());
+
+ setContextBasics("POST", "/isc/org", HttpVersion(2, 0));
+ EXPECT_THROW(request_->create(), HttpRequestError);
+}
+
+// This test verifies that it is possible to specify required
+// HTTP headers for the request and that an error is thrown if
+// the required header is not included.
+TEST_F(HttpRequestTest, requiredHeader) {
+ request_->requireHeader("Content-Length");
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ addHeaderToContext("Content-Type", "application/json");
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ addHeaderToContext("Content-Length", "2048");
+ EXPECT_NO_THROW(request_->create());
+}
+
+// This test verifies that it is possible to specify required
+// HTTP header value for the request and that an error is thrown
+// if the value doesn't match.
+TEST_F(HttpRequestTest, requiredHeaderValue) {
+ request_->requireHeaderValue("Content-Type", "application/json");
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Type", "text/html");
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ addHeaderToContext("Content-Type", "application/json");
+
+ EXPECT_NO_THROW(request_->create());
+}
+
+// This test verifies that an error is thrown upon an attempt to
+// fetch request properties before the request is finalized.
+TEST_F(HttpRequestTest, notCreated) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Type", "text/html");
+ addHeaderToContext("Content-Length", "1024");
+
+ EXPECT_THROW(static_cast<void>(request_->getMethod()), HttpMessageError);
+ EXPECT_THROW(static_cast<void>(request_->getHttpVersion()),
+ HttpMessageError);
+ EXPECT_THROW(static_cast<void>(request_->getUri()), HttpMessageError);
+ EXPECT_THROW(static_cast<void>(request_->getHeaderValue("Content-Type")),
+ HttpMessageError);
+ EXPECT_THROW(static_cast<void>(request_->getHeaderValueAsUint64("Content-Length")),
+ HttpMessageError);
+ EXPECT_THROW(static_cast<void>(request_->getBody()), HttpMessageError);
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ EXPECT_NO_THROW(static_cast<void>(request_->getMethod()));
+ EXPECT_NO_THROW(static_cast<void>(request_->getHttpVersion()));
+ EXPECT_NO_THROW(static_cast<void>(request_->getUri()));
+ EXPECT_NO_THROW(static_cast<void>(request_->getHeaderValue("Content-Type")));
+ EXPECT_NO_THROW(
+ static_cast<void>(request_->getHeaderValueAsUint64("Content-Length"))
+ );
+ EXPECT_NO_THROW(static_cast<void>(request_->getBody()));
+}
+
+// This test verifies that it is possible to fetch the request
+// body.
+TEST_F(HttpRequestTest, getBody) {
+ std::string json_body = "{ \"param1\": \"foo\" }";
+
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body.length());
+
+ request_->context()->body_ = json_body;
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ EXPECT_EQ(json_body, request_->getBody());
+}
+
+// This test verifies the behavior of the requiresBody function.
+TEST_F(HttpRequestTest, requiresBody) {
+ ASSERT_FALSE(request_->requiresBody());
+ request_->requireHeader("Content-Length");
+ EXPECT_TRUE(request_->requiresBody());
+}
+
+// This test verifies that HTTP/1.0 connections are not persistent
+// by default.
+TEST_F(HttpRequestTest, isPersistentHttp10) {
+ // In HTTP 1.0 the connection is by default non-persistent.
+ EXPECT_FALSE(isPersistent(HttpVersion(1, 0)));
+}
+
+// This test verifies that HTTP/1.1 connections are persistent
+// by default.
+TEST_F(HttpRequestTest, isPersistentHttp11) {
+ // In HTTP 1.1 the connection is by default persistent.
+ EXPECT_TRUE(isPersistent(HttpVersion(1, 1)));
+}
+
+// This test verifies that HTTP/1.0 connection becomes persistent
+// when keep-alive value of the Connection header is included.
+TEST_F(HttpRequestTest, isPersistentHttp10KeepAlive) {
+ // In HTTP 1.0 the client indicates that the connection is desired to be
+ // persistent by including "Connection: keep-alive" header.
+ EXPECT_TRUE(
+ isPersistent(HttpVersion(1, 0), HttpHeader("Connection", "Keep-alive"))
+ );
+}
+
+// This test verifies that HTTP/1.1 connection is closed when the
+// close value of the Connection header is included.
+TEST_F(HttpRequestTest, isPersistentHttp11Close) {
+ // In HTTP 1.1 the client would include "Connection: close" header if it
+ // desires to close the connection.
+ EXPECT_FALSE(
+ isPersistent(HttpVersion(1, 1), HttpHeader("Connection", "close"))
+ );
+}
+
+// This test verifies the contents of the HTTP outbound request.
+TEST_F(HttpRequestTest, clientRequest) {
+ ASSERT_NO_THROW(
+ request_.reset(new HttpRequest(HttpRequest::Method::HTTP_POST,
+ "/isc/org",
+ HttpVersion(1, 0),
+ HostHttpHeader("www.example.org")));
+ );
+
+ // Capture current date and time.
+ HttpDateTime date_time;
+
+ // Add headers.
+ request_->context()->headers_.push_back(HttpHeaderContext("Date", date_time.rfc1123Format()));
+ request_->context()->headers_.push_back(HttpHeaderContext("Content-Type", "text/html"));
+ request_->context()->headers_.push_back(HttpHeaderContext("Accept", "text/html"));
+ // Add a body.
+ request_->context()->body_ = "<html></html>";
+ // Commit and validate the data.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Check that the HTTP request in the textual format is correct. Note that
+ // it should include "Content-Length", even though we haven't explicitly set
+ // this header. It is dynamically computed from the body size.
+ EXPECT_EQ("POST /isc/org HTTP/1.0\r\n"
+ "Host: www.example.org\r\n"
+ "Accept: text/html\r\n"
+ "Content-Length: 13\r\n"
+ "Content-Type: text/html\r\n"
+ "Date: " + date_time.rfc1123Format() + "\r\n"
+ "\r\n"
+ "<html></html>",
+ request_->toString());
+}
+
+// This test verifies the contents of the HTTP outbound request
+// which lacks body.
+TEST_F(HttpRequestTest, clientRequestNoBody) {
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 1));
+ // Add headers.
+ request_->context()->headers_.push_back(HttpHeaderContext("Content-Type", "text/html"));
+ // Commit and validate the data.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Check that the HTTP request in the textual format is correct. Note that
+ // there should be no Content-Length included, because the body is empty.
+ EXPECT_EQ("GET /isc/org HTTP/1.1\r\n"
+ "Content-Type: text/html\r\n"
+ "\r\n",
+ request_->toString());
+}
+
+// This test verifies the first line of the HTTP request.
+TEST_F(HttpRequestTest, toBriefString) {
+ // Create the request.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 1));
+ // Add headers.
+ request_->context()->headers_.push_back(HttpHeaderContext("Content-Type", "application/json"));
+ // Must be finalized before can be used.
+ ASSERT_NO_THROW(request_->finalize());
+ // Check that the brief string is correct.
+ EXPECT_EQ("POST /isc/org HTTP/1.1", request_->toBriefString());
+}
+
+// This test verifies that no basic HTTP authentication is supported.
+TEST_F(HttpRequestTest, noBasicAuth) {
+ ASSERT_NO_THROW(request_.reset(new HttpRequest(HttpRequest::Method::HTTP_GET,
+ "/isc/org",
+ HttpVersion(1, 1),
+ HostHttpHeader("www.example.org"))));
+
+ ASSERT_NO_THROW(request_->finalize());
+ ASSERT_THROW(request_->getHeader("Authorization"),
+ HttpMessageNonExistingHeader);
+}
+
+// This test verifies that basic HTTP authentication works as expected.
+TEST_F(HttpRequestTest, basicAuth) {
+ BasicHttpAuthPtr basic_auth;
+ EXPECT_NO_THROW(basic_auth.reset(new BasicHttpAuth("foo", "bar")));
+ ASSERT_TRUE(basic_auth);
+
+ ASSERT_NO_THROW(request_.reset(new HttpRequest(HttpRequest::Method::HTTP_GET,
+ "/isc/org",
+ HttpVersion(1, 1),
+ HostHttpHeader("www.example.org"),
+ basic_auth)));
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ std::string value;
+ EXPECT_NO_THROW(value = request_->getHeaderValue("Authorization"));
+ EXPECT_EQ(value, "Basic " + basic_auth->getCredential());
+}
+
+/// This test verifies that access parameters are handled as expected.
+TEST_F(HttpRequestTest, parameters) {
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 1));
+ ASSERT_NO_THROW(request_->create());
+
+ EXPECT_TRUE(request_->getRemote().empty());
+ EXPECT_FALSE(request_->getTls());
+ EXPECT_TRUE(request_->getSubject().empty());
+ EXPECT_TRUE(request_->getIssuer().empty());
+ EXPECT_TRUE(request_->getBasicAuth().empty());
+ EXPECT_TRUE(request_->getCustom().empty());
+
+ request_->setRemote("my-remote");
+ request_->setTls(true);
+ request_->setSubject("my-subject");
+ request_->setIssuer("my-issuer");
+ request_->setBasicAuth("foo");
+ request_->setCustom("bar");
+
+ EXPECT_EQ("my-remote", request_->getRemote());
+ EXPECT_TRUE(request_->getTls());
+ EXPECT_EQ("my-subject", request_->getSubject());
+ EXPECT_EQ("my-issuer", request_->getIssuer());
+ EXPECT_EQ("foo", request_->getBasicAuth());
+ EXPECT_EQ("bar", request_->getCustom());
+}
+
+}
diff --git a/src/lib/http/tests/response_creator_unittests.cc b/src/lib/http/tests/response_creator_unittests.cc
new file mode 100644
index 0000000..c5370cf
--- /dev/null
+++ b/src/lib/http/tests/response_creator_unittests.cc
@@ -0,0 +1,340 @@
+// Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <http/basic_auth.h>
+#include <http/basic_auth_config.h>
+#include <http/http_types.h>
+#include <http/request.h>
+#include <http/response.h>
+#include <http/response_creator.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <testutils/log_utils.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp::test;
+using namespace isc::http;
+using namespace isc::http::test;
+using namespace std;
+
+namespace {
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new HttpRequest()));
+ }
+
+private:
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed ok).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // This will generate the response holding JSON content.
+ ResponsePtr response(new Response(http_version, status_code));
+ response->finalize();
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP OK response with no content.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) {
+ // The simplest thing is to create a response with no content.
+ // We don't need content to test our class.
+ ResponsePtr response(new Response(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ response->finalize();
+ return (response);
+ }
+};
+
+/// @brief Pointer to test HTTP response creator.
+typedef boost::shared_ptr<TestHttpResponseCreator> TestHttpResponseCreatorPtr;
+
+// This test verifies that Bad Request status is generated when the request
+// hasn't been finalized.
+TEST(HttpResponseCreatorTest, badRequest) {
+ HttpResponsePtr response;
+ // Create a request but do not finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+
+ // Use test specific implementation of Response Creator. It should
+ // generate HTTP error 400.
+ TestHttpResponseCreator creator;
+ ASSERT_NO_THROW(response = creator.createHttpResponse(request));
+ ASSERT_TRUE(response);
+
+ EXPECT_EQ("HTTP/1.0 400 Bad Request\r\n"
+ "Content-Length: 40\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n\r\n"
+ "{ \"result\": 400, \"text\": \"Bad Request\" }",
+ response->toString());
+}
+
+// This test verifies that response is generated successfully from the
+// finalized/parsed request.
+TEST(HttpResponseCreatorTest, goodRequest) {
+ // There is no credentials so it checks also what happens when
+ // authentication is not required.
+
+ HttpResponsePtr response;
+ // Create request and finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+ ASSERT_NO_THROW(request->finalize());
+
+ // Use test specific implementation of the Response Creator to generate
+ // a response.
+ TestHttpResponseCreator creator;
+ ASSERT_NO_THROW(response = creator.createHttpResponse(request));
+ ASSERT_TRUE(response);
+
+ EXPECT_EQ("HTTP/1.0 200 OK\r\n"
+ "Content-Length: 0\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n\r\n",
+ response->toString());
+}
+
+/// @brief Test fixture for HTTP response creator authentication.
+class HttpResponseCreatorAuthTest : public LogContentTest { };
+
+// This test verifies that missing required authentication header gives
+// unauthorized error.
+TEST_F(HttpResponseCreatorAuthTest, noAuth) {
+ // Create basic HTTP authentication configuration.
+ BasicHttpAuthConfigPtr auth_config(new BasicHttpAuthConfig());
+ EXPECT_NO_THROW(auth_config->add("test", "", "123\xa3", ""));
+ const BasicHttpAuthMap& credentials = auth_config->getCredentialMap();
+ auto cred = credentials.find("dGVzdDoxMjPCow==");
+ EXPECT_NE(cred, credentials.end());
+ EXPECT_EQ(cred->second, "test");
+ auth_config->setRealm("ISC.ORG");
+
+ // Create request and finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+ ASSERT_NO_THROW(request->finalize());
+ HttpRequest::recordBasicAuth_ = true;
+
+ HttpResponsePtr response;
+ TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());;
+ ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request));
+ ASSERT_TRUE(response);
+
+ EXPECT_EQ("HTTP/1.0 401 Unauthorized\r\n"
+ "Content-Length: 41\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "WWW-Authenticate: Basic realm=\"ISC.ORG\"\r\n\r\n"
+ "{ \"result\": 401, \"text\": \"Unauthorized\" }",
+ response->toString());
+
+ EXPECT_TRUE(request->getBasicAuth().empty());
+ addString("HTTP_CLIENT_REQUEST_NO_AUTH_HEADER received HTTP request "
+ "without required authentication header");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies that too short authentication header is rejected.
+TEST_F(HttpResponseCreatorAuthTest, authTooShort) {
+ // Create basic HTTP authentication configuration.
+ BasicHttpAuthConfigPtr auth_config(new BasicHttpAuthConfig());
+ EXPECT_NO_THROW(auth_config->add("test", "", "123\xa3", ""));
+ const BasicHttpAuthMap& credentials = auth_config->getCredentialMap();
+ auto cred = credentials.find("dGVzdDoxMjPCow==");
+ EXPECT_NE(cred, credentials.end());
+ EXPECT_EQ(cred->second, "test");
+ auth_config->setRealm("ISC.ORG");
+
+ // Create request and finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+ HttpHeaderContext auth("Authorization", "Basic =");
+ request->context()->headers_.push_back(auth);
+ ASSERT_NO_THROW(request->finalize());
+ HttpRequest::recordBasicAuth_ = true;
+
+ HttpResponsePtr response;
+ TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());;
+ ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request));
+ ASSERT_TRUE(response);
+
+ EXPECT_EQ("HTTP/1.0 401 Unauthorized\r\n"
+ "Content-Length: 41\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "WWW-Authenticate: Basic realm=\"ISC.ORG\"\r\n\r\n"
+ "{ \"result\": 401, \"text\": \"Unauthorized\" }",
+ response->toString());
+
+ EXPECT_TRUE(request->getBasicAuth().empty());
+ addString("HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER received HTTP request "
+ "with malformed authentication header: "
+ "header content is too short");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies that another authentication schema is rejected.
+TEST_F(HttpResponseCreatorAuthTest, badScheme) {
+ // Create basic HTTP authentication configuration.
+ BasicHttpAuthConfigPtr auth_config(new BasicHttpAuthConfig());
+ EXPECT_NO_THROW(auth_config->add("test", "", "123\xa3", ""));
+ const BasicHttpAuthMap& credentials = auth_config->getCredentialMap();
+ auto cred = credentials.find("dGVzdDoxMjPCow==");
+ EXPECT_NE(cred, credentials.end());
+ EXPECT_EQ(cred->second, "test");
+ auth_config->setRealm("ISC.ORG");
+
+ // Create request and finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+ HttpHeaderContext auth("Authorization", "Basis dGVzdDoxMjPCow==");
+ request->context()->headers_.push_back(auth);
+ ASSERT_NO_THROW(request->finalize());
+ HttpRequest::recordBasicAuth_ = true;
+
+ HttpResponsePtr response;
+ TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());;
+ ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request));
+ ASSERT_TRUE(response);
+
+ EXPECT_EQ("HTTP/1.0 401 Unauthorized\r\n"
+ "Content-Length: 41\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "WWW-Authenticate: Basic realm=\"ISC.ORG\"\r\n\r\n"
+ "{ \"result\": 401, \"text\": \"Unauthorized\" }",
+ response->toString());
+
+ EXPECT_TRUE(request->getBasicAuth().empty());
+ addString("HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER received HTTP request "
+ "with malformed authentication header: "
+ "not basic authentication");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies that not matching credential is rejected.
+TEST_F(HttpResponseCreatorAuthTest, notMatching) {
+ // Create basic HTTP authentication configuration.
+ BasicHttpAuthConfigPtr auth_config(new BasicHttpAuthConfig());
+ EXPECT_NO_THROW(auth_config->add("test", "", "123\xa3", ""));
+ const BasicHttpAuthMap& credentials = auth_config->getCredentialMap();
+ auto cred = credentials.find("dGVzdDoxMjPCow==");
+ EXPECT_NE(cred, credentials.end());
+ EXPECT_EQ(cred->second, "test");
+ auth_config->setRealm("ISC.ORG");
+
+ // Create request and finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+ // Slightly different credential...
+ HttpHeaderContext auth("Authorization", "Basic dGvZdDoxMjPcOw==");
+ request->context()->headers_.push_back(auth);
+ ASSERT_NO_THROW(request->finalize());
+ HttpRequest::recordBasicAuth_ = true;
+
+ HttpResponsePtr response;
+ TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());;
+ ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request));
+ ASSERT_TRUE(response);
+
+ EXPECT_EQ("HTTP/1.0 401 Unauthorized\r\n"
+ "Content-Length: 41\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "WWW-Authenticate: Basic realm=\"ISC.ORG\"\r\n\r\n"
+ "{ \"result\": 401, \"text\": \"Unauthorized\" }",
+ response->toString());
+
+ EXPECT_TRUE(request->getBasicAuth().empty());
+ addString("HTTP_CLIENT_REQUEST_NOT_AUTHORIZED received HTTP request "
+ "with not matching authentication header");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies that matching credential is accepted.
+TEST_F(HttpResponseCreatorAuthTest, matching) {
+ // Create basic HTTP authentication configuration.
+ BasicHttpAuthConfigPtr auth_config(new BasicHttpAuthConfig());
+ EXPECT_NO_THROW(auth_config->add("test", "", "123\xa3", ""));
+ const BasicHttpAuthMap& credentials = auth_config->getCredentialMap();
+ auto cred = credentials.find("dGVzdDoxMjPCow==");
+ EXPECT_NE(cred, credentials.end());
+ EXPECT_EQ(cred->second, "test");
+ auth_config->setRealm("ISC.ORG");
+
+ // Create request and finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+ HttpHeaderContext auth("Authorization", "Basic dGVzdDoxMjPCow==");
+ request->context()->headers_.push_back(auth);
+ ASSERT_NO_THROW(request->finalize());
+ HttpRequest::recordBasicAuth_ = true;
+
+ HttpResponsePtr response;
+ TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());;
+ ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request));
+ EXPECT_FALSE(response);
+
+ EXPECT_EQ("test", request->getBasicAuth());
+ addString("HTTP_CLIENT_REQUEST_AUTHORIZED received HTTP request "
+ "authorized for 'test'");
+ EXPECT_TRUE(checkFile());
+ HttpRequest::recordBasicAuth_ = false;
+}
+
+}
diff --git a/src/lib/http/tests/response_json_unittests.cc b/src/lib/http/tests/response_json_unittests.cc
new file mode 100644
index 0000000..e3829b5
--- /dev/null
+++ b/src/lib/http/tests/response_json_unittests.cc
@@ -0,0 +1,151 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <cc/data.h>
+#include <http/http_types.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Response type used in tests.
+typedef TestHttpResponseBase<HttpResponseJson> TestHttpResponseJson;
+
+/// @brief Test fixture class for @ref HttpResponseJson.
+class HttpResponseJsonTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes the following class members:
+ /// - json_string_ - which is a pretty formatted JSON content,
+ /// - json_ - A structure of Element objects representing the JSON,
+ /// - json_string_from_json_ - which is a JSON text converted back from
+ /// the json_ data structure. It is the same content as json_string_
+ /// but has different whitespaces.
+ HttpResponseJsonTest()
+ : json_(), json_string_(), json_string_from_json_() {
+ json_string_ =
+ "["
+ " {"
+ " \"pid\": 8080,"
+ " \"status\": 10,"
+ " \"comment\": \"Nice comment from 8080\""
+ " },"
+ " {"
+ " \"pid\": 8081,"
+ " \"status\": 12,"
+ " \"comment\": \"A comment from 8081\""
+ " }"
+ "]";
+
+ json_ = Element::fromJSON(json_string_);
+ json_string_from_json_ = json_->str();
+ }
+
+ /// @brief Test that the response format is correct.
+ ///
+ /// @param status_code HTTP status code for which the response should
+ /// be tested.
+ /// @param status_message HTTP status message.
+ void testGenericResponse(const HttpStatusCode& status_code,
+ const std::string& status_message) {
+ TestHttpResponseJson response(HttpVersion(1, 0), status_code);
+ ASSERT_NO_THROW(response.finalize());
+ std::ostringstream status_message_json;
+ // Build the expected content.
+ status_message_json << "{ \"result\": "
+ << static_cast<uint16_t>(status_code)
+ << ", \"text\": "
+ << "\"" << status_message << "\" }";
+ std::ostringstream response_string;
+ response_string <<
+ "HTTP/1.0 " << static_cast<uint16_t>(status_code) << " "
+ << status_message << "\r\n";
+
+ // The content must only be generated for error codes.
+ if (HttpResponse::isClientError(status_code) ||
+ HttpResponse::isServerError(status_code)) {
+ response_string << "Content-Length: " << status_message_json.str().size()
+ << "\r\n";
+ } else {
+ response_string << "Content-Length: 0\r\n";
+ }
+
+ // Content-Type and Date are automatically included.
+ response_string << "Content-Type: application/json\r\n"
+ "Date: " << response.getDateHeaderValue() << "\r\n\r\n";
+
+ if (HttpResponse::isClientError(status_code) ||
+ HttpResponse::isServerError(status_code)) {
+ response_string << status_message_json.str();
+ }
+
+ // Check that the output is as expected.
+ EXPECT_EQ(response_string.str(), response.toString());
+ }
+
+ /// @brief JSON content represented as structure of Element objects.
+ ConstElementPtr json_;
+
+ /// @brief Pretty formatted JSON content.
+ std::string json_string_;
+
+ /// @brief JSON content parsed back from json_ structure.
+ std::string json_string_from_json_;
+
+};
+
+// Test that the response with custom JSON content is generated properly.
+TEST_F(HttpResponseJsonTest, responseWithContent) {
+ TestHttpResponseJson response(HttpVersion(1, 1), HttpStatusCode::OK);
+ ASSERT_NO_THROW(response.setBodyAsJson(json_));
+ ASSERT_NO_THROW(response.finalize());
+
+ std::ostringstream response_string;
+ response_string <<
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: " << json_string_from_json_.length() << "\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: " << response.getDateHeaderValue() << "\r\n\r\n"
+ << json_string_from_json_;
+ EXPECT_EQ(response_string.str(), response.toString());
+}
+
+// Test that generic responses are created properly.
+TEST_F(HttpResponseJsonTest, genericResponse) {
+ testGenericResponse(HttpStatusCode::OK, "OK");
+ testGenericResponse(HttpStatusCode::CREATED, "Created");
+ testGenericResponse(HttpStatusCode::ACCEPTED, "Accepted");
+ testGenericResponse(HttpStatusCode::NO_CONTENT, "No Content");
+ testGenericResponse(HttpStatusCode::MULTIPLE_CHOICES,
+ "Multiple Choices");
+ testGenericResponse(HttpStatusCode::MOVED_PERMANENTLY,
+ "Moved Permanently");
+ testGenericResponse(HttpStatusCode::MOVED_TEMPORARILY,
+ "Moved Temporarily");
+ testGenericResponse(HttpStatusCode::NOT_MODIFIED, "Not Modified");
+ testGenericResponse(HttpStatusCode::BAD_REQUEST, "Bad Request");
+ testGenericResponse(HttpStatusCode::UNAUTHORIZED, "Unauthorized");
+ testGenericResponse(HttpStatusCode::FORBIDDEN, "Forbidden");
+ testGenericResponse(HttpStatusCode::NOT_FOUND, "Not Found");
+ testGenericResponse(HttpStatusCode::REQUEST_TIMEOUT, "Request Timeout");
+ testGenericResponse(HttpStatusCode::INTERNAL_SERVER_ERROR,
+ "Internal Server Error");
+ testGenericResponse(HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented");
+ testGenericResponse(HttpStatusCode::BAD_GATEWAY, "Bad Gateway");
+ testGenericResponse(HttpStatusCode::SERVICE_UNAVAILABLE,
+ "Service Unavailable");
+}
+
+}
diff --git a/src/lib/http/tests/response_parser_unittests.cc b/src/lib/http/tests/response_parser_unittests.cc
new file mode 100644
index 0000000..58d479d
--- /dev/null
+++ b/src/lib/http/tests/response_parser_unittests.cc
@@ -0,0 +1,351 @@
+// Copyright (C) 2017-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/data.h>
+#include <http/response_json.h>
+#include <http/response_parser.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::http;
+
+namespace {
+
+/// @brief Test fixture class for @ref HttpResponseParser.
+class HttpResponseParserTest : public ::testing::Test {
+public:
+
+ /// @brief Creates HTTP response string.
+ ///
+ /// @param preamble A string including HTTP response's first line
+ /// and all headers except "Content-Length".
+ /// @param payload A string containing HTTP response payload.
+ std::string createResponseString(const std::string& preamble,
+ const std::string& payload) {
+ std::ostringstream s;
+ s << preamble;
+ s << "Content-Length: " << payload.length() << "\r\n\r\n"
+ << payload;
+ return (s.str());
+ }
+
+ /// @brief Parses the HTTP response and checks that parsing was
+ /// successful.
+ ///
+ /// @param http_resp HTTP response string.
+ void doParse(const std::string& http_resp) {
+ HttpResponseParser parser(response_);
+ ASSERT_NO_THROW(parser.initModel());
+
+ parser.postBuffer(&http_resp[0], http_resp.size());
+ ASSERT_NO_THROW(parser.poll());
+
+ ASSERT_FALSE(parser.needData());
+ ASSERT_TRUE(parser.httpParseOk());
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+ }
+
+ /// @brief Tests that parsing fails when malformed HTTP response
+ /// is received.
+ ///
+ /// @param http_resp HTTP response string.
+ void testInvalidHttpResponse(const std::string& http_resp) {
+ HttpResponseParser parser(response_);
+ ASSERT_NO_THROW(parser.initModel());
+
+ parser.postBuffer(&http_resp[0], http_resp.size());
+ ASSERT_NO_THROW(parser.poll());
+
+ EXPECT_FALSE(parser.needData());
+ EXPECT_FALSE(parser.httpParseOk());
+ EXPECT_FALSE(parser.getErrorMessage().empty());
+ }
+
+ /// @brief Tests that the response specified with (header, body) can
+ /// be parsed properly.
+ ///
+ /// @param header specifies the header of the response to be parsed
+ /// @param json specifies the body of the response (JSON in text format) to be parsed
+ /// @param expect_success whether the parsing is expected to be successful
+ ///
+ /// @return a parser that parsed the response for further inspection
+ HttpResponseJson testResponseWithJson(const std::string& header,
+ const std::string& json,
+ bool expect_success = true) {
+ std::string http_resp = createResponseString(header, json);
+
+ // Create HTTP response which accepts JSON as a body.
+ HttpResponseJson response;
+
+ // Create a parser and make it use the response we created.
+ HttpResponseParser parser(response);
+ EXPECT_NO_THROW(parser.initModel());
+
+ // Simulate receiving HTTP response in chunks.
+ const unsigned chunk_size = 10;
+ while (!http_resp.empty()) {
+ size_t chunk = http_resp.size() % chunk_size;
+ if (chunk == 0) {
+ chunk = chunk_size;
+ }
+
+ parser.postBuffer(&http_resp[0], chunk);
+ http_resp.erase(0, chunk);
+ parser.poll();
+ if (chunk < chunk_size) {
+ EXPECT_TRUE(parser.needData());
+ if (!parser.needData()) {
+ ADD_FAILURE() << "Parser completed prematurely";
+ return (response);
+ }
+ }
+ }
+
+ if (expect_success) {
+ // Parser should have parsed the response and should expect no more data.
+ EXPECT_FALSE(parser.needData());
+ // Parsing should be successful.
+ EXPECT_TRUE(parser.httpParseOk()) << parser.getErrorMessage();
+ // There should be no error message.
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+ }
+
+ return (response);
+ }
+
+ /// @brief Instance of the HttpResponse used by the unit tests.
+ HttpResponse response_;
+};
+
+// Test test verifies that an HTTP response including JSON body is parsed
+// successfully.
+TEST_F(HttpResponseParserTest, responseWithJson) {
+ std::string http_resp = "HTTP/1.1 408 Request Timeout\r\n"
+ "Content-Type: application/json\r\n";
+ std::string json = "{ \"result\": 0, \"text\": \"All ok\" }";
+
+ HttpResponseJson response = testResponseWithJson(http_resp, json);
+
+ // Verify HTTP version, status code and phrase.
+ EXPECT_EQ(1, response.getHttpVersion().major_);
+ EXPECT_EQ(1, response.getHttpVersion().minor_);
+ EXPECT_EQ(HttpStatusCode::REQUEST_TIMEOUT, response.getStatusCode());
+ EXPECT_EQ("Request Timeout", response.getStatusPhrase());
+
+ // Try to retrieve values carried in JSON payload.
+ ConstElementPtr json_element;
+ ASSERT_NO_THROW(json_element = response.getJsonElement("result"));
+ EXPECT_EQ(0, json_element->intValue());
+
+ ASSERT_NO_THROW(json_element = response.getJsonElement("text"));
+ EXPECT_EQ("All ok", json_element->stringValue());
+}
+
+// This test verifies that extraneous data in the response will not cause
+// an error if "Content-Length" value refers to the length of the valid
+// part of the response.
+TEST_F(HttpResponseParserTest, extraneousDataInResponse) {
+ std::string http_resp = "HTTP/1.0 200 OK\r\n"
+ "Content-Type: application/json\r\n";
+ std::string json = "{ \"service\": \"dhcp4\", \"command\": \"shutdown\" }";
+
+ // Create valid response.
+ http_resp = createResponseString(http_resp, json);
+
+ // Add some garbage at the end.
+ http_resp += "some stuff which, if parsed, will cause errors";
+
+ // Create HTTP response which accepts JSON as a body.
+ HttpResponseJson response;
+
+ // Create a parser and make it use the response we created.
+ HttpResponseParser parser(response);
+ ASSERT_NO_THROW(parser.initModel());
+
+ // Feed the parser with the response containing some garbage at the end.
+ parser.postBuffer(&http_resp[0], http_resp.size());
+ ASSERT_NO_THROW(parser.poll());
+
+ // The parser should only parse the valid part of the response as indicated
+ // by the Content-Length.
+ ASSERT_FALSE(parser.needData());
+ ASSERT_TRUE(parser.httpParseOk());
+ // There should be no error message.
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+
+ // Do another poll() to see if the parser will parse the garbage. We
+ // expect that it doesn't.
+ ASSERT_NO_THROW(parser.poll());
+ EXPECT_FALSE(parser.needData());
+ EXPECT_TRUE(parser.httpParseOk());
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+}
+
+// This test verifies that LWS is parsed correctly. The LWS (linear white
+// space) marks line breaks in the HTTP header values.
+TEST_F(HttpResponseParserTest, getLWS) {
+ // "User-Agent" header contains line breaks with whitespaces in the new
+ // lines to mark continuation of the header value.
+ std::string http_resp = "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html\r\n"
+ "User-Agent: Kea/1.2 Command \r\n"
+ " Control \r\n"
+ "\tClient\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_resp));
+
+ // Verify parsed values.
+ EXPECT_EQ(1, response_.getHttpVersion().major_);
+ EXPECT_EQ(1, response_.getHttpVersion().minor_);
+ EXPECT_EQ(HttpStatusCode::OK, response_.getStatusCode());
+ EXPECT_EQ("OK", response_.getStatusPhrase());
+ EXPECT_EQ("text/html", response_.getHeaderValue("Content-Type"));
+ EXPECT_EQ("Kea/1.2 Command Control Client",
+ response_.getHeaderValue("User-Agent"));
+}
+
+// This test verifies that the HTTP response with no headers is
+// parsed correctly.
+TEST_F(HttpResponseParserTest, noHeaders) {
+ std::string http_resp = "HTTP/1.1 204 No Content\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_resp));
+
+ // Verify the values.
+ EXPECT_EQ(1, response_.getHttpVersion().major_);
+ EXPECT_EQ(1, response_.getHttpVersion().minor_);
+ EXPECT_EQ(HttpStatusCode::NO_CONTENT, response_.getStatusCode());
+}
+
+// This test verifies that headers are case insensitive.
+TEST_F(HttpResponseParserTest, headersCaseInsensitive) {
+ std::string http_resp = "HTTP/1.1 200 OK\r\n"
+ "Content-type: text/html\r\n"
+ "connection: clOSe\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_resp));
+
+ EXPECT_EQ("text/html", response_.getHeader("Content-Type")->getValue());
+ EXPECT_EQ("close", response_.getHeader("Connection")->getLowerCaseValue());
+ EXPECT_EQ(1, response_.getHttpVersion().major_);
+ EXPECT_EQ(1, response_.getHttpVersion().minor_);
+ EXPECT_EQ(HttpStatusCode::OK, response_.getStatusCode());
+ EXPECT_EQ("OK", response_.getStatusPhrase());
+}
+
+// This test verifies that the header with no whitespace between the
+// colon and header value is accepted.
+TEST_F(HttpResponseParserTest, noHeaderWhitespace) {
+ std::string http_resp = "HTTP/1.0 200 OK\r\n"
+ "Content-Type:text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_resp));
+
+ EXPECT_EQ("text/html", response_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(1, response_.getHttpVersion().major_);
+ EXPECT_EQ(0, response_.getHttpVersion().minor_);
+ EXPECT_EQ(HttpStatusCode::OK, response_.getStatusCode());
+ EXPECT_EQ("OK", response_.getStatusPhrase());
+}
+
+// This test verifies that the header value preceded with multiple
+// whitespaces is accepted.
+TEST_F(HttpResponseParserTest, multipleLeadingHeaderWhitespaces) {
+ std::string http_resp = "HTTP/1.0 200 OK\r\n"
+ "Content-Type: text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_resp));
+
+ EXPECT_EQ("text/html", response_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(1, response_.getHttpVersion().major_);
+ EXPECT_EQ(0, response_.getHttpVersion().minor_);
+ EXPECT_EQ(HttpStatusCode::OK, response_.getStatusCode());
+ EXPECT_EQ("OK", response_.getStatusPhrase());
+}
+
+// This test verifies that the response containing a typo in the
+// HTTP version string causes parsing error.
+TEST_F(HttpResponseParserTest, invalidHTTPString) {
+ std::string http_resp = "HTLP/2.0 100 OK\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpResponse(http_resp);
+}
+
+// This test verifies that error is reported when the HTTP version
+// string doesn't contain a slash character.
+TEST_F(HttpResponseParserTest, invalidHttpVersionNoSlash) {
+ std::string http_resp = "HTTP 1.1 100 OK\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpResponse(http_resp);
+}
+
+// This test verifies that error is reported when HTTP version string
+// doesn't contain the minor version number.
+TEST_F(HttpResponseParserTest, invalidHttpNoMinorVersion) {
+ std::string http_resp = "HTTP/1 200 OK\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpResponse(http_resp);
+}
+
+// This test verifies that error is reported when HTTP header name
+// contains an invalid character.
+TEST_F(HttpResponseParserTest, invalidHeaderName) {
+ std::string http_resp = "HTTP/1.1 200 OK\r\n"
+ "Content-;: text/html\r\n\r\n";
+ testInvalidHttpResponse(http_resp);
+}
+
+// This test verifies that error is reported when HTTP header value
+// is not preceded with the colon character.
+TEST_F(HttpResponseParserTest, noColonInHttpHeader) {
+ std::string http_resp = "HTTP/1.1 200 OK\r\n"
+ "Content-Type text/html\r\n\r\n";
+ testInvalidHttpResponse(http_resp);
+}
+
+// This test verifies that the HTTP response is formatted for logging.
+TEST_F(HttpResponseParserTest, logFormatHttpMessage) {
+ std::string message = "POST / HTTP/1.1\r\n"
+ "Host: 127.0.0.1:8080\r\n"
+ "User-Agent: curl/7.59.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 51\r\n\r\n"
+ "{ \"command\": \"config-get\", \"service\": [ \"dhcp4\" ] }";
+
+ // limit = 0 means no limit
+ EXPECT_EQ(message, HttpResponseParser::logFormatHttpMessage(message, 0));
+
+ // large enough limit should not cause the truncation.
+ EXPECT_EQ(message, HttpResponseParser::logFormatHttpMessage(message, 1024));
+
+ // Only 3 characters requested. The request should be truncated.
+ EXPECT_EQ("POS.........\n(truncating HTTP message larger than 3 characters)\n",
+ HttpResponseParser::logFormatHttpMessage(message, 3));
+}
+
+TEST_F(HttpResponseParserTest, parseEmptyResponse) {
+ std::string http_resp = "HTTP/1.1 200 OK\r\n"
+ "Content-Type: application/json\r\n";
+ std::string json = "";
+
+ http_resp = createResponseString(http_resp, json);
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_resp));
+
+ HttpResponseJson response = testResponseWithJson(http_resp, json);
+
+ EXPECT_EQ("", response_.getBody());
+ EXPECT_EQ(1, response_.getHttpVersion().major_);
+ EXPECT_EQ(1, response_.getHttpVersion().minor_);
+ EXPECT_EQ(HttpStatusCode::OK, response_.getStatusCode());
+ EXPECT_EQ("OK", response_.getStatusPhrase());
+}
+
+}
diff --git a/src/lib/http/tests/response_test.h b/src/lib/http/tests/response_test.h
new file mode 100644
index 0000000..d342a64
--- /dev/null
+++ b/src/lib/http/tests/response_test.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_TEST_H
+#define HTTP_RESPONSE_TEST_H
+
+#include <http/http_types.h>
+#include <http/response.h>
+#include <boost/lexical_cast.hpp>
+#include <cstdint>
+
+namespace isc {
+namespace http {
+namespace test {
+
+/// @brief Base class for test HTTP response.
+template<typename HttpResponseType>
+class TestHttpResponseBase : public HttpResponseType {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param version HTTP version for the response.
+ /// @param status_code HTTP status code.
+ TestHttpResponseBase(const HttpVersion& version,
+ const HttpStatusCode& status_code)
+ : HttpResponseType(version, status_code) {
+ }
+
+ /// @brief Returns fixed header value.
+ ///
+ /// Including fixed header value in the response makes the
+ /// response deterministic, which is critical for the unit
+ /// tests.
+ virtual std::string getDateHeaderValue() const {
+ return ("Tue, 19 Dec 2016 18:53:35 GMT");
+ }
+
+ /// @brief Returns date header value.
+ std::string generateDateHeaderValue() const {
+ return (HttpResponseType::getDateHeaderValue());
+ }
+
+ /// @brief Sets custom content length.
+ ///
+ /// @param content_length Content length value.
+ void setContentLength(const uint64_t content_length) {
+ HttpHeaderPtr length_header(new HttpHeader("Content-Length",
+ boost::lexical_cast<std::string>
+ (content_length)));
+ HttpResponseType::headers_["content-length"] = length_header;
+ }
+};
+
+} // namespace test
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/tests/response_unittests.cc b/src/lib/http/tests/response_unittests.cc
new file mode 100644
index 0000000..f54e65d
--- /dev/null
+++ b/src/lib/http/tests/response_unittests.cc
@@ -0,0 +1,169 @@
+// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <http/date_time.h>
+#include <http/http_types.h>
+#include <http/response.h>
+#include <http/tests/response_test.h>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+using namespace boost::posix_time;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Response type used in tests.
+typedef TestHttpResponseBase<HttpResponse> TestHttpResponse;
+
+/// @brief Test fixture class for @ref HttpResponse.
+class HttpResponseTest : public ::testing::Test {
+public:
+
+ /// @brief Checks if the format of the response is correct.
+ ///
+ /// @param status_code HTTP status code in the response.
+ /// @param status_message HTTP status message in the response.
+ void testResponse(const HttpStatusCode& status_code,
+ const std::string& status_message) {
+ // Create the response. Because we're using derived class
+ // it returns the fixed value of the Date header, which is
+ // very useful in unit tests.
+ TestHttpResponse response(HttpVersion(1, 0), status_code);
+ response.context()->headers_.push_back(HttpHeaderContext("Content-Type", "text/html"));
+ ASSERT_NO_THROW(response.finalize());
+ std::ostringstream response_string;
+ response_string << "HTTP/1.0 " << static_cast<uint16_t>(status_code)
+ << " " << status_message;
+ EXPECT_EQ(response_string.str(), response.toBriefString());
+
+ response_string
+ << "\r\nContent-Length: 0\r\n"
+ << "Content-Type: text/html\r\n"
+ << "Date: " << response.getDateHeaderValue() << "\r\n\r\n";
+ EXPECT_EQ(response_string.str(), response.toString());
+ }
+};
+
+// Test the case of HTTP OK message.
+TEST_F(HttpResponseTest, responseOK) {
+ // Include HTML body.
+ const std::string sample_body =
+ "<html>"
+ "<head><title>Kea page title</title></head>"
+ "<body><h1>Some header</h1></body>"
+ "</html>";
+
+ // Create the message and add some headers.
+ TestHttpResponse response(HttpVersion(1, 0), HttpStatusCode::OK);
+ response.context()->headers_.push_back(HttpHeaderContext("Content-Type", "text/html"));
+ response.context()->headers_.push_back(HttpHeaderContext("Host", "kea.example.org"));
+ response.context()->body_ = sample_body;
+ ASSERT_NO_THROW(response.finalize());
+
+ // Create a string holding expected response. Note that the Date
+ // is a fixed value returned by the customized TestHttpResponse
+ // class.
+ std::ostringstream response_string;
+ response_string <<
+ "HTTP/1.0 200 OK\r\n"
+ "Content-Length: " << sample_body.length() << "\r\n"
+ "Content-Type: text/html\r\n"
+ "Date: " << response.getDateHeaderValue() << "\r\n"
+ "Host: kea.example.org\r\n\r\n" << sample_body;
+
+ EXPECT_EQ(response_string.str(), response.toString());
+}
+
+// Test generic responses for various status codes.
+TEST_F(HttpResponseTest, genericResponse) {
+ testResponse(HttpStatusCode::OK, "OK");
+ testResponse(HttpStatusCode::CREATED, "Created");
+ testResponse(HttpStatusCode::ACCEPTED, "Accepted");
+ testResponse(HttpStatusCode::NO_CONTENT, "No Content");
+ testResponse(HttpStatusCode::MULTIPLE_CHOICES, "Multiple Choices");
+ testResponse(HttpStatusCode::MOVED_PERMANENTLY, "Moved Permanently");
+ testResponse(HttpStatusCode::MOVED_TEMPORARILY, "Moved Temporarily");
+ testResponse(HttpStatusCode::NOT_MODIFIED, "Not Modified");
+ testResponse(HttpStatusCode::BAD_REQUEST, "Bad Request");
+ testResponse(HttpStatusCode::UNAUTHORIZED, "Unauthorized");
+ testResponse(HttpStatusCode::FORBIDDEN, "Forbidden");
+ testResponse(HttpStatusCode::NOT_FOUND, "Not Found");
+ testResponse(HttpStatusCode::REQUEST_TIMEOUT, "Request Timeout");
+ testResponse(HttpStatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error");
+ testResponse(HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented");
+ testResponse(HttpStatusCode::BAD_GATEWAY, "Bad Gateway");
+ testResponse(HttpStatusCode::SERVICE_UNAVAILABLE, "Service Unavailable");
+}
+
+// Test if the class correctly identifies client errors.
+TEST_F(HttpResponseTest, isClientError) {
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::OK));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::CREATED));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::ACCEPTED));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::NO_CONTENT));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::MULTIPLE_CHOICES));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::MOVED_PERMANENTLY));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::MOVED_TEMPORARILY));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::NOT_MODIFIED));
+ EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::BAD_REQUEST));
+ EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::UNAUTHORIZED));
+ EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::FORBIDDEN));
+ EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::NOT_FOUND));
+ EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::REQUEST_TIMEOUT));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::INTERNAL_SERVER_ERROR));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::NOT_IMPLEMENTED));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::BAD_GATEWAY));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::SERVICE_UNAVAILABLE));
+}
+
+// Test if the class correctly identifies server errors.
+TEST_F(HttpResponseTest, isServerError) {
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::OK));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::CREATED));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::ACCEPTED));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::NO_CONTENT));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::MULTIPLE_CHOICES));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::MOVED_PERMANENTLY));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::MOVED_TEMPORARILY));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::NOT_MODIFIED));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::BAD_REQUEST));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::UNAUTHORIZED));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::FORBIDDEN));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::NOT_FOUND));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::REQUEST_TIMEOUT));
+ EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::INTERNAL_SERVER_ERROR));
+ EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::NOT_IMPLEMENTED));
+ EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::BAD_GATEWAY));
+ EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::SERVICE_UNAVAILABLE));
+}
+
+// Test that the generated time value, being included in the Date
+// header, is correct.
+TEST_F(HttpResponseTest, getDateHeaderValue) {
+ // Create a response and retrieve the value to be included in the
+ // Date header. This value should hold a current time in the
+ // RFC1123 format.
+ TestHttpResponse response(HttpVersion(1, 0), HttpStatusCode::OK);
+ std::string generated_date = response.generateDateHeaderValue();
+
+ // Use our date/time utilities to parse this value into the ptime.
+ HttpDateTime parsed_time = HttpDateTime::fromRfc1123(generated_date);
+
+ // Now that we have it converted back, we can check how far this
+ // value is from the current time. To be on the safe side, we check
+ // that it is not later than 10 seconds apart, rather than checking
+ // it for equality. In fact, checking it for equality would almost
+ // certainly cause an error. Especially on a virtual machine.
+ time_duration parsed_to_current =
+ microsec_clock::universal_time() - parsed_time.getPtime();
+ EXPECT_LT(parsed_to_current.seconds(), 10);
+}
+
+}
diff --git a/src/lib/http/tests/run_unittests.cc b/src/lib/http/tests/run_unittests.cc
new file mode 100644
index 0000000..17255dc
--- /dev/null
+++ b/src/lib/http/tests/run_unittests.cc
@@ -0,0 +1,21 @@
+// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <log/logger_support.h>
+#include <http/http_log.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/http/tests/server_client_unittests.cc b/src/lib/http/tests/server_client_unittests.cc
new file mode 100644
index 0000000..4ca280d
--- /dev/null
+++ b/src/lib/http/tests/server_client_unittests.cc
@@ -0,0 +1,2041 @@
+// Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/tls_acceptor.h>
+#include <cc/data.h>
+#include <test_http_client.h>
+#include <http/client.h>
+#include <http/http_types.h>
+#include <http/listener.h>
+#include <http/listener_impl.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_creator_factory.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <http/url.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <list>
+#include <sstream>
+#include <string>
+
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief IP address to which HTTP service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief IPv6 address to whch HTTP service is bound.
+const std::string IPV6_SERVER_ADDRESS = "::1";
+
+/// @brief Port number to which HTTP service is bound.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Request Timeout used in most of the tests (ms).
+const long REQUEST_TIMEOUT = 10000;
+
+/// @brief Persistent connection idle timeout used in most of the tests (ms).
+const long IDLE_TIMEOUT = 10000;
+
+/// @brief Persistent connection idle timeout used in tests where idle connections
+/// are tested (ms).
+const long SHORT_IDLE_TIMEOUT = 200;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Generic test HTTP response.
+typedef TestHttpResponseBase<HttpResponse> GenericResponse;
+
+/// @brief Pointer to generic test HTTP response.
+typedef boost::shared_ptr<GenericResponse> GenericResponsePtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+ }
+
+private:
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed ok).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // This will generate the response holding JSON content.
+ ResponsePtr response(new Response(http_version, status_code));
+ response->finalize();
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// This method generates 3 types of responses:
+ /// - response with a requested content type,
+ /// - partial response with incomplete JSON body,
+ /// - response with JSON body copied from the request.
+ ///
+ /// The first one is useful to test situations when received response can't
+ /// be parsed because of the content type mismatch. The second one is useful
+ /// to test request timeouts. The third type is used by most of the unit tests
+ /// to test successful transactions.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP OK response with no content.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) {
+ // Request must always be JSON.
+ PostHttpRequestJsonPtr request_json =
+ boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
+ ConstElementPtr body;
+ if (request_json) {
+ body = request_json->getBodyAsJson();
+ if (body) {
+ // Check if the client requested one of the two first response
+ // types.
+ GenericResponsePtr response;
+ ConstElementPtr content_type = body->get("requested-content-type");
+ ConstElementPtr partial_response = body->get("partial-response");
+ if (content_type || partial_response) {
+ // The first two response types can only be generated using the
+ // generic response as we have to explicitly modify some of the
+ // values.
+ response.reset(new GenericResponse(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ HttpResponseContextPtr ctx = response->context();
+
+ if (content_type) {
+ // Provide requested content type.
+ ctx->headers_.push_back(HttpHeaderContext("Content-Type",
+ content_type->stringValue()));
+ // It doesn't matter what body is there.
+ ctx->body_ = "abcd";
+ response->finalize();
+
+ } else {
+ // Generate JSON response.
+ ctx->headers_.push_back(HttpHeaderContext("Content-Type",
+ "application/json"));
+ // The body lacks '}' so the client will be waiting for it and
+ // eventually should time out.
+ ctx->body_ = "{";
+ response->finalize();
+ // The auto generated Content-Length header would be based on the
+ // body size (so set to 1 byte). We have to override it to
+ // account for the missing '}' character.
+ response->setContentLength(2);
+ }
+ return (response);
+ }
+ }
+ }
+
+ // Third type of response is requested.
+ ResponsePtr response(new Response(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ // If body was included in the request. Let's copy it.
+ if (body) {
+ response->setBodyAsJson(body);
+ }
+
+ response->finalize();
+ return (response);
+ }
+};
+
+/// @brief Implementation of the test @ref HttpResponseCreatorFactory.
+///
+/// This factory class creates @ref TestHttpResponseCreator instances.
+class TestHttpResponseCreatorFactory : public HttpResponseCreatorFactory {
+public:
+
+ /// @brief Creates @ref TestHttpResponseCreator instance.
+ virtual HttpResponseCreatorPtr create() const {
+ HttpResponseCreatorPtr response_creator(new TestHttpResponseCreator());
+ return (response_creator);
+ }
+};
+
+/// @brief Implementation of the HTTP listener used in tests.
+///
+/// This implementation replaces the @c HttpConnection type with a custom
+/// implementation.
+///
+/// @tparam HttpConnectionType Type of the connection object to be used by
+/// the listener implementation.
+template<typename HttpConnectionType>
+class HttpListenerImplCustom : public HttpListenerImpl {
+public:
+
+ HttpListenerImplCustom(IOService& io_service,
+ const IOAddress& server_address,
+ const unsigned short server_port,
+ const TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const long request_timeout,
+ const long idle_timeout)
+ : HttpListenerImpl(io_service, server_address, server_port,
+ tls_context, creator_factory, request_timeout,
+ idle_timeout) {
+ }
+
+protected:
+
+ /// @brief Creates an instance of the @c HttpConnection.
+ ///
+ /// This method is virtual so as it can be overridden when customized
+ /// connections are to be used, e.g. in case of unit testing.
+ ///
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ ///
+ /// @return Pointer to the created connection.
+ virtual HttpConnectionPtr createConnection(const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback) {
+ HttpConnectionPtr
+ conn(new HttpConnectionType(io_service_, acceptor_,
+ tls_context_, connections_,
+ response_creator, callback,
+ request_timeout_, idle_timeout_));
+ return (conn);
+ }
+};
+
+/// @brief Derivation of the @c HttpListener used in tests.
+///
+/// This class replaces the default implementation instance with the
+/// @c HttpListenerImplCustom using the customized connection type.
+///
+/// @tparam HttpConnectionType Type of the connection object to be used by
+/// the listener implementation.
+template<typename HttpConnectionType>
+class HttpListenerCustom : public HttpListener {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the listener.
+ /// @param server_address Address on which the HTTP service should run.
+ /// @param server_port Port number on which the HTTP service should run.
+ /// @param tls_context TLS context.
+ /// @param creator_factory Pointer to the caller-defined
+ /// @ref HttpResponseCreatorFactory derivation which should be used to
+ /// create @ref HttpResponseCreator instances.
+ /// @param request_timeout Timeout after which the HTTP Request Timeout
+ /// is generated.
+ /// @param idle_timeout Timeout after which an idle persistent HTTP
+ /// connection is closed by the server.
+ ///
+ /// @throw HttpListenerError when any of the specified parameters is
+ /// invalid.
+ HttpListenerCustom(IOService& io_service,
+ const IOAddress& server_address,
+ const unsigned short server_port,
+ const TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const HttpListener::RequestTimeout& request_timeout,
+ const HttpListener::IdleTimeout& idle_timeout)
+ : HttpListener(io_service, server_address, server_port,
+ tls_context, creator_factory,
+ request_timeout, idle_timeout) {
+ // Replace the default implementation with the customized version
+ // using the custom derivation of the HttpConnection.
+ impl_.reset(new HttpListenerImplCustom<HttpConnectionType>
+ (io_service, server_address, server_port,
+ tls_context, creator_factory, request_timeout.value_,
+ idle_timeout.value_));
+ }
+};
+
+/// @brief Implementation of the @c HttpConnection which injects greater
+/// length value than the buffer size into the write socket callback.
+class HttpConnectionLongWriteBuffer : public HttpConnection {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the connection.
+ /// @param acceptor Pointer to the TCP acceptor object used to listen for
+ /// new HTTP connections.
+ /// @param tls_context TLS context.
+ /// @param connection_pool Connection pool in which this connection is
+ /// stored.
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ /// @param request_timeout Configured timeout for a HTTP request.
+ /// @param idle_timeout Timeout after which persistent HTTP connection is
+ /// closed by the server.
+ HttpConnectionLongWriteBuffer(IOService& io_service,
+ const HttpAcceptorPtr& acceptor,
+ const TlsContextPtr& tls_context,
+ HttpConnectionPool& connection_pool,
+ const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback,
+ const long request_timeout,
+ const long idle_timeout)
+ : HttpConnection(io_service, acceptor, tls_context, connection_pool,
+ response_creator, callback, request_timeout,
+ idle_timeout) {
+ }
+
+ /// @brief Callback invoked when data is sent over the socket.
+ ///
+ /// @param transaction Pointer to the transaction for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the data sent.
+ virtual void socketWriteCallback(HttpConnection::TransactionPtr transaction,
+ boost::system::error_code ec,
+ size_t length) {
+ // Pass greater length of the data written. The callback should deal
+ // with this and adjust the data length.
+ HttpConnection::socketWriteCallback(transaction, ec, length + 1);
+ }
+};
+
+/// @brief Implementation of the @c HttpConnection which replaces
+/// transaction instance prior to calling write socket callback.
+class HttpConnectionTransactionChange : public HttpConnection {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the connection.
+ /// @param acceptor Pointer to the TCP acceptor object used to listen for
+ /// new HTTP connections.
+ /// @param context TLS tls_context.
+ /// @param connection_pool Connection pool in which this connection is
+ /// stored.
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ /// @param request_timeout Configured timeout for a HTTP request.
+ /// @param idle_timeout Timeout after which persistent HTTP connection is
+ /// closed by the server.
+ HttpConnectionTransactionChange(IOService& io_service,
+ const HttpAcceptorPtr& acceptor,
+ const TlsContextPtr& tls_context,
+ HttpConnectionPool& connection_pool,
+ const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback,
+ const long request_timeout,
+ const long idle_timeout)
+ : HttpConnection(io_service, acceptor, tls_context, connection_pool,
+ response_creator, callback, request_timeout,
+ idle_timeout) {
+ }
+
+ /// @brief Callback invoked when data is sent over the socket.
+ ///
+ /// @param transaction Pointer to the transaction for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the data sent.
+ virtual void socketWriteCallback(HttpConnection::TransactionPtr transaction,
+ boost::system::error_code ec,
+ size_t length) {
+ // Replace the transaction. The socket callback should deal with this
+ // gracefully. It should detect that the output buffer is empty. Then
+ // try to see if the connection is persistent. This check should fail,
+ // because the request hasn't been created/finalized. The exception
+ // thrown upon checking the persistence should be caught and the
+ // connection closed.
+ transaction = HttpConnection::Transaction::create(response_creator_);
+ HttpConnection::socketWriteCallback(transaction, ec, length);
+ }
+};
+
+/// @brief Pointer to the TestHttpClient.
+typedef boost::shared_ptr<TestHttpClient> TestHttpClientPtr;
+
+/// @brief Test fixture class for @ref HttpListener.
+class HttpListenerTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Starts test timer which detects timeouts.
+ HttpListenerTest()
+ : io_service_(), factory_(new TestHttpResponseCreatorFactory()),
+ test_timer_(io_service_), run_io_service_timer_(io_service_), clients_() {
+ test_timer_.setup(std::bind(&HttpListenerTest::timeoutHandler, this, true),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes active HTTP clients.
+ virtual ~HttpListenerTest() {
+ for (auto client = clients_.begin(); client != clients_.end();
+ ++client) {
+ (*client)->close();
+ }
+ }
+
+ /// @brief Connect to the endpoint.
+ ///
+ /// This method creates TestHttpClient instance and retains it in the clients_
+ /// list.
+ ///
+ /// @param request String containing the HTTP request to be sent.
+ void startRequest(const std::string& request) {
+ TestHttpClientPtr client(new TestHttpClient(io_service_));
+ clients_.push_back(client);
+ clients_.back()->startRequest(request);
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void timeoutHandler(const bool fail_on_timeout) {
+ if (fail_on_timeout) {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ }
+ io_service_.stop();
+ }
+
+ /// @brief Runs IO service with optional timeout.
+ ///
+ /// @param timeout Optional value specifying for how long the io service
+ /// should be ran.
+ void runIOService(long timeout = 0) {
+ io_service_.get_io_service().reset();
+
+ if (timeout > 0) {
+ run_io_service_timer_.setup(std::bind(&HttpListenerTest::timeoutHandler,
+ this, false),
+ timeout, IntervalTimer::ONE_SHOT);
+ }
+ io_service_.run();
+ io_service_.get_io_service().reset();
+ io_service_.poll();
+ }
+
+ /// @brief Returns HTTP OK response expected by unit tests.
+ ///
+ /// @param http_version HTTP version.
+ ///
+ /// @return HTTP OK response expected by unit tests.
+ std::string httpOk(const HttpVersion& http_version) {
+ std::ostringstream s;
+ s << "HTTP/" << http_version.major_ << "." << http_version.minor_ << " 200 OK\r\n"
+ "Content-Length: 33\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"remote-address\": \"127.0.0.1\" }";
+ return (s.str());
+ }
+
+ /// @brief Tests that HTTP request timeout status is returned when the
+ /// server does not receive the entire request.
+ ///
+ /// @param request Partial request for which the parser will be waiting for
+ /// the next chunks of data.
+ /// @param expected_version HTTP version expected in the response.
+ void testRequestTimeout(const std::string& request,
+ const HttpVersion& expected_version) {
+ // Open the listener with the Request Timeout of 1 sec and post the
+ // partial request.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, TlsContextPtr(),
+ factory_, HttpListener::RequestTimeout(1000),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+
+ // Build the reference response.
+ std::ostringstream expected_response;
+ expected_response
+ << "HTTP/" << expected_version.major_ << "." << expected_version.minor_
+ << " 408 Request Timeout\r\n"
+ "Content-Length: 44\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"result\": 408, \"text\": \"Request Timeout\" }";
+
+ // The server should wait for the missing part of the request for 1 second.
+ // The missing part never arrives so the server should respond with the
+ // HTTP Request Timeout status.
+ EXPECT_EQ(expected_response.str(), client->getResponse());
+ }
+
+ /// @brief Tests various cases when unexpected data is passed to the
+ /// socket write handler.
+ ///
+ /// This test uses the custom listener and the test specific derivations of
+ /// the @c HttpConnection class to enforce injection of the unexpected
+ /// data to the socket write callback. The two example applications of
+ /// this test are:
+ /// - injecting greater length value than the output buffer size,
+ /// - replacing the transaction with another transaction.
+ ///
+ /// It is expected that the socket write callback deals gracefully with
+ /// those situations.
+ ///
+ /// @tparam HttpConnectionType Test specific derivation of the
+ /// @c HttpConnection class.
+ template<typename HttpConnectionType>
+ void testWriteBufferIssues() {
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ // Use custom listener and the specialized connection object.
+ HttpListenerCustom<HttpConnectionType>
+ listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request.
+ ASSERT_NO_THROW(startRequest(request));
+
+ // Injecting unexpected data should not result in an exception.
+ ASSERT_NO_THROW(runIOService());
+
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+ }
+
+ /// @brief IO service used in the tests.
+ IOService io_service_;
+
+ /// @brief Pointer to the response creator factory.
+ HttpResponseCreatorFactoryPtr factory_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief Asynchronous timer for running IO service for a specified amount
+ /// of time.
+ IntervalTimer run_io_service_timer_;
+
+ /// @brief List of client connections.
+ std::list<TestHttpClientPtr> clients_;
+};
+
+// This test verifies that HTTP connection can be established and used to
+// transmit HTTP request and receive a response.
+TEST_F(HttpListenerTest, listen) {
+ const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+
+// This test verifies that persistent HTTP connection can be established when
+// "Connection: Keep-Alive" header value is specified.
+TEST_F(HttpListenerTest, keepAlive) {
+
+ // The first request contains the keep-alive header which instructs the server
+ // to maintain the TCP connection after sending a response.
+ std::string request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: Keep-Alive\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request with the keep-alive header.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ // We have sent keep-alive header so we expect that the connection with
+ // the server remains active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Test that we can send another request via the same connection. This time
+ // it lacks the keep-alive header, so the server should close the connection
+ // after sending the response.
+ request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ // Send request reusing the existing connection.
+ ASSERT_NO_THROW(client->sendRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ // Connection should have been closed by the server.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that persistent HTTP connection is established by default
+// when HTTP/1.1 is in use.
+TEST_F(HttpListenerTest, persistentConnection) {
+
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the first request.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ // HTTP/1.1 connection is persistent by default.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Test that we can send another request via the same connection. This time
+ // it includes the "Connection: close" header which instructs the server to
+ // close the connection after responding.
+ request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: close\r\n\r\n"
+ "{ }";
+
+ // Send request reusing the existing connection.
+ ASSERT_NO_THROW(client->sendRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ // Connection should have been closed by the server.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that "keep-alive" connection is closed by the server after
+// an idle time.
+TEST_F(HttpListenerTest, keepAliveTimeout) {
+
+ // The first request contains the keep-alive header which instructs the server
+ // to maintain the TCP connection after sending a response.
+ std::string request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: Keep-Alive\r\n\r\n"
+ "{ }";
+
+ // Specify the idle timeout of 500ms.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(500));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request with the keep-alive header.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ // We have sent keep-alive header so we expect that the connection with
+ // the server remains active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Run IO service for 1000ms. The idle time is set to 500ms, so the connection
+ // should be closed by the server while we wait here.
+ runIOService(1000);
+
+ // Make sure the connection has been closed.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ // Check if we can re-establish the connection and send another request.
+ clients_.clear();
+ request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that persistent connection is closed by the server after
+// an idle time.
+TEST_F(HttpListenerTest, persistentConnectionTimeout) {
+
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ // Specify the idle timeout of 500ms.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(500));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ // The connection should remain active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Run IO service for 1000ms. The idle time is set to 500ms, so the connection
+ // should be closed by the server while we wait here.
+ runIOService(1000);
+
+ // Make sure the connection has been closed.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ // Check if we can re-establish the connection and send another request.
+ clients_.clear();
+ request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: close\r\n\r\n"
+ "{ }";
+
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that HTTP/1.1 connection remains open even if there is an
+// error in the message body.
+TEST_F(HttpListenerTest, persistentConnectionBadBody) {
+
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 12\r\n\r\n"
+ "{ \"a\": abc }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ("HTTP/1.1 400 Bad Request\r\n"
+ "Content-Length: 40\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"result\": 400, \"text\": \"Bad Request\" }",
+ client->getResponse());
+
+ // The connection should remain active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Make sure that we can send another request. This time we specify the
+ // "close" connection-token to force the connection to close.
+ request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: close\r\n\r\n"
+ "{ }";
+
+ // Send request reusing the existing connection.
+ ASSERT_NO_THROW(client->sendRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that the HTTP listener can't be started twice.
+TEST_F(HttpListenerTest, startTwice) {
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ EXPECT_THROW(listener.start(), HttpListenerError);
+}
+
+// This test verifies that Bad Request status is returned when the request
+// is malformed.
+TEST_F(HttpListenerTest, badRequest) {
+ // Content-Type is wrong. This should result in Bad Request status.
+ const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: foo\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ("HTTP/1.1 400 Bad Request\r\n"
+ "Content-Length: 40\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"result\": 400, \"text\": \"Bad Request\" }",
+ client->getResponse());
+}
+
+// This test verifies that NULL pointer can't be specified for the
+// HttpResponseCreatorFactory.
+TEST_F(HttpListenerTest, invalidFactory) {
+ EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, TlsContextPtr(),
+ HttpResponseCreatorFactoryPtr(),
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)),
+ HttpListenerError);
+}
+
+// This test verifies that the timeout of 0 can't be specified for the
+// Request Timeout.
+TEST_F(HttpListenerTest, invalidRequestTimeout) {
+ EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(0),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)),
+ HttpListenerError);
+}
+
+// This test verifies that the timeout of 0 can't be specified for the
+// idle persistent connection timeout.
+TEST_F(HttpListenerTest, invalidIdleTimeout) {
+ EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(0)),
+ HttpListenerError);
+}
+
+// This test verifies that listener can't be bound to the port to which
+// other server is bound.
+TEST_F(HttpListenerTest, addressInUse) {
+ tcp::acceptor acceptor(io_service_.get_io_service());
+ // Use other port than SERVER_PORT to make sure that this TCP connection
+ // doesn't affect subsequent tests.
+ tcp::endpoint endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT + 1);
+ acceptor.open(endpoint.protocol());
+ acceptor.bind(endpoint);
+
+ // Listener should report an error when we try to start it because another
+ // acceptor is bound to that port and address.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT + 1, TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ EXPECT_THROW(listener.start(), HttpListenerError);
+}
+
+// This test verifies that HTTP Request Timeout status is returned as
+// expected when the read part of the request contains the HTTP
+// version number. The timeout response should contain the same
+// HTTP version number as the partial request.
+TEST_F(HttpListenerTest, requestTimeoutHttpVersionFound) {
+ // The part of the request specified here is correct but it is not
+ // a complete request.
+ const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length:";
+
+ testRequestTimeout(request, HttpVersion::HTTP_11());
+}
+
+// This test verifies that HTTP Request Timeout status is returned as
+// expected when the read part of the request does not contain
+// the HTTP version number. The timeout response should by default
+// contain HTTP/1.0 version number.
+TEST_F(HttpListenerTest, requestTimeoutHttpVersionNotFound) {
+ // The part of the request specified here is correct but it is not
+ // a complete request.
+ const std::string request = "POST /foo/bar HTTP";
+
+ testRequestTimeout(request, HttpVersion::HTTP_10());
+}
+
+// This test verifies that injecting length value greater than the
+// output buffer length to the socket write callback does not cause
+// an exception.
+TEST_F(HttpListenerTest, tooLongWriteBuffer) {
+ testWriteBufferIssues<HttpConnectionLongWriteBuffer>();
+}
+
+// This test verifies that changing the transaction before calling
+// the socket write callback does not cause an exception.
+TEST_F(HttpListenerTest, transactionChangeDuringWrite) {
+ testWriteBufferIssues<HttpConnectionTransactionChange>();
+}
+
+/// @brief Test fixture class for testing HTTP client.
+class HttpClientTest : public HttpListenerTest {
+public:
+
+ /// @brief Constructor.
+ HttpClientTest()
+ : HttpListenerTest(),
+ listener_(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)),
+ listener2_(io_service_, IOAddress(IPV6_SERVER_ADDRESS), SERVER_PORT + 1,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)),
+ listener3_(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT + 2,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(SHORT_IDLE_TIMEOUT)) {
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destructor.
+ ~HttpClientTest() {
+ listener_.stop();
+ listener2_.stop();
+ listener3_.stop();
+ io_service_.poll();
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Creates HTTP request with JSON body.
+ ///
+ /// It includes a JSON parameter with a specified value.
+ ///
+ /// @param parameter_name JSON parameter to be included.
+ /// @param value JSON parameter value.
+ /// @param version HTTP version to be used. Default is HTTP/1.1.
+ template<typename ValueType>
+ PostHttpRequestJsonPtr createRequest(const std::string& parameter_name,
+ const ValueType& value,
+ const HttpVersion& version = HttpVersion(1, 1)) {
+ // Create POST request with JSON body.
+ PostHttpRequestJsonPtr request(new PostHttpRequestJson(HttpRequest::Method::HTTP_POST,
+ "/", version));
+ // Body is a map with a specified parameter included.
+ ElementPtr body = Element::createMap();
+ body->set(parameter_name, Element::create(value));
+ request->setBodyAsJson(body);
+ try {
+ request->finalize();
+
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "failed to create request: " << ex.what();
+ }
+
+ return (request);
+ }
+
+ /// @brief Test that two consecutive requests can be sent over the same
+ /// connection (if persistent, if not persistent two connections will
+ /// be used).
+ ///
+ /// @param version HTTP version to be used.
+ void testConsecutiveRequests(const HttpVersion& version) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create a client and specify the URL on which the server can be reached.
+ HttpClient client(io_service_);
+ Url url("http://127.0.0.1:18123");
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1, version);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ EXPECT_FALSE(ec);
+ }));
+
+ // Initiate another request to the destination.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2, version);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ EXPECT_FALSE(ec);
+ }));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure that the received responses are different. We check that by
+ // comparing value of the sequence parameters.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief Test that the client can communicate with two different
+ /// destinations simultaneously.
+ void testMultipleDestinations() {
+ // Start two servers running on different ports.
+ ASSERT_NO_THROW(listener_.start());
+ ASSERT_NO_THROW(listener2_.start());
+
+ // Create the client. It will be communicating with the two servers.
+ HttpClient client(io_service_);
+
+ // Specify the URLs on which the servers are available.
+ Url url1("http://127.0.0.1:18123");
+ Url url2("http://[::1]:18124");
+
+ // Create a request to the first server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url1, TlsContextPtr(),
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ EXPECT_FALSE(ec);
+ }));
+
+ // Create a request to the second server.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url2, TlsContextPtr(),
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ EXPECT_FALSE(ec);
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure we have received two different responses.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief Test that the client can communicate with the same destination
+ /// address and port but with different TLS contexts so
+ void testMultipleTlsContexts() {
+ // Start only one server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create the client.
+ HttpClient client(io_service_);
+
+ // Specify the URL on which the server is available.
+ Url url("http://127.0.0.1:18123");
+
+ // Create a request to the first server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Create a request with the second TLS context.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure we have received two different responses.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief Test that idle connection can be resumed for second request.
+ void testIdleConnection() {
+ // Start the server that has short idle timeout. It closes the idle
+ // connection after 200ms.
+ ASSERT_NO_THROW(listener3_.start());
+
+ // Create the client that will communicate with this server.
+ HttpClient client(io_service_);
+
+ // Specify the URL of this server.
+ Url url("http://127.0.0.1:18125");
+
+ // Create the first request.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request1, response1,
+ [this](const boost::system::error_code& ec, const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+ EXPECT_FALSE(ec);
+ }));
+
+ // Run the IO service until the response is received.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure the response has been received.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ // Delay the generation of the second request by 2x server idle timeout.
+ // This should be enough to cause the server to close the connection.
+ ASSERT_NO_THROW(runIOService(SHORT_IDLE_TIMEOUT * 2));
+
+ // Create another request.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request2, response2,
+ [this](const boost::system::error_code& ec, const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+ EXPECT_FALSE(ec);
+ }));
+
+ // Actually trigger the second request.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sire that the server has responded.
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ // Make sure that two different responses have been received.
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief This test verifies that the client returns IO error code when the
+ /// server is unreachable.
+ void testUnreachable () {
+ // Create the client.
+ HttpClient client(io_service_);
+
+ // Specify the URL of the server. This server is down.
+ Url url("http://127.0.0.1:18123");
+
+ // Create the request.
+ PostHttpRequestJsonPtr request = createRequest("sequence", 1);
+ HttpResponseJsonPtr response(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request, response,
+ [this](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+ // The server should have returned an IO error.
+ EXPECT_TRUE(ec);
+ }));
+
+ // Actually trigger the request.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Test that an error is returned by the client if the server
+ /// response is malformed.
+ void testMalformedResponse () {
+ // Start the server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create the client.
+ HttpClient client(io_service_);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ // The response is going to be malformed in such a way that it holds
+ // an invalid content type. We affect the content type by creating
+ // a request that holds a JSON parameter requesting a specific
+ // content type.
+ PostHttpRequestJsonPtr request = createRequest("requested-content-type",
+ "text/html");
+ HttpResponseJsonPtr response(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request, response,
+ [this](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string& parsing_error) {
+ io_service_.stop();
+ // There should be no IO error (answer from the server is received).
+ EXPECT_FALSE(ec);
+ // The response object is NULL because it couldn't be finalized.
+ EXPECT_FALSE(response);
+ // The message parsing error should be returned.
+ EXPECT_FALSE(parsing_error.empty());
+ }));
+
+ // Actually trigger the request.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Test that client times out when it doesn't receive the entire
+ /// response from the server within a desired time.
+ void testClientRequestTimeout() {
+ // Start the server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create the client.
+ HttpClient client(io_service_);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ unsigned cb_num = 0;
+
+ // Create the request which asks the server to generate a partial
+ // (although well formed) response. The client will be waiting for the
+ // rest of the response to be provided and will eventually time out.
+ PostHttpRequestJsonPtr request1 = createRequest("partial-response", true);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ // This value will be set to true if the connection close callback is
+ // invoked upon time out.
+ auto connection_closed = false;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request1, response1,
+ [this, &cb_num](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ // In this particular case we know exactly the type of the
+ // IO error returned, because the client explicitly sets this
+ // error code.
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ // There should be no response returned.
+ EXPECT_FALSE(response);
+ },
+ HttpClient::RequestTimeout(100),
+ HttpClient::ConnectHandler(),
+ HttpClient::HandshakeHandler(),
+ [&connection_closed](const int) {
+ // This callback is called when the connection gets closed
+ // by the client.
+ connection_closed = true;
+ })
+ );
+
+ // Create another request after the timeout. It should be handled ok.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request2, response2,
+ [this, &cb_num](const boost::system::error_code& /*ec*/,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+ // Make sure that the client has closed the connection upon timeout.
+ EXPECT_TRUE(connection_closed);
+ }
+
+ /// @brief Test that client times out when connection takes too long.
+ void testClientConnectTimeout() {
+ // Start the server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create the client.
+ HttpClient client(io_service_);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ unsigned cb_num = 0;
+
+ PostHttpRequestJsonPtr request = createRequest("sequence", 1);
+ HttpResponseJsonPtr response(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request, response,
+ [this, &cb_num](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ // In this particular case we know exactly the type of the
+ // IO error returned, because the client explicitly sets this
+ // error code.
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ // There should be no response returned.
+ EXPECT_FALSE(response);
+
+ }, HttpClient::RequestTimeout(100),
+
+ // This callback is invoked upon an attempt to connect to the
+ // server. The false value indicates to the HttpClient to not
+ // try to send a request to the server. This simulates the
+ // case of connect() taking very long and should eventually
+ // cause the transaction to time out.
+ [](const boost::system::error_code& /*ec*/, int) {
+ return (false);
+ }));
+
+ // Create another request after the timeout. It should be handled ok.
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request, response,
+ [this, &cb_num](const boost::system::error_code& /*ec*/,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Tests the behavior of the HTTP client when the premature
+ /// timeout occurs.
+ ///
+ /// The premature timeout may occur when the system clock is moved
+ /// during the transaction. This test simulates this behavior by
+ /// starting new transaction and waiting for the timeout to occur
+ /// before the IO service is ran. The timeout handler is invoked
+ /// first and it resets the transaction state. This test verifies
+ /// that the started transaction tears down gracefully after the
+ /// transaction state is reset.
+ ///
+ /// There are two variants of this test. The first variant schedules
+ /// one transaction before running the IO service. The second variant
+ /// schedules two transactions prior to running the IO service. The
+ /// second transaction is queued, so it is expected that it doesn't
+ /// time out and it runs successfully.
+ ///
+ /// @param queue_two_requests Boolean value indicating if a single
+ /// transaction should be queued (false), or two (true).
+ void testClientRequestLateStart(const bool queue_two_requests) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create the client.
+ HttpClient client(io_service_);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ // Specify the TLS context of the server.
+ TlsContextPtr tls_context;
+
+ // Generate first request.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+
+ // Use very short timeout to make sure that it occurs before we actually
+ // run the transaction.
+ ASSERT_NO_THROW(client.asyncSendRequest(url, tls_context,
+ request1, response1,
+ [](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+
+ // In this particular case we know exactly the type of the
+ // IO error returned, because the client explicitly sets this
+ // error code.
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ // There should be no response returned.
+ EXPECT_FALSE(response);
+ }, HttpClient::RequestTimeout(1)));
+
+ if (queue_two_requests) {
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, tls_context,
+ request2, response2,
+ [](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+
+ // This second request should be successful.
+ EXPECT_TRUE(ec.value() == 0);
+ EXPECT_TRUE(response);
+ }));
+ }
+
+ // This waits for 3ms to make sure that the timeout occurs before we
+ // run the transaction. This leads to an unusual situation that the
+ // transaction state is reset as a result of the timeout but the
+ // transaction is alive. We want to make sure that the client can
+ // gracefully deal with this situation.
+ usleep(3000);
+
+ // Run the transaction and hope it will gracefully tear down.
+ ASSERT_NO_THROW(runIOService(100));
+
+ // Now try to send another request to make sure that the client
+ // is healthy.
+ PostHttpRequestJsonPtr request3 = createRequest("sequence", 3);
+ HttpResponseJsonPtr response3(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request3, response3,
+ [this](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+
+ // Everything should be ok.
+ EXPECT_TRUE(ec.value() == 0);
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Tests that underlying TCP socket can be registered and
+ /// unregistered via connection and close callbacks.
+ ///
+ /// It conducts to consecutive requests over the same client.
+ ///
+ /// @param version HTTP version to be used.
+ void testConnectCloseCallbacks(const HttpVersion& version) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create a client and specify the URL on which the server can be reached.
+ HttpClient client(io_service_);
+ Url url("http://127.0.0.1:18123");
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1, version);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ExternalMonitor monitor;
+
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+
+ EXPECT_FALSE(ec);
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Initiate another request to the destination.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2, version);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ EXPECT_FALSE(ec);
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // We should have had 2 connect invocations, no closes
+ // and a valid registered fd
+ EXPECT_EQ(2, monitor.connect_cnt_);
+ EXPECT_EQ(0, monitor.close_cnt_);
+ EXPECT_GT(monitor.registered_fd_, -1);
+
+ // Make sure that the received responses are different. We check that by
+ // comparing value of the sequence parameters.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+
+ // Stopping the client the close the connection.
+ client.stop();
+
+ // We should have had 2 connect invocations, 1 closes
+ // and an invalid registered fd
+ EXPECT_EQ(2, monitor.connect_cnt_);
+ EXPECT_EQ(1, monitor.close_cnt_);
+ EXPECT_EQ(-1, monitor.registered_fd_);
+ }
+
+ /// @brief Tests detection and handling out-of-band socket events
+ ///
+ /// It initiates a transaction and verifies that a mid-transaction call
+ /// to HttpClient::closeIfOutOfBand() has no affect on the connection.
+ /// After successful completion of the transaction, a second call to
+ /// HttpClient::closeIfOutOfBand() is made. This should result in the
+ /// connection being closed.
+ /// This step is repeated to verify that after an OOB closure, transactions
+ /// to the same destination can be processed.
+ ///
+ /// Lastly, we verify that HttpClient::stop() closes the connection correctly.
+ ///
+ /// @param version HTTP version to be used.
+ void testCloseIfOutOfBand(const HttpVersion& version) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create a client and specify the URL on which the server can be reached.
+ HttpClient client(io_service_);
+ Url url("http://127.0.0.1:18123");
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1, version);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ExternalMonitor monitor;
+
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request1, response1,
+ [this, &client, &resp_num, &monitor](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num == 1) {
+ io_service_.stop();
+ }
+
+ EXPECT_EQ(1, monitor.connect_cnt_); // We should have 1 connect.
+ EXPECT_EQ(0, monitor.close_cnt_); // We should have 0 closes
+ ASSERT_GT(monitor.registered_fd_, -1); // We should have a valid fd.
+ int orig_fd = monitor.registered_fd_;
+
+ // Test our socket for OOBness.
+ client.closeIfOutOfBand(monitor.registered_fd_);
+
+ // Since we're in a transaction, we should have no closes and
+ // the same valid fd.
+ EXPECT_EQ(0, monitor.close_cnt_);
+ ASSERT_EQ(monitor.registered_fd_, orig_fd);
+
+ EXPECT_FALSE(ec);
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure that we received a response.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+ EXPECT_EQ(1, sequence1->intValue());
+
+ // We should have had 1 connect invocations, no closes
+ // and a valid registered fd
+ EXPECT_EQ(1, monitor.connect_cnt_);
+ EXPECT_EQ(0, monitor.close_cnt_);
+ EXPECT_GT(monitor.registered_fd_, -1);
+
+ // Test our socket for OOBness.
+ client.closeIfOutOfBand(monitor.registered_fd_);
+
+ // Since we're in a transaction, we should have no closes and
+ // the same valid fd.
+ EXPECT_EQ(1, monitor.close_cnt_);
+ EXPECT_EQ(-1, monitor.registered_fd_);
+
+ // Now let's do another request to the destination to verify that
+ // we'll reopen the connection without issue.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2, version);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request2, response2,
+ [this, &client, &resp_num, &monitor](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num == 1) {
+ io_service_.stop();
+ }
+
+ EXPECT_EQ(2, monitor.connect_cnt_); // We should have 1 connect.
+ EXPECT_EQ(1, monitor.close_cnt_); // We should have 0 closes
+ ASSERT_GT(monitor.registered_fd_, -1); // We should have a valid fd.
+ int orig_fd = monitor.registered_fd_;
+
+ // Test our socket for OOBness.
+ client.closeIfOutOfBand(monitor.registered_fd_);
+
+ // Since we're in a transaction, we should have no closes and
+ // the same valid fd.
+ EXPECT_EQ(1, monitor.close_cnt_);
+ ASSERT_EQ(monitor.registered_fd_, orig_fd);
+
+ EXPECT_FALSE(ec);
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure that we received the second response.
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+ EXPECT_EQ(2, sequence2->intValue());
+
+ // Stopping the client the close the connection.
+ client.stop();
+
+ // We should have had 2 connect invocations, 2 closes
+ // and an invalid registered fd
+ EXPECT_EQ(2, monitor.connect_cnt_);
+ EXPECT_EQ(2, monitor.close_cnt_);
+ EXPECT_EQ(-1, monitor.registered_fd_);
+ }
+
+ /// @brief Simulates external registery of Connection TCP sockets
+ ///
+ /// Provides methods compatible with Connection callbacks for connect
+ /// and close operations.
+ class ExternalMonitor {
+ public:
+ /// @brief Constructor
+ ExternalMonitor()
+ : registered_fd_(-1), connect_cnt_(0), close_cnt_(0) {
+ }
+
+ /// @brief Connect callback handler
+ /// @param ec Error status of the ASIO connect
+ /// @param tcp_native_fd socket descriptor to register
+ bool connectHandler(const boost::system::error_code& ec, int tcp_native_fd) {
+ ++connect_cnt_;
+ if ((!ec || (ec.value() == boost::asio::error::in_progress))
+ && (tcp_native_fd >= 0)) {
+ registered_fd_ = tcp_native_fd;
+ return (true);
+ } else if ((ec.value() == boost::asio::error::already_connected)
+ && (registered_fd_ != tcp_native_fd)) {
+ return (false);
+ }
+
+ // ec indicates an error, return true, so that error can be handled
+ // by Connection logic.
+ return (true);
+ }
+
+ /// @brief Handshake callback handler
+ /// @param ec Error status of the ASIO connect
+ bool handshakeHandler(const boost::system::error_code&, int) {
+ ADD_FAILURE() << "handshake callback handler is called";
+ // ec indicates an error, return true, so that error can be handled
+ // by Connection logic.
+ return (true);
+ }
+
+ /// @brief Close callback handler
+ ///
+ /// @param tcp_native_fd socket descriptor to register
+ void closeHandler(int tcp_native_fd) {
+ ++close_cnt_;
+ EXPECT_EQ(tcp_native_fd, registered_fd_) << "closeHandler fd mismatch";
+ if (tcp_native_fd >= 0) {
+ registered_fd_ = -1;
+ }
+ }
+
+ /// @brief Keeps track of socket currently "registered" for external monitoring.
+ int registered_fd_;
+
+ /// @brief Tracks how many times the connect callback is invoked.
+ int connect_cnt_;
+
+ /// @brief Tracks how many times the close callback is invoked.
+ int close_cnt_;
+ };
+
+ /// @brief Instance of the listener used in the tests.
+ HttpListener listener_;
+
+ /// @brief Instance of the second listener used in the tests.
+ HttpListener listener2_;
+
+ /// @brief Instance of the third listener used in the tests (with short idle
+ /// timeout).
+ HttpListener listener3_;
+
+};
+
+// Test that two consecutive requests can be sent over the same (persistent)
+// connection.
+TEST_F(HttpClientTest, consecutiveRequests) {
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 1)));
+}
+
+// Test that two consecutive requests can be sent over the same (persistent)
+// connection.
+TEST_F(HttpClientTest, consecutiveRequestsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 1)));
+}
+
+// Test that two consecutive requests can be sent over non-persistent connection.
+// This is achieved by sending HTTP/1.0 requests, which are non-persistent by
+// default. The client should close the connection right after receiving a response
+// from the server.
+TEST_F(HttpClientTest, closeBetweenRequests) {
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 0)));
+}
+
+// Test that two consecutive requests can be sent over non-persistent connection.
+// This is achieved by sending HTTP/1.0 requests, which are non-persistent by
+// default. The client should close the connection right after receiving a response
+// from the server.
+TEST_F(HttpClientTest, closeBetweenRequestsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 0)));
+}
+
+// Test that the client can communicate with two different destinations
+// simultaneously.
+TEST_F(HttpClientTest, multipleDestinations) {
+ ASSERT_NO_FATAL_FAILURE(testMultipleDestinations());
+}
+
+// Test that the client can communicate with two different destinations
+// simultaneously.
+TEST_F(HttpClientTest, multipleDestinationsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testMultipleDestinations());
+}
+
+// Test that the client can use two different TLS contexts to the same
+// destination address and port simultaneously.
+TEST_F(HttpClientTest, multipleTlsContexts) {
+ ASSERT_NO_FATAL_FAILURE(testMultipleTlsContexts());
+}
+
+// Test that the client can use two different TLS contexts to the same
+// destination address and port simultaneously.
+TEST_F(HttpClientTest, multipleTlsContextsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testMultipleTlsContexts());
+}
+
+// Test that idle connection can be resumed for second request.
+TEST_F(HttpClientTest, idleConnection) {
+ ASSERT_NO_FATAL_FAILURE(testIdleConnection());
+}
+
+// Test that idle connection can be resumed for second request.
+TEST_F(HttpClientTest, idleConnectionMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testIdleConnection());
+}
+
+// This test verifies that the client returns IO error code when the
+// server is unreachable.
+TEST_F(HttpClientTest, unreachable) {
+ ASSERT_NO_FATAL_FAILURE(testUnreachable());
+}
+
+// This test verifies that the client returns IO error code when the
+// server is unreachable.
+TEST_F(HttpClientTest, unreachableMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testUnreachable());
+}
+
+// Test that an error is returned by the client if the server response is
+// malformed.
+TEST_F(HttpClientTest, malformedResponse) {
+ ASSERT_NO_FATAL_FAILURE(testMalformedResponse());
+}
+
+// Test that an error is returned by the client if the server response is
+// malformed.
+TEST_F(HttpClientTest, malformedResponseMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testMalformedResponse());
+}
+
+// Test that client times out when it doesn't receive the entire response
+// from the server within a desired time.
+TEST_F(HttpClientTest, clientRequestTimeout) {
+ ASSERT_NO_FATAL_FAILURE(testClientRequestTimeout());
+}
+
+// Test that client times out when it doesn't receive the entire response
+// from the server within a desired time.
+TEST_F(HttpClientTest, clientRequestTimeoutMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testClientRequestTimeout());
+}
+
+// This test verifies the behavior of the HTTP client when the premature
+// (and unexpected) timeout occurs. The premature timeout may be caused
+// by the system clock move.
+TEST_F(HttpClientTest, clientRequestLateStartNoQueue) {
+ testClientRequestLateStart(false);
+}
+
+// This test verifies the behavior of the HTTP client when the premature
+// (and unexpected) timeout occurs. The premature timeout may be caused
+// by the system clock move.
+TEST_F(HttpClientTest, clientRequestLateStartNoQueueMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testClientRequestLateStart(false);
+}
+
+// This test verifies the behavior of the HTTP client when the premature
+// timeout occurs and there are requests queued after the request which
+// times out.
+TEST_F(HttpClientTest, clientRequestLateStartQueue) {
+ testClientRequestLateStart(true);
+}
+
+// This test verifies the behavior of the HTTP client when the premature
+// timeout occurs and there are requests queued after the request which
+// times out.
+TEST_F(HttpClientTest, clientRequestLateStartQueueMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testClientRequestLateStart(true);
+}
+
+// Test that client times out when connection takes too long.
+TEST_F(HttpClientTest, clientConnectTimeout) {
+ ASSERT_NO_FATAL_FAILURE(testClientConnectTimeout());
+}
+
+// Test that client times out when connection takes too long.
+TEST_F(HttpClientTest, clientConnectTimeoutMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testClientConnectTimeout());
+}
+
+/// Tests that connect and close callbacks work correctly.
+TEST_F(HttpClientTest, connectCloseCallbacks) {
+ ASSERT_NO_FATAL_FAILURE(testConnectCloseCallbacks(HttpVersion(1, 1)));
+}
+
+/// Tests that connect and close callbacks work correctly.
+TEST_F(HttpClientTest, connectCloseCallbacksMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testConnectCloseCallbacks(HttpVersion(1, 1)));
+}
+
+/// Tests that HttpClient::closeIfOutOfBand works correctly.
+TEST_F(HttpClientTest, closeIfOutOfBand) {
+ ASSERT_NO_FATAL_FAILURE(testCloseIfOutOfBand(HttpVersion(1, 1)));
+}
+
+/// Tests that HttpClient::closeIfOutOfBand works correctly.
+TEST_F(HttpClientTest, closeIfOutOfBandMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testCloseIfOutOfBand(HttpVersion(1, 1)));
+}
+
+}
diff --git a/src/lib/http/tests/test_http_client.h b/src/lib/http/tests/test_http_client.h
new file mode 100644
index 0000000..f95b111
--- /dev/null
+++ b/src/lib/http/tests/test_http_client.h
@@ -0,0 +1,273 @@
+// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TEST_HTTP_CLIENT_H
+#define TEST_HTTP_CLIENT_H
+
+#include <cc/data.h>
+#include <http/client.h>
+#include <http/http_types.h>
+
+#include <boost/asio/read.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <gtest/gtest.h>
+
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+
+/// @brief Entity which can connect to the HTTP server endpoint.
+class TestHttpClient : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new socket instance. It doesn't connect. Call
+ /// connect() to connect to the server.
+ ///
+ /// @param io_service IO service to be stopped on error or completion.
+ /// @param server_address string containing the IP address of the server.
+ /// @param port port number of the server.
+ explicit TestHttpClient(IOService& io_service,
+ const std::string& server_address = "127.0.0.1",
+ uint16_t port = 18123)
+ : io_service_(io_service.get_io_service()), socket_(io_service_),
+ buf_(), response_(), server_address_(server_address),
+ server_port_(port), receive_done_(false) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes the underlying socket if it is open.
+ ~TestHttpClient() {
+ close();
+ }
+
+ /// @brief Send HTTP request specified in textual format.
+ ///
+ /// @param request HTTP request in the textual format.
+ void startRequest(const std::string& request) {
+ tcp::endpoint endpoint(address::from_string(server_address_), server_port_);
+ socket_.async_connect(endpoint,
+ [this, request](const boost::system::error_code& ec) {
+ receive_done_ = false;
+ if (ec) {
+ // One would expect that async_connect wouldn't return
+ // EINPROGRESS error code, but simply wait for the connection
+ // to get established before the handler is invoked. It turns out,
+ // however, that on some OSes the connect handler may receive this
+ // error code which doesn't necessarily indicate a problem.
+ // Making an attempt to write and read from this socket will
+ // typically succeed. So, we ignore this error.
+ if (ec.value() != boost::asio::error::in_progress) {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ io_service_.stop();
+ return;
+ }
+ }
+ sendRequest(request);
+ });
+ }
+
+ /// @brief Send HTTP request.
+ ///
+ /// @param request HTTP request in the textual format.
+ void sendRequest(const std::string& request) {
+ sendPartialRequest(request);
+ }
+
+ /// @brief Send part of the HTTP request.
+ ///
+ /// @param request part of the HTTP request to be sent.
+ void sendPartialRequest(std::string request) {
+ socket_.async_send(boost::asio::buffer(request.data(), request.size()),
+ [this, request](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) mutable {
+ if (ec) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ } else if ((ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block)) {
+ // If we should try again make sure there is no garbage in the
+ // bytes_transferred.
+ bytes_transferred = 0;
+
+ } else {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ io_service_.stop();
+ return;
+ }
+ }
+
+ // Remove the part of the request which has been sent.
+ if (bytes_transferred > 0 && (request.size() <= bytes_transferred)) {
+ request.erase(0, bytes_transferred);
+ }
+
+ // Continue sending request data if there are still some data to be
+ // sent.
+ if (!request.empty()) {
+ sendPartialRequest(request);
+
+ } else {
+ // Request has been sent. Start receiving response.
+ response_.clear();
+ receivePartialResponse();
+ }
+ });
+ }
+
+ /// @brief Receive response from the server.
+ void receivePartialResponse() {
+ socket_.async_read_some(boost::asio::buffer(buf_.data(), buf_.size()),
+ [this](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) {
+ if (ec) {
+ // IO service stopped so simply return.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ } else if ((ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block)) {
+ // If we should try again, make sure that there is no garbage
+ // in the bytes_transferred.
+ bytes_transferred = 0;
+
+ } else {
+ // Error occurred, bail...
+ ADD_FAILURE() << "error occurred while receiving HTTP"
+ " response from the server: " << ec.message();
+ io_service_.stop();
+ }
+ }
+
+ if (bytes_transferred > 0) {
+ response_.insert(response_.end(), buf_.data(),
+ buf_.data() + bytes_transferred);
+ }
+
+ // Two consecutive new lines end the part of the response we're
+ // expecting.
+ if (response_.find("\r\n\r\n", 0) != std::string::npos) {
+ receive_done_ = true;
+ io_service_.stop();
+ } else {
+ receivePartialResponse();
+ }
+ });
+ }
+
+ /// @brief Checks if the TCP connection is still open.
+ ///
+ /// Tests the TCP connection by trying to read from the socket.
+ /// Unfortunately expected failure depends on a race between the read
+ /// and the other side close so to check if the connection is closed
+ /// please use @c isConnectionClosed instead.
+ ///
+ /// @return true if the TCP connection is open.
+ bool isConnectionAlive() {
+ // Remember the current non blocking setting.
+ const bool non_blocking_orig = socket_.non_blocking();
+ // Set the socket to non blocking mode. We're going to test if the socket
+ // returns would_block status on the attempt to read from it.
+ socket_.non_blocking(true);
+
+ // We need to provide a buffer for a call to read.
+ char data[2];
+ boost::system::error_code ec;
+ boost::asio::read(socket_, boost::asio::buffer(data, sizeof(data)), ec);
+
+ // Revert the original non_blocking flag on the socket.
+ socket_.non_blocking(non_blocking_orig);
+
+ // If the connection is alive we'd typically get would_block status code.
+ // If there are any data that haven't been read we may also get success
+ // status. We're guessing that try_again may also be returned by some
+ // implementations in some situations. Any other error code indicates a
+ // problem with the connection so we assume that the connection has been
+ // closed.
+ return (!ec || (ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block));
+ }
+
+ /// @brief Checks if the TCP connection is already closed.
+ ///
+ /// Tests the TCP connection by trying to read from the socket.
+ /// The read can block so this must be used to check if a connection
+ /// is alive so to check if the connection is alive please always
+ /// use @c isConnectionAlive.
+ ///
+ /// @return true if the TCP connection is closed.
+ bool isConnectionClosed() {
+ // Remember the current non blocking setting.
+ const bool non_blocking_orig = socket_.non_blocking();
+ // Set the socket to blocking mode. We're going to test if the socket
+ // returns eof status on the attempt to read from it.
+ socket_.non_blocking(false);
+
+ // We need to provide a buffer for a call to read.
+ char data[2];
+ boost::system::error_code ec;
+ boost::asio::read(socket_, boost::asio::buffer(data, sizeof(data)), ec);
+
+ // Revert the original non_blocking flag on the socket.
+ socket_.non_blocking(non_blocking_orig);
+
+ // If the connection is closed we'd typically get eof status code.
+ return (ec.value() == boost::asio::error::eof);
+ }
+
+ /// @brief Close connection.
+ void close() {
+ socket_.close();
+ }
+
+ /// @brief Returns the HTTP response string.
+ ///
+ /// @return string containing the response.
+ std::string getResponse() const {
+ return (response_);
+ }
+
+ /// @brief Returns true if the receive completed without error.
+ ///
+ /// @return True if the receive completed successfully, false
+ /// otherwise.
+ bool receiveDone() {
+ return (receive_done_);
+ }
+
+private:
+
+ /// @brief Holds reference to the IO service.
+ boost::asio::io_service& io_service_;
+
+ /// @brief A socket used for the connection.
+ boost::asio::ip::tcp::socket socket_;
+
+ /// @brief Buffer into which response is written.
+ std::array<char, 8192> buf_;
+
+ /// @brief Response in the textual format.
+ std::string response_;
+
+ /// @brief IP address of the server.
+ std::string server_address_;
+
+ /// @brief IP port of the server.
+ uint16_t server_port_;
+
+ /// @brief Set to true when the receive has completed successfully.
+ bool receive_done_;
+};
+
+/// @brief Pointer to the TestHttpClient.
+typedef boost::shared_ptr<TestHttpClient> TestHttpClientPtr;
+
+#endif
diff --git a/src/lib/http/tests/testdata/empty b/src/lib/http/tests/testdata/empty
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/lib/http/tests/testdata/empty
diff --git a/src/lib/http/tests/testdata/hiddenp b/src/lib/http/tests/testdata/hiddenp
new file mode 100644
index 0000000..e8b2395
--- /dev/null
+++ b/src/lib/http/tests/testdata/hiddenp
@@ -0,0 +1 @@
+KeaTest \ No newline at end of file
diff --git a/src/lib/http/tests/testdata/hiddens b/src/lib/http/tests/testdata/hiddens
new file mode 100644
index 0000000..f52fb83
--- /dev/null
+++ b/src/lib/http/tests/testdata/hiddens
@@ -0,0 +1 @@
+kea:test \ No newline at end of file
diff --git a/src/lib/http/tests/testdata/hiddenu b/src/lib/http/tests/testdata/hiddenu
new file mode 100644
index 0000000..801489a
--- /dev/null
+++ b/src/lib/http/tests/testdata/hiddenu
@@ -0,0 +1 @@
+keatest \ No newline at end of file
diff --git a/src/lib/http/tests/tls_client_unittests.cc b/src/lib/http/tests/tls_client_unittests.cc
new file mode 100644
index 0000000..4d82e95
--- /dev/null
+++ b/src/lib/http/tests/tls_client_unittests.cc
@@ -0,0 +1,1398 @@
+// Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/tls_acceptor.h>
+#include <asiolink/testutils/test_tls.h>
+#include <cc/data.h>
+#include <http/client.h>
+#include <http/http_types.h>
+#include <http/listener.h>
+#include <http/listener_impl.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_creator_factory.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <http/url.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <list>
+#include <sstream>
+#include <string>
+
+#ifdef WITH_BOTAN
+#define DISABLE_SOME_TESTS
+#endif
+#ifdef WITH_OPENSSL
+#if !defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x10100000L)
+#define DISABLE_SOME_TESTS
+#endif
+#endif
+
+using namespace boost::asio;
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+using namespace isc::asiolink::test;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+/// @todo: put the common part of client and server tests in its own file(s).
+
+namespace {
+
+/// @brief IP address to which HTTP service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief IPv6 address to whch HTTP service is bound.
+const std::string IPV6_SERVER_ADDRESS = "::1";
+
+/// @brief Port number to which HTTP service is bound.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Request Timeout used in most of the tests (ms).
+const long REQUEST_TIMEOUT = 10000;
+
+/// @brief Persistent connection idle timeout used in most of the tests (ms).
+const long IDLE_TIMEOUT = 10000;
+
+/// @brief Persistent connection idle timeout used in tests where idle connections
+/// are tested (ms).
+const long SHORT_IDLE_TIMEOUT = 200;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Generic test HTTP response.
+typedef TestHttpResponseBase<HttpResponse> GenericResponse;
+
+/// @brief Pointer to generic test HTTP response.
+typedef boost::shared_ptr<GenericResponse> GenericResponsePtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+ }
+
+private:
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed ok).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // This will generate the response holding JSON content.
+ ResponsePtr response(new Response(http_version, status_code));
+ response->finalize();
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// This method generates 3 types of responses:
+ /// - response with a requested content type,
+ /// - partial response with incomplete JSON body,
+ /// - response with JSON body copied from the request.
+ ///
+ /// The first one is useful to test situations when received response can't
+ /// be parsed because of the content type mismatch. The second one is useful
+ /// to test request timeouts. The third type is used by most of the unit tests
+ /// to test successful transactions.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP OK response with no content.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) {
+ // Check access parameters.
+ if (HttpRequest::recordSubject_) {
+ EXPECT_TRUE(request->getTls());
+ EXPECT_EQ("kea-client", request->getSubject());
+ }
+ if (HttpRequest::recordIssuer_) {
+ EXPECT_TRUE(request->getTls());
+ EXPECT_EQ("kea-ca", request->getIssuer());
+ }
+ // Request must always be JSON.
+ PostHttpRequestJsonPtr request_json =
+ boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
+ ConstElementPtr body;
+ if (request_json) {
+ body = request_json->getBodyAsJson();
+ if (body) {
+ // Check if the client requested one of the two first response
+ // types.
+ GenericResponsePtr response;
+ ConstElementPtr content_type = body->get("requested-content-type");
+ ConstElementPtr partial_response = body->get("partial-response");
+ if (content_type || partial_response) {
+ // The first two response types can only be generated using the
+ // generic response as we have to explicitly modify some of the
+ // values.
+ response.reset(new GenericResponse(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ HttpResponseContextPtr ctx = response->context();
+
+ if (content_type) {
+ // Provide requested content type.
+ ctx->headers_.push_back(HttpHeaderContext("Content-Type",
+ content_type->stringValue()));
+ // It doesn't matter what body is there.
+ ctx->body_ = "abcd";
+ response->finalize();
+
+ } else {
+ // Generate JSON response.
+ ctx->headers_.push_back(HttpHeaderContext("Content-Type",
+ "application/json"));
+ // The body lacks '}' so the client will be waiting for it and
+ // eventually should time out.
+ ctx->body_ = "{";
+ response->finalize();
+ // The auto generated Content-Length header would be based on the
+ // body size (so set to 1 byte). We have to override it to
+ // account for the missing '}' character.
+ response->setContentLength(2);
+ }
+ return (response);
+ }
+ }
+ }
+
+ // Third type of response is requested.
+ ResponsePtr response(new Response(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ // If body was included in the request. Let's copy it.
+ if (body) {
+ response->setBodyAsJson(body);
+ }
+
+ response->finalize();
+ return (response);
+ }
+};
+
+/// @brief Implementation of the test @ref HttpResponseCreatorFactory.
+///
+/// This factory class creates @ref TestHttpResponseCreator instances.
+class TestHttpResponseCreatorFactory : public HttpResponseCreatorFactory {
+public:
+
+ /// @brief Creates @ref TestHttpResponseCreator instance.
+ virtual HttpResponseCreatorPtr create() const {
+ HttpResponseCreatorPtr response_creator(new TestHttpResponseCreator());
+ return (response_creator);
+ }
+};
+
+/// @brief Test fixture class for @ref HttpListener.
+class HttpListenerTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Starts test timer which detects timeouts.
+ HttpListenerTest()
+ : io_service_(), factory_(new TestHttpResponseCreatorFactory()),
+ test_timer_(io_service_), run_io_service_timer_(io_service_) {
+ test_timer_.setup(std::bind(&HttpListenerTest::timeoutHandler, this, true),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void timeoutHandler(const bool fail_on_timeout) {
+ if (fail_on_timeout) {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ }
+ io_service_.stop();
+ }
+
+ /// @brief Runs IO service with optional timeout.
+ ///
+ /// @param timeout Optional value specifying for how long the io service
+ /// should be ran (ms).
+ void runIOService(long timeout = 0) {
+ io_service_.get_io_service().reset();
+
+ if (timeout > 0) {
+ run_io_service_timer_.setup(std::bind(&HttpListenerTest::timeoutHandler,
+ this, false),
+ timeout, IntervalTimer::ONE_SHOT);
+ }
+ io_service_.run();
+ io_service_.get_io_service().reset();
+ io_service_.poll();
+ }
+
+ /// @brief IO service used in the tests.
+ IOService io_service_;
+
+ /// @brief Pointer to the response creator factory.
+ HttpResponseCreatorFactoryPtr factory_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief Asynchronous timer for running IO service for a specified amount
+ /// of time.
+ IntervalTimer run_io_service_timer_;
+};
+
+/// @brief Test fixture class for testing HTTP client.
+class HttpsClientTest : public HttpListenerTest {
+public:
+
+ /// @brief Constructor.
+ HttpsClientTest()
+ : HttpListenerTest(), listener_(), listener2_(), listener3_(),
+ server_context_(), client_context_() {
+ configServer(server_context_);
+ configClient(client_context_);
+ listener_.reset(new HttpListener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT,
+ server_context_,
+ factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)));
+ listener2_.reset(new HttpListener(io_service_,
+ IOAddress(IPV6_SERVER_ADDRESS),
+ SERVER_PORT + 1,
+ server_context_,
+ factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)));
+ listener3_.reset(new HttpListener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT + 2,
+ server_context_,
+ factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(SHORT_IDLE_TIMEOUT)));
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destructor.
+ ~HttpsClientTest() {
+ listener_->stop();
+ listener2_->stop();
+ listener3_->stop();
+ io_service_.poll();
+ MultiThreadingMgr::instance().setMode(false);
+ HttpRequest::recordSubject_ = false;
+ HttpRequest::recordIssuer_ = false;
+ }
+
+ /// @brief Creates HTTP request with JSON body.
+ ///
+ /// It includes a JSON parameter with a specified value.
+ ///
+ /// @param parameter_name JSON parameter to be included.
+ /// @param value JSON parameter value.
+ /// @param version HTTP version to be used. Default is HTTP/1.1.
+ template<typename ValueType>
+ PostHttpRequestJsonPtr createRequest(const std::string& parameter_name,
+ const ValueType& value,
+ const HttpVersion& version = HttpVersion(1, 1)) {
+ // Create POST request with JSON body.
+ PostHttpRequestJsonPtr request(new PostHttpRequestJson(HttpRequest::Method::HTTP_POST,
+ "/", version));
+ // Body is a map with a specified parameter included.
+ ElementPtr body = Element::createMap();
+ body->set(parameter_name, Element::create(value));
+ request->setBodyAsJson(body);
+ try {
+ request->finalize();
+
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "failed to create request: " << ex.what();
+ }
+
+ return (request);
+ }
+
+ /// @brief Test that two consecutive requests can be sent over the same
+ /// connection (if persistent, if not persistent two connections will
+ /// be used).
+ ///
+ /// @param version HTTP version to be used.
+ void testConsecutiveRequests(const HttpVersion& version) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create a client and specify the URL on which the server can be reached.
+ HttpClient client(io_service_);
+ Url url("http://127.0.0.1:18123");
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1, version);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Initiate another request to the destination.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2, version);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure that the received responses are different. We check that by
+ // comparing value of the sequence parameters.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief Test that the client can communicate with two different
+ /// destinations simultaneously.
+ void testMultipleDestinations() {
+ // Start two servers running on different ports.
+ ASSERT_NO_THROW(listener_->start());
+ ASSERT_NO_THROW(listener2_->start());
+
+ // Create the client. It will be communicating with the two servers.
+ HttpClient client(io_service_);
+
+ // Specify the URLs on which the servers are available.
+ Url url1("http://127.0.0.1:18123");
+ Url url2("http://[::1]:18124");
+
+ // Create a request to the first server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url1, client_context_,
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Create a request to the second server.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url2, client_context_,
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure we have received two different responses.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief Test that the client can communicate with the same destination
+ /// address and port but with different TLS contexts so
+ void testMultipleTlsContexts() {
+ // Start only one server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create the client.
+ HttpClient client(io_service_);
+
+ // Create a second client context.
+ TlsContextPtr client_context2;
+ configClient(client_context2);
+
+ // Specify the URL on which the server is available.
+ Url url("http://127.0.0.1:18123");
+
+ // Create a request to the first server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Create a request with the second TLS context.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context2,
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Record subject and issuer: they will be checked during response creation.
+ HttpRequest::recordSubject_ = true;
+ HttpRequest::recordIssuer_ = true;
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure we have received two different responses.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief Test that idle connection can be resumed for second request.
+ void testIdleConnection() {
+ // Start the server that has short idle timeout. It closes the idle
+ // connection after 200ms.
+ ASSERT_NO_THROW(listener3_->start());
+
+ // Create the client that will communicate with this server.
+ HttpClient client(io_service_);
+
+ // Specify the URL of this server.
+ Url url("http://127.0.0.1:18125");
+
+ // Create the first request.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request1, response1,
+ [this](const boost::system::error_code& ec, const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Run the IO service until the response is received.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure the response has been received.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ // Delay the generation of the second request by 2x server idle timeout.
+ // This should be enough to cause the server to close the connection.
+ ASSERT_NO_THROW(runIOService(SHORT_IDLE_TIMEOUT * 2));
+
+ // Create another request.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request2, response2,
+ [this](const boost::system::error_code& ec, const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Actually trigger the second request.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sire that the server has responded.
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ // Make sure that two different responses have been received.
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief This test verifies that the client returns IO error code when the
+ /// server is unreachable.
+ void testUnreachable () {
+ // Create the client.
+ HttpClient client(io_service_);
+
+ // Specify the URL of the server. This server is down.
+ Url url("http://127.0.0.1:18123");
+
+ // Create the request.
+ PostHttpRequestJsonPtr request = createRequest("sequence", 1);
+ HttpResponseJsonPtr response(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request, response,
+ [this](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+ // The server should have returned an IO error.
+ if (!ec) {
+ ADD_FAILURE() << "asyncSendRequest didn't fail";
+ }
+ }));
+
+ // Actually trigger the request.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Test that an error is returned by the client if the server
+ /// response is malformed.
+ void testMalformedResponse () {
+ // Start the server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create the client.
+ HttpClient client(io_service_);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ // The response is going to be malformed in such a way that it holds
+ // an invalid content type. We affect the content type by creating
+ // a request that holds a JSON parameter requesting a specific
+ // content type.
+ PostHttpRequestJsonPtr request = createRequest("requested-content-type",
+ "text/html");
+ HttpResponseJsonPtr response(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request, response,
+ [this](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string& parsing_error) {
+ io_service_.stop();
+ // There should be no IO error (answer from the server is received).
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ // The response object is NULL because it couldn't be finalized.
+ EXPECT_FALSE(response);
+ // The message parsing error should be returned.
+ EXPECT_FALSE(parsing_error.empty());
+ }));
+
+ // Actually trigger the request.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Test that client times out when it doesn't receive the entire
+ /// response from the server within a desired time.
+ void testClientRequestTimeout() {
+ // Start the server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create the client.
+ HttpClient client(io_service_);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ unsigned cb_num = 0;
+
+ // Create the request which asks the server to generate a partial
+ // (although well formed) response. The client will be waiting for the
+ // rest of the response to be provided and will eventually time out.
+ PostHttpRequestJsonPtr request1 = createRequest("partial-response", true);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ // This value will be set to true if the connection close callback is
+ // invoked upon time out.
+ auto connection_closed = false;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request1, response1,
+ [this, &cb_num](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ // In this particular case we know exactly the type of the
+ // IO error returned, because the client explicitly sets this
+ // error code.
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ // There should be no response returned.
+ EXPECT_FALSE(response);
+ },
+ HttpClient::RequestTimeout(100),
+ HttpClient::ConnectHandler(),
+ HttpClient::HandshakeHandler(),
+ [&connection_closed](const int) {
+ // This callback is called when the connection gets closed
+ // by the client.
+ connection_closed = true;
+ })
+ );
+
+ // Create another request after the timeout. It should be handled ok.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request2, response2,
+ [this, &cb_num](const boost::system::error_code& /*ec*/,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+ // Make sure that the client has closed the connection upon timeout.
+ EXPECT_TRUE(connection_closed);
+ }
+
+ /// @brief Test that client times out when connection takes too long.
+ void testClientConnectTimeout() {
+ // Start the server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create the client.
+ HttpClient client(io_service_);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ unsigned cb_num = 0;
+
+ PostHttpRequestJsonPtr request = createRequest("sequence", 1);
+ HttpResponseJsonPtr response(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request, response,
+ [this, &cb_num](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ // In this particular case we know exactly the type of the
+ // IO error returned, because the client explicitly sets this
+ // error code.
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ // There should be no response returned.
+ EXPECT_FALSE(response);
+ },
+ HttpClient::RequestTimeout(100),
+
+ // This callback is invoked upon an attempt to connect to the
+ // server. The false value indicates to the HttpClient to not
+ // try to send a request to the server. This simulates the
+ // case of connect() taking very long and should eventually
+ // cause the transaction to time out.
+ [](const boost::system::error_code& /*ec*/, int) {
+ return (false);
+ }));
+
+ // Create another request after the timeout. It should be handled ok.
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request, response,
+ [this, &cb_num](const boost::system::error_code& /*ec*/,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Tests the behavior of the HTTP client when the premature
+ /// timeout occurs.
+ ///
+ /// The premature timeout may occur when the system clock is moved
+ /// during the transaction. This test simulates this behavior by
+ /// starting new transaction and waiting for the timeout to occur
+ /// before the IO service is ran. The timeout handler is invoked
+ /// first and it resets the transaction state. This test verifies
+ /// that the started transaction tears down gracefully after the
+ /// transaction state is reset.
+ ///
+ /// There are two variants of this test. The first variant schedules
+ /// one transaction before running the IO service. The second variant
+ /// schedules two transactions prior to running the IO service. The
+ /// second transaction is queued, so it is expected that it doesn't
+ /// time out and it runs successfully.
+ ///
+ /// @param queue_two_requests Boolean value indicating if a single
+ /// transaction should be queued (false), or two (true).
+ void testClientRequestLateStart(const bool queue_two_requests) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create the client.
+ HttpClient client(io_service_);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ // Generate first request.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+
+ // Use very short timeout to make sure that it occurs before we actually
+ // run the transaction.
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request1, response1,
+ [](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+
+ // In this particular case we know exactly the type of the
+ // IO error returned, because the client explicitly sets this
+ // error code.
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ // There should be no response returned.
+ EXPECT_FALSE(response);
+ },
+ HttpClient::RequestTimeout(1)));
+
+ if (queue_two_requests) {
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request2, response2,
+ [](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+
+ // This second request should be successful.
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ EXPECT_TRUE(response);
+ }));
+ }
+
+ // This waits for 3ms to make sure that the timeout occurs before we
+ // run the transaction. This leads to an unusual situation that the
+ // transaction state is reset as a result of the timeout but the
+ // transaction is alive. We want to make sure that the client can
+ // gracefully deal with this situation.
+ usleep(3000);
+
+ // Run the transaction and hope it will gracefully tear down.
+ ASSERT_NO_THROW(runIOService(100));
+
+ // Now try to send another request to make sure that the client
+ // is healthy.
+ PostHttpRequestJsonPtr request3 = createRequest("sequence", 3);
+ HttpResponseJsonPtr response3(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request3, response3,
+ [this](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+
+ // Everything should be ok.
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Tests that underlying TCP socket can be registered and
+ /// unregistered via connection and close callbacks.
+ ///
+ /// It conducts to consecutive requests over the same client.
+ ///
+ /// @param version HTTP version to be used.
+ void testConnectCloseCallbacks(const HttpVersion& version) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create a client and specify the URL on which the server can be reached.
+ HttpClient client(io_service_);
+ Url url("http://127.0.0.1:18123");
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1, version);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ExternalMonitor monitor;
+
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Initiate another request to the destination.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2, version);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // We should have had 2 connect invocations, no closes
+ // and a valid registered fd
+ EXPECT_EQ(2, monitor.connect_cnt_);
+ EXPECT_EQ(0, monitor.close_cnt_);
+ EXPECT_GT(monitor.registered_fd_, -1);
+
+ // Make sure that the received responses are different. We check that by
+ // comparing value of the sequence parameters.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+
+ // Stopping the client the close the connection.
+ client.stop();
+
+ // We should have had 2 connect invocations, 1 closes
+ // and an invalid registered fd
+ EXPECT_EQ(2, monitor.connect_cnt_);
+ EXPECT_EQ(1, monitor.close_cnt_);
+ EXPECT_EQ(-1, monitor.registered_fd_);
+ }
+
+ /// @brief Tests detection and handling out-of-band socket events
+ ///
+ /// It initiates a transaction and verifies that a mid-transaction call
+ /// to HttpClient::closeIfOutOfBand() has no affect on the connection.
+ /// After successful completion of the transaction, a second call to
+ /// HttpClient::closeIfOutOfBand() is made. This should result in the
+ /// connection being closed.
+ /// This step is repeated to verify that after an OOB closure, transactions
+ /// to the same destination can be processed.
+ ///
+ /// Lastly, we verify that HttpClient::stop() closes the connection correctly.
+ ///
+ /// @param version HTTP version to be used.
+ void testCloseIfOutOfBand(const HttpVersion& version) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create a client and specify the URL on which the server can be reached.
+ HttpClient client(io_service_);
+ Url url("http://127.0.0.1:18123");
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1, version);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ExternalMonitor monitor;
+
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request1, response1,
+ [this, &client, &resp_num, &monitor](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num == 1) {
+ io_service_.stop();
+ }
+
+ // We should have 1 connect.
+ EXPECT_EQ(1, monitor.connect_cnt_);
+ // We should have 1 handshake.
+ EXPECT_EQ(1, monitor.handshake_cnt_);
+ // We should have 0 closes
+ EXPECT_EQ(0, monitor.close_cnt_);
+ // We should have a valid fd.
+ ASSERT_GT(monitor.registered_fd_, -1);
+ int orig_fd = monitor.registered_fd_;
+
+ // Test our socket for OOBness.
+ client.closeIfOutOfBand(monitor.registered_fd_);
+
+ // Since we're in a transaction, we should have no closes and
+ // the same valid fd.
+ EXPECT_EQ(0, monitor.close_cnt_);
+ ASSERT_EQ(monitor.registered_fd_, orig_fd);
+
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure that we received a response.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+ EXPECT_EQ(1, sequence1->intValue());
+
+ // We should have had 1 connect invocations, no closes
+ // and a valid registered fd
+ EXPECT_EQ(1, monitor.connect_cnt_);
+ EXPECT_EQ(0, monitor.close_cnt_);
+ EXPECT_GT(monitor.registered_fd_, -1);
+
+ // Test our socket for OOBness.
+ client.closeIfOutOfBand(monitor.registered_fd_);
+
+ // Since we're in a transaction, we should have no closes and
+ // the same valid fd.
+ EXPECT_EQ(1, monitor.close_cnt_);
+ EXPECT_EQ(-1, monitor.registered_fd_);
+
+ // Now let's do another request to the destination to verify that
+ // we'll reopen the connection without issue.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2, version);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request2, response2,
+ [this, &client, &resp_num, &monitor](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num == 1) {
+ io_service_.stop();
+ }
+
+ // We should have 1 connect.
+ EXPECT_EQ(2, monitor.connect_cnt_);
+ // We should have 2 handshake.
+ EXPECT_EQ(2, monitor.handshake_cnt_);
+ // We should have 0 closes
+ EXPECT_EQ(1, monitor.close_cnt_);
+ // We should have a valid fd.
+ ASSERT_GT(monitor.registered_fd_, -1);
+ int orig_fd = monitor.registered_fd_;
+
+ // Test our socket for OOBness.
+ client.closeIfOutOfBand(monitor.registered_fd_);
+
+ // Since we're in a transaction, we should have no closes and
+ // the same valid fd.
+ EXPECT_EQ(1, monitor.close_cnt_);
+ ASSERT_EQ(monitor.registered_fd_, orig_fd);
+
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure that we received the second response.
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+ EXPECT_EQ(2, sequence2->intValue());
+
+ // Stopping the client the close the connection.
+ client.stop();
+
+ // We should have had 2 connect invocations, 2 closes
+ // and an invalid registered fd
+ EXPECT_EQ(2, monitor.connect_cnt_);
+ EXPECT_EQ(2, monitor.close_cnt_);
+ EXPECT_EQ(-1, monitor.registered_fd_);
+ }
+
+ /// @brief Simulates external registry of Connection TCP sockets
+ ///
+ /// Provides methods compatible with Connection callbacks for connect
+ /// and close operations.
+ class ExternalMonitor {
+ public:
+ /// @brief Constructor
+ ExternalMonitor()
+ : registered_fd_(-1), connect_cnt_(0), handshake_cnt_(0),
+ close_cnt_(0) {
+ }
+
+ /// @brief Connect callback handler
+ /// @param ec Error status of the ASIO connect
+ /// @param tcp_native_fd socket descriptor to register
+ bool connectHandler(const boost::system::error_code& ec, int tcp_native_fd) {
+ ++connect_cnt_;
+ if ((!ec || (ec.value() == boost::asio::error::in_progress))
+ && (tcp_native_fd >= 0)) {
+ registered_fd_ = tcp_native_fd;
+ return (true);
+ } else if ((ec.value() == boost::asio::error::already_connected)
+ && (registered_fd_ != tcp_native_fd)) {
+ return (false);
+ }
+
+ // ec indicates an error, return true, so that error can be handled
+ // by Connection logic.
+ return (true);
+ }
+
+ /// @brief Handshake callback handler
+ /// @param ec Error status of the ASIO connect
+ bool handshakeHandler(const boost::system::error_code&, int) {
+ ++handshake_cnt_;
+ // ec indicates an error, return true, so that error can be handled
+ // by Connection logic.
+ return (true);
+ }
+
+ /// @brief Close callback handler
+ ///
+ /// @param tcp_native_fd socket descriptor to register
+ void closeHandler(int tcp_native_fd) {
+ ++close_cnt_;
+ EXPECT_EQ(tcp_native_fd, registered_fd_) << "closeHandler fd mismatch";
+ if (tcp_native_fd >= 0) {
+ registered_fd_ = -1;
+ }
+ }
+
+ /// @brief Keeps track of socket currently "registered" for external monitoring.
+ int registered_fd_;
+
+ /// @brief Tracks how many times the connect callback is invoked.
+ int connect_cnt_;
+
+ /// @brief Tracks how many times the handshake callback is invoked.
+ int handshake_cnt_;
+
+ /// @brief Tracks how many times the close callback is invoked.
+ int close_cnt_;
+ };
+
+ /// @brief Instance of the listener used in the tests.
+ std::unique_ptr<HttpListener> listener_;
+
+ /// @brief Instance of the second listener used in the tests.
+ std::unique_ptr<HttpListener> listener2_;
+
+ /// @brief Instance of the third listener used in the tests (with short idle
+ /// timeout).
+ std::unique_ptr<HttpListener> listener3_;
+
+ /// @brief Server TLS context.
+ TlsContextPtr server_context_;
+
+ /// @brief Client TLS context.
+ TlsContextPtr client_context_;
+};
+
+#ifndef DISABLE_SOME_TESTS
+// Test that two consecutive requests can be sent over the same (persistent)
+// connection.
+TEST_F(HttpsClientTest, consecutiveRequests) {
+
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 1)));
+}
+
+// Test that two consecutive requests can be sent over the same (persistent)
+// connection.
+TEST_F(HttpsClientTest, consecutiveRequestsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 1)));
+}
+#endif
+
+// Test that two consecutive requests can be sent over non-persistent connection.
+// This is achieved by sending HTTP/1.0 requests, which are non-persistent by
+// default. The client should close the connection right after receiving a response
+// from the server.
+TEST_F(HttpsClientTest, closeBetweenRequests) {
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 0)));
+}
+
+// Test that two consecutive requests can be sent over non-persistent connection.
+// This is achieved by sending HTTP/1.0 requests, which are non-persistent by
+// default. The client should close the connection right after receiving a response
+// from the server.
+TEST_F(HttpsClientTest, closeBetweenRequestsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 0)));
+}
+
+// Test that the client can communicate with two different destinations
+// simultaneously.
+TEST_F(HttpsClientTest, multipleDestinations) {
+ ASSERT_NO_FATAL_FAILURE(testMultipleDestinations());
+}
+
+// Test that the client can communicate with two different destinations
+// simultaneously.
+TEST_F(HttpsClientTest, multipleDestinationsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testMultipleDestinations());
+}
+
+// Test that the client can use two different TLS contexts to the same
+// destination address and port simultaneously.
+TEST_F(HttpsClientTest, multipleTlsContexts) {
+ ASSERT_NO_FATAL_FAILURE(testMultipleTlsContexts());
+}
+
+// Test that the client can use two different TLS contexts to the same
+// destination address and port simultaneously.
+TEST_F(HttpsClientTest, multipleTlsContextsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testMultipleTlsContexts());
+}
+
+// Test that idle connection can be resumed for second request.
+TEST_F(HttpsClientTest, idleConnection) {
+ ASSERT_NO_FATAL_FAILURE(testIdleConnection());
+}
+
+// Test that idle connection can be resumed for second request.
+TEST_F(HttpsClientTest, idleConnectionMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testIdleConnection());
+}
+
+// This test verifies that the client returns IO error code when the
+// server is unreachable.
+TEST_F(HttpsClientTest, unreachable) {
+ ASSERT_NO_FATAL_FAILURE(testUnreachable());
+}
+
+// This test verifies that the client returns IO error code when the
+// server is unreachable.
+TEST_F(HttpsClientTest, unreachableMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testUnreachable());
+}
+
+// Test that an error is returned by the client if the server response is
+// malformed.
+TEST_F(HttpsClientTest, malformedResponse) {
+ ASSERT_NO_FATAL_FAILURE(testMalformedResponse());
+}
+
+// Test that an error is returned by the client if the server response is
+// malformed.
+TEST_F(HttpsClientTest, malformedResponseMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testMalformedResponse());
+}
+
+// Test that client times out when it doesn't receive the entire response
+// from the server within a desired time.
+TEST_F(HttpsClientTest, clientRequestTimeout) {
+ ASSERT_NO_FATAL_FAILURE(testClientRequestTimeout());
+}
+
+// Test that client times out when it doesn't receive the entire response
+// from the server within a desired time.
+TEST_F(HttpsClientTest, clientRequestTimeoutMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testClientRequestTimeout());
+}
+
+// This test verifies the behavior of the HTTP client when the premature
+// (and unexpected) timeout occurs. The premature timeout may be caused
+// by the system clock move.
+TEST_F(HttpsClientTest, DISABLED_clientRequestLateStartNoQueue) {
+ testClientRequestLateStart(false);
+}
+
+// This test verifies the behavior of the HTTP client when the premature
+// (and unexpected) timeout occurs. The premature timeout may be caused
+// by the system clock move.
+TEST_F(HttpsClientTest, DISABLED_clientRequestLateStartNoQueueMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testClientRequestLateStart(false);
+}
+
+#ifndef DISABLE_SOME_TESTS
+// This test verifies the behavior of the HTTP client when the premature
+// timeout occurs and there are requests queued after the request which
+// times out.
+TEST_F(HttpsClientTest, clientRequestLateStartQueue) {
+
+ testClientRequestLateStart(true);
+}
+
+// This test verifies the behavior of the HTTP client when the premature
+// timeout occurs and there are requests queued after the request which
+// times out.
+TEST_F(HttpsClientTest, clientRequestLateStartQueueMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testClientRequestLateStart(true);
+}
+#endif
+
+// Test that client times out when connection takes too long.
+TEST_F(HttpsClientTest, clientConnectTimeout) {
+ ASSERT_NO_FATAL_FAILURE(testClientConnectTimeout());
+}
+
+// Test that client times out when connection takes too long.
+TEST_F(HttpsClientTest, clientConnectTimeoutMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testClientConnectTimeout());
+}
+
+#ifndef DISABLE_SOME_TESTS
+/// Tests that connect and close callbacks work correctly.
+TEST_F(HttpsClientTest, connectCloseCallbacks) {
+ ASSERT_NO_FATAL_FAILURE(testConnectCloseCallbacks(HttpVersion(1, 1)));
+}
+
+/// Tests that connect and close callbacks work correctly.
+TEST_F(HttpsClientTest, connectCloseCallbacksMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testConnectCloseCallbacks(HttpVersion(1, 1)));
+}
+#endif
+
+/// Tests that HttpClient::closeIfOutOfBand works correctly.
+TEST_F(HttpsClientTest, closeIfOutOfBand) {
+ ASSERT_NO_FATAL_FAILURE(testCloseIfOutOfBand(HttpVersion(1, 1)));
+}
+
+/// Tests that HttpClient::closeIfOutOfBand works correctly.
+TEST_F(HttpsClientTest, closeIfOutOfBandMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testCloseIfOutOfBand(HttpVersion(1, 1)));
+}
+
+}
diff --git a/src/lib/http/tests/tls_server_unittests.cc b/src/lib/http/tests/tls_server_unittests.cc
new file mode 100644
index 0000000..a2a6f9d
--- /dev/null
+++ b/src/lib/http/tests/tls_server_unittests.cc
@@ -0,0 +1,1253 @@
+// Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/tls_acceptor.h>
+#include <asiolink/testutils/test_tls.h>
+#include <cc/data.h>
+#include <http/client.h>
+#include <http/http_types.h>
+#include <http/listener.h>
+#include <http/listener_impl.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_creator_factory.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <http/url.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <list>
+#include <sstream>
+#include <string>
+
+using namespace boost::asio;
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+using namespace isc::asiolink::test;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+/// @todo: put the common part of client and server tests in its own file(s).
+
+namespace {
+
+/// @brief IP address to which HTTP service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief IPv6 address to whch HTTP service is bound.
+const std::string IPV6_SERVER_ADDRESS = "::1";
+
+/// @brief Port number to which HTTP service is bound.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Request Timeout used in most of the tests (ms).
+const long REQUEST_TIMEOUT = 10000;
+
+/// @brief Persistent connection idle timeout used in most of the tests (ms).
+const long IDLE_TIMEOUT = 10000;
+
+/// @brief Persistent connection idle timeout used in tests where idle connections
+/// are tested (ms).
+const long SHORT_IDLE_TIMEOUT = 200;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Generic test HTTP response.
+typedef TestHttpResponseBase<HttpResponse> GenericResponse;
+
+/// @brief Pointer to generic test HTTP response.
+typedef boost::shared_ptr<GenericResponse> GenericResponsePtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+ }
+
+private:
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed ok).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // This will generate the response holding JSON content.
+ ResponsePtr response(new Response(http_version, status_code));
+ response->finalize();
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// This method generates 3 types of responses:
+ /// - response with a requested content type,
+ /// - partial response with incomplete JSON body,
+ /// - response with JSON body copied from the request.
+ ///
+ /// The first one is useful to test situations when received response can't
+ /// be parsed because of the content type mismatch. The second one is useful
+ /// to test request timeouts. The third type is used by most of the unit tests
+ /// to test successful transactions.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP OK response with no content.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) {
+ // Request must always be JSON.
+ PostHttpRequestJsonPtr request_json =
+ boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
+ ConstElementPtr body;
+ if (request_json) {
+ body = request_json->getBodyAsJson();
+ if (body) {
+ // Check if the client requested one of the two first response
+ // types.
+ GenericResponsePtr response;
+ ConstElementPtr content_type = body->get("requested-content-type");
+ ConstElementPtr partial_response = body->get("partial-response");
+ if (content_type || partial_response) {
+ // The first two response types can only be generated using the
+ // generic response as we have to explicitly modify some of the
+ // values.
+ response.reset(new GenericResponse(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ HttpResponseContextPtr ctx = response->context();
+
+ if (content_type) {
+ // Provide requested content type.
+ ctx->headers_.push_back(HttpHeaderContext("Content-Type",
+ content_type->stringValue()));
+ // It doesn't matter what body is there.
+ ctx->body_ = "abcd";
+ response->finalize();
+
+ } else {
+ // Generate JSON response.
+ ctx->headers_.push_back(HttpHeaderContext("Content-Type",
+ "application/json"));
+ // The body lacks '}' so the client will be waiting for it and
+ // eventually should time out.
+ ctx->body_ = "{";
+ response->finalize();
+ // The auto generated Content-Length header would be based on the
+ // body size (so set to 1 byte). We have to override it to
+ // account for the missing '}' character.
+ response->setContentLength(2);
+ }
+ return (response);
+ }
+ }
+ }
+
+ // Third type of response is requested.
+ ResponsePtr response(new Response(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ // If body was included in the request. Let's copy it.
+ if (body) {
+ response->setBodyAsJson(body);
+ }
+
+ response->finalize();
+ return (response);
+ }
+};
+
+/// @brief Implementation of the test @ref HttpResponseCreatorFactory.
+///
+/// This factory class creates @ref TestHttpResponseCreator instances.
+class TestHttpResponseCreatorFactory : public HttpResponseCreatorFactory {
+public:
+
+ /// @brief Creates @ref TestHttpResponseCreator instance.
+ virtual HttpResponseCreatorPtr create() const {
+ HttpResponseCreatorPtr response_creator(new TestHttpResponseCreator());
+ return (response_creator);
+ }
+};
+
+/// @brief Implementation of the HTTP listener used in tests.
+///
+/// This implementation replaces the @c HttpConnection type with a custom
+/// implementation.
+///
+/// @tparam HttpConnectionType Type of the connection object to be used by
+/// the listener implementation.
+template<typename HttpConnectionType>
+class HttpListenerImplCustom : public HttpListenerImpl {
+public:
+
+ HttpListenerImplCustom(IOService& io_service,
+ const IOAddress& server_address,
+ const unsigned short server_port,
+ const TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const long request_timeout,
+ const long idle_timeout)
+ : HttpListenerImpl(io_service, server_address, server_port,
+ tls_context, creator_factory, request_timeout,
+ idle_timeout) {
+ }
+
+protected:
+
+ /// @brief Creates an instance of the @c HttpConnection.
+ ///
+ /// This method is virtual so as it can be overridden when customized
+ /// connections are to be used, e.g. in case of unit testing.
+ ///
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ ///
+ /// @return Pointer to the created connection.
+ virtual HttpConnectionPtr createConnection(const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback) {
+ TlsContextPtr tls_context;
+ configClient(tls_context);
+ HttpConnectionPtr
+ conn(new HttpConnectionType(io_service_, acceptor_,
+ tls_context_, connections_,
+ response_creator, callback,
+ request_timeout_, idle_timeout_));
+ return (conn);
+ }
+};
+
+/// @brief Derivation of the @c HttpListener used in tests.
+///
+/// This class replaces the default implementation instance with the
+/// @c HttpListenerImplCustom using the customized connection type.
+///
+/// @tparam HttpConnectionType Type of the connection object to be used by
+/// the listener implementation.
+template<typename HttpConnectionType>
+class HttpListenerCustom : public HttpListener {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the listener.
+ /// @param server_address Address on which the HTTP service should run.
+ /// @param server_port Port number on which the HTTP service should run.
+ /// @param tls_context TLS context.
+ /// @param creator_factory Pointer to the caller-defined
+ /// @ref HttpResponseCreatorFactory derivation which should be used to
+ /// create @ref HttpResponseCreator instances.
+ /// @param request_timeout Timeout after which the HTTP Request Timeout
+ /// is generated.
+ /// @param idle_timeout Timeout after which an idle persistent HTTP
+ /// connection is closed by the server.
+ ///
+ /// @throw HttpListenerError when any of the specified parameters is
+ /// invalid.
+ HttpListenerCustom(IOService& io_service,
+ const IOAddress& server_address,
+ const unsigned short server_port,
+ const TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const HttpListener::RequestTimeout& request_timeout,
+ const HttpListener::IdleTimeout& idle_timeout)
+ : HttpListener(io_service, server_address, server_port,
+ tls_context, creator_factory,
+ request_timeout, idle_timeout) {
+ // Replace the default implementation with the customized version
+ // using the custom derivation of the HttpConnection.
+ impl_.reset(new HttpListenerImplCustom<HttpConnectionType>
+ (io_service, server_address, server_port,
+ tls_context, creator_factory, request_timeout.value_,
+ idle_timeout.value_));
+ }
+};
+
+/// @brief Implementation of the @c HttpConnection which injects greater
+/// length value than the buffer size into the write socket callback.
+class HttpConnectionLongWriteBuffer : public HttpConnection {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the connection.
+ /// @param acceptor Pointer to the TCP acceptor object used to listen for
+ /// new HTTP connections.
+ /// @param tls_context TLS context.
+ /// @param connection_pool Connection pool in which this connection is
+ /// stored.
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ /// @param request_timeout Configured timeout for a HTTP request.
+ /// @param idle_timeout Timeout after which persistent HTTP connection is
+ /// closed by the server.
+ HttpConnectionLongWriteBuffer(IOService& io_service,
+ const HttpAcceptorPtr& acceptor,
+ const TlsContextPtr& tls_context,
+ HttpConnectionPool& connection_pool,
+ const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback,
+ const long request_timeout,
+ const long idle_timeout)
+ : HttpConnection(io_service, acceptor, tls_context, connection_pool,
+ response_creator, callback, request_timeout,
+ idle_timeout) {
+ }
+
+ /// @brief Callback invoked when data is sent over the socket.
+ ///
+ /// @param transaction Pointer to the transaction for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the data sent.
+ virtual void socketWriteCallback(HttpConnection::TransactionPtr transaction,
+ boost::system::error_code ec,
+ size_t length) {
+ // Pass greater length of the data written. The callback should deal
+ // with this and adjust the data length.
+ HttpConnection::socketWriteCallback(transaction, ec, length + 1);
+ }
+};
+
+/// @brief Implementation of the @c HttpConnection which replaces
+/// transaction instance prior to calling write socket callback.
+class HttpConnectionTransactionChange : public HttpConnection {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the connection.
+ /// @param acceptor Pointer to the TCP acceptor object used to listen for
+ /// new HTTP connections.
+ /// @param tls_context TLS context.
+ /// @param connection_pool Connection pool in which this connection is
+ /// stored.
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ /// @param request_timeout Configured timeout for a HTTP request.
+ /// @param idle_timeout Timeout after which persistent HTTP connection is
+ /// closed by the server.
+ HttpConnectionTransactionChange(IOService& io_service,
+ const HttpAcceptorPtr& acceptor,
+ const TlsContextPtr& tls_context,
+ HttpConnectionPool& connection_pool,
+ const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback,
+ const long request_timeout,
+ const long idle_timeout)
+ : HttpConnection(io_service, acceptor, tls_context, connection_pool,
+ response_creator, callback, request_timeout,
+ idle_timeout) {
+ }
+
+ /// @brief Callback invoked when data is sent over the socket.
+ ///
+ /// @param transaction Pointer to the transaction for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the data sent.
+ virtual void socketWriteCallback(HttpConnection::TransactionPtr transaction,
+ boost::system::error_code ec,
+ size_t length) {
+ // Replace the transaction. The socket callback should deal with this
+ // gracefully. It should detect that the output buffer is empty. Then
+ // try to see if the connection is persistent. This check should fail,
+ // because the request hasn't been created/finalized. The exception
+ // thrown upon checking the persistence should be caught and the
+ // connection closed.
+ transaction = HttpConnection::Transaction::create(response_creator_);
+ HttpConnection::socketWriteCallback(transaction, ec, length);
+ }
+};
+
+
+/// @brief Entity which can connect to the HTTP server endpoint.
+class TestHttpClient : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new socket instance. It doesn't connect. Call
+ /// connect() to connect to the server.
+ ///
+ /// @param io_service IO service to be stopped on error.
+ /// @param tls_context TLS context.
+ TestHttpClient(IOService& io_service, TlsContextPtr tls_context)
+ : io_service_(io_service.get_io_service()),
+ stream_(io_service_, tls_context->getContext()),
+ buf_(), response_() {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes the underlying socket if it is open.
+ ~TestHttpClient() {
+ close();
+ }
+
+ /// @brief Send HTTP request specified in textual format.
+ ///
+ /// @param request HTTP request in the textual format.
+ void startRequest(const std::string& request) {
+ tcp::endpoint endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT);
+ stream_.lowest_layer().async_connect(endpoint,
+ [this, request](const boost::system::error_code& ec) {
+ if (ec) {
+ // One would expect that async_connect wouldn't return
+ // EINPROGRESS error code, but simply wait for the connection
+ // to get established before the handler is invoked. It turns out,
+ // however, that on some OSes the connect handler may receive this
+ // error code which doesn't necessarily indicate a problem.
+ // Making an attempt to write and read from this socket will
+ // typically succeed. So, we ignore this error.
+ if (ec.value() != boost::asio::error::in_progress) {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ io_service_.stop();
+ return;
+ }
+ }
+ stream_.async_handshake(roleToImpl(TlsRole::CLIENT),
+ [this, request](const boost::system::error_code& ec) {
+ if (ec) {
+ ADD_FAILURE() << "error occurred during handshake: "
+ << ec.message();
+ io_service_.stop();
+ return;
+ }
+ sendRequest(request);
+ });
+ });
+ }
+
+ /// @brief Send HTTP request.
+ ///
+ /// @param request HTTP request in the textual format.
+ void sendRequest(const std::string& request) {
+ sendPartialRequest(request);
+ }
+
+ /// @brief Send part of the HTTP request.
+ ///
+ /// @param request part of the HTTP request to be sent.
+ void sendPartialRequest(std::string request) {
+ boost::asio::async_write(stream_,
+ boost::asio::buffer(request.data(), request.size()),
+ [this, request](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) mutable {
+ if (ec) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ } else if ((ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block)) {
+ // If we should try again make sure there is no garbage in the
+ // bytes_transferred.
+ bytes_transferred = 0;
+
+ } else {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ io_service_.stop();
+ return;
+ }
+ }
+
+ // Remove the part of the request which has been sent.
+ if (bytes_transferred > 0 && (request.size() <= bytes_transferred)) {
+ request.erase(0, bytes_transferred);
+ }
+
+ // Continue sending request data if there are still some data to be
+ // sent.
+ if (!request.empty()) {
+ sendPartialRequest(request);
+
+ } else {
+ // Request has been sent. Start receiving response.
+ response_.clear();
+ receivePartialResponse();
+ }
+ });
+ }
+
+ /// @brief Receive response from the server.
+ void receivePartialResponse() {
+ stream_.async_read_some(boost::asio::buffer(buf_.data(), buf_.size()),
+ [this](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) {
+ if (ec) {
+ // IO service stopped so simply return.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ } else if ((ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block)) {
+ // If we should try again, make sure that there is no garbage
+ // in the bytes_transferred.
+ bytes_transferred = 0;
+
+ } else {
+ // Error occurred, bail...
+ ADD_FAILURE() << "error occurred while receiving HTTP"
+ " response from the server: " << ec.message();
+ io_service_.stop();
+ }
+ }
+
+ if (bytes_transferred > 0) {
+ response_.insert(response_.end(), buf_.data(),
+ buf_.data() + bytes_transferred);
+ }
+
+ // Two consecutive new lines end the part of the response we're
+ // expecting.
+ if (response_.find("\r\n\r\n", 0) != std::string::npos) {
+ io_service_.stop();
+
+ } else {
+ receivePartialResponse();
+ }
+
+ });
+ }
+
+ /// @brief Checks if the TCP connection is still open.
+ ///
+ /// Tests the TCP connection by trying to read from the socket.
+ /// Unfortunately expected failure depends on a race between the read
+ /// and the other side close so to check if the connection is closed
+ /// please use @c isConnectionClosed instead.
+ ///
+ /// @return true if the TCP connection is open.
+ bool isConnectionAlive() {
+ // Remember the current non blocking setting.
+ const bool non_blocking_orig = stream_.lowest_layer().non_blocking();
+ // Set the socket to non blocking mode. We're going to test if the socket
+ // returns would_block status on the attempt to read from it.
+ stream_.lowest_layer().non_blocking(true);
+
+ // We need to provide a buffer for a call to read.
+ char data[2];
+ boost::system::error_code ec;
+ boost::asio::read(stream_, boost::asio::buffer(data, sizeof(data)), ec);
+
+ // Revert the original non_blocking flag on the socket.
+ stream_.lowest_layer().non_blocking(non_blocking_orig);
+
+ // If the connection is alive we'd typically get would_block status code.
+ // If there are any data that haven't been read we may also get success
+ // status. We're guessing that try_again may also be returned by some
+ // implementations in some situations. Any other error code indicates a
+ // problem with the connection so we assume that the connection has been
+ // closed.
+ return (!ec || (ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block));
+ }
+
+ /// @brief Checks if the TCP connection is already closed.
+ ///
+ /// Tests the TCP connection by trying to read from the socket.
+ /// The read can block so this must be used to check if a connection
+ /// is alive so to check if the connection is alive please always
+ /// use @c isConnectionAlive.
+ ///
+ /// @return true if the TCP connection is closed.
+ bool isConnectionClosed() {
+ // Remember the current non blocking setting.
+ const bool non_blocking_orig = stream_.lowest_layer().non_blocking();
+ // Set the socket to blocking mode. We're going to test if the socket
+ // returns eof status on the attempt to read from it.
+ stream_.lowest_layer().non_blocking(false);
+
+ // We need to provide a buffer for a call to read.
+ char data[2];
+ boost::system::error_code ec;
+ boost::asio::read(stream_, boost::asio::buffer(data, sizeof(data)), ec);
+
+ // Revert the original non_blocking flag on the socket.
+ stream_.lowest_layer().non_blocking(non_blocking_orig);
+
+ // If the connection is closed we'd typically get eof or
+ // stream_truncated status code.
+ return ((ec.value() == boost::asio::error::eof) ||
+ (ec.value() == STREAM_TRUNCATED));
+ }
+
+ /// @brief Close connection.
+ void close() {
+ stream_.lowest_layer().close();
+ }
+
+ std::string getResponse() const {
+ return (response_);
+ }
+
+private:
+
+ /// @brief Holds reference to the IO service.
+ boost::asio::io_service& io_service_;
+
+ /// @brief A socket used for the connection.
+ TlsStreamImpl stream_;
+
+ /// @brief Buffer into which response is written.
+ std::array<char, 8192> buf_;
+
+ /// @brief Response in the textual format.
+ std::string response_;
+};
+
+/// @brief Pointer to the TestHttpClient.
+typedef boost::shared_ptr<TestHttpClient> TestHttpClientPtr;
+
+/// @brief Test fixture class for @ref HttpListener.
+class HttpsListenerTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Starts test timer which detects timeouts.
+ HttpsListenerTest()
+ : io_service_(), factory_(new TestHttpResponseCreatorFactory()),
+ test_timer_(io_service_), run_io_service_timer_(io_service_),
+ clients_(), server_context_(), client_context_() {
+ configServer(server_context_);
+ configClient(client_context_);
+ test_timer_.setup(std::bind(&HttpsListenerTest::timeoutHandler, this, true),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes active HTTP clients.
+ virtual ~HttpsListenerTest() {
+ for (auto client = clients_.begin(); client != clients_.end();
+ ++client) {
+ (*client)->close();
+ }
+ }
+
+ /// @brief Connect to the endpoint.
+ ///
+ /// This method creates TestHttpClient instance and retains it in the clients_
+ /// list.
+ ///
+ /// @param request String containing the HTTP request to be sent.
+ void startRequest(const std::string& request) {
+ TestHttpClientPtr client(new TestHttpClient(io_service_,
+ client_context_));
+ clients_.push_back(client);
+ clients_.back()->startRequest(request);
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void timeoutHandler(const bool fail_on_timeout) {
+ if (fail_on_timeout) {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ }
+ io_service_.stop();
+ }
+
+ /// @brief Runs IO service with optional timeout.
+ ///
+ /// @param timeout Optional value specifying for how long the io service
+ /// should be ran.
+ void runIOService(long timeout = 0) {
+ io_service_.get_io_service().reset();
+
+ if (timeout > 0) {
+ run_io_service_timer_.setup(std::bind(&HttpsListenerTest::timeoutHandler,
+ this, false),
+ timeout, IntervalTimer::ONE_SHOT);
+ }
+ io_service_.run();
+ io_service_.get_io_service().reset();
+ io_service_.poll();
+ }
+
+ /// @brief Returns HTTP OK response expected by unit tests.
+ ///
+ /// @param http_version HTTP version.
+ ///
+ /// @return HTTP OK response expected by unit tests.
+ std::string httpOk(const HttpVersion& http_version) {
+ std::ostringstream s;
+ s << "HTTP/" << http_version.major_ << "." << http_version.minor_ << " 200 OK\r\n"
+ "Content-Length: 33\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"remote-address\": \"127.0.0.1\" }";
+ return (s.str());
+ }
+
+ /// @brief Tests that HTTP request timeout status is returned when the
+ /// server does not receive the entire request.
+ ///
+ /// @param request Partial request for which the parser will be waiting for
+ /// the next chunks of data.
+ /// @param expected_version HTTP version expected in the response.
+ void testRequestTimeout(const std::string& request,
+ const HttpVersion& expected_version) {
+ // Open the listener with the Request Timeout of 1 sec and post the
+ // partial request.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, server_context_,
+ factory_, HttpListener::RequestTimeout(1000),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+
+ // Build the reference response.
+ std::ostringstream expected_response;
+ expected_response
+ << "HTTP/" << expected_version.major_ << "." << expected_version.minor_
+ << " 408 Request Timeout\r\n"
+ "Content-Length: 44\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"result\": 408, \"text\": \"Request Timeout\" }";
+
+ // The server should wait for the missing part of the request for 1 second.
+ // The missing part never arrives so the server should respond with the
+ // HTTP Request Timeout status.
+ EXPECT_EQ(expected_response.str(), client->getResponse());
+ }
+
+ /// @brief Tests various cases when unexpected data is passed to the
+ /// socket write handler.
+ ///
+ /// This test uses the custom listener and the test specific derivations of
+ /// the @c HttpConnection class to enforce injection of the unexpected
+ /// data to the socket write callback. The two example applications of
+ /// this test are:
+ /// - injecting greater length value than the output buffer size,
+ /// - replacing the transaction with another transaction.
+ ///
+ /// It is expected that the socket write callback deals gracefully with
+ /// those situations.
+ ///
+ /// @tparam HttpConnectionType Test specific derivation of the
+ /// @c HttpConnection class.
+ template<typename HttpConnectionType>
+ void testWriteBufferIssues() {
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ // Use custom listener and the specialized connection object.
+ HttpListenerCustom<HttpConnectionType>
+ listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request.
+ ASSERT_NO_THROW(startRequest(request));
+
+ // Injecting unexpected data should not result in an exception.
+ ASSERT_NO_THROW(runIOService());
+
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+ }
+
+ /// @brief IO service used in the tests.
+ IOService io_service_;
+
+ /// @brief Pointer to the response creator factory.
+ HttpResponseCreatorFactoryPtr factory_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief Asynchronous timer for running IO service for a specified amount
+ /// of time.
+ IntervalTimer run_io_service_timer_;
+
+ /// @brief List of client connections.
+ std::list<TestHttpClientPtr> clients_;
+
+ /// @brief Server TLS context.
+ TlsContextPtr server_context_;
+
+ /// @brief Client TLS context.
+ TlsContextPtr client_context_;
+};
+
+// This test verifies that HTTP connection can be established and used to
+// transmit HTTP request and receive a response.
+TEST_F(HttpsListenerTest, listen) {
+ const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+
+// This test verifies that persistent HTTP connection can be established when
+// "Connection: Keep-Alive" header value is specified.
+TEST_F(HttpsListenerTest, keepAlive) {
+
+ // The first request contains the keep-alive header which instructs the server
+ // to maintain the TCP connection after sending a response.
+ std::string request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: Keep-Alive\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request with the keep-alive header.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ // We have sent keep-alive header so we expect that the connection with
+ // the server remains active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Test that we can send another request via the same connection. This time
+ // it lacks the keep-alive header, so the server should close the connection
+ // after sending the response.
+ request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ // Send request reusing the existing connection.
+ ASSERT_NO_THROW(client->sendRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ // Connection should have been closed by the server.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that persistent HTTP connection is established by default
+// when HTTP/1.1 is in use.
+TEST_F(HttpsListenerTest, persistentConnection) {
+
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the first request.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ // HTTP/1.1 connection is persistent by default.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Test that we can send another request via the same connection. This time
+ // it includes the "Connection: close" header which instructs the server to
+ // close the connection after responding.
+ request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: close\r\n\r\n"
+ "{ }";
+
+ // Send request reusing the existing connection.
+ ASSERT_NO_THROW(client->sendRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ // Connection should have been closed by the server.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that "keep-alive" connection is closed by the server after
+// an idle time.
+TEST_F(HttpsListenerTest, keepAliveTimeout) {
+
+ // The first request contains the keep-alive header which instructs the server
+ // to maintain the TCP connection after sending a response.
+ std::string request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: Keep-Alive\r\n\r\n"
+ "{ }";
+
+ // Specify the idle timeout of 500ms.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(500));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request with the keep-alive header.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ // We have sent keep-alive header so we expect that the connection with
+ // the server remains active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Run IO service for 1000ms. The idle time is set to 500ms, so the connection
+ // should be closed by the server while we wait here.
+ runIOService(1000);
+
+ // Make sure the connection has been closed.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ // Check if we can re-establish the connection and send another request.
+ clients_.clear();
+ request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that persistent connection is closed by the server after
+// an idle time.
+TEST_F(HttpsListenerTest, persistentConnectionTimeout) {
+
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ // Specify the idle timeout of 500ms.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(500));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ // The connection should remain active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Run IO service for 1000ms. The idle time is set to 500ms, so the connection
+ // should be closed by the server while we wait here.
+ runIOService(1000);
+
+ // Make sure the connection has been closed.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ // Check if we can re-establish the connection and send another request.
+ clients_.clear();
+ request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: close\r\n\r\n"
+ "{ }";
+
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that HTTP/1.1 connection remains open even if there is an
+// error in the message body.
+TEST_F(HttpsListenerTest, persistentConnectionBadBody) {
+
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 12\r\n\r\n"
+ "{ \"a\": abc }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ("HTTP/1.1 400 Bad Request\r\n"
+ "Content-Length: 40\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"result\": 400, \"text\": \"Bad Request\" }",
+ client->getResponse());
+
+ // The connection should remain active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Make sure that we can send another request. This time we specify the
+ // "close" connection-token to force the connection to close.
+ request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: close\r\n\r\n"
+ "{ }";
+
+ // Send request reusing the existing connection.
+ ASSERT_NO_THROW(client->sendRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that the HTTP listener can't be started twice.
+TEST_F(HttpsListenerTest, startTwice) {
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ EXPECT_THROW(listener.start(), HttpListenerError);
+}
+
+// This test verifies that Bad Request status is returned when the request
+// is malformed.
+TEST_F(HttpsListenerTest, badRequest) {
+ // Content-Type is wrong. This should result in Bad Request status.
+ const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: foo\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ("HTTP/1.1 400 Bad Request\r\n"
+ "Content-Length: 40\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"result\": 400, \"text\": \"Bad Request\" }",
+ client->getResponse());
+}
+
+// This test verifies that NULL pointer can't be specified for the
+// HttpResponseCreatorFactory.
+TEST_F(HttpsListenerTest, invalidFactory) {
+ EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, server_context_,
+ HttpResponseCreatorFactoryPtr(),
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)),
+ HttpListenerError);
+}
+
+// This test verifies that the timeout of 0 can't be specified for the
+// Request Timeout.
+TEST_F(HttpsListenerTest, invalidRequestTimeout) {
+ EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, server_context_, factory_,
+ HttpListener::RequestTimeout(0),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)),
+ HttpListenerError);
+}
+
+// This test verifies that the timeout of 0 can't be specified for the
+// idle persistent connection timeout.
+TEST_F(HttpsListenerTest, invalidIdleTimeout) {
+ EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(0)),
+ HttpListenerError);
+}
+
+// This test verifies that listener can't be bound to the port to which
+// other server is bound.
+TEST_F(HttpsListenerTest, addressInUse) {
+ tcp::acceptor acceptor(io_service_.get_io_service());
+ // Use other port than SERVER_PORT to make sure that this TCP connection
+ // doesn't affect subsequent tests.
+ tcp::endpoint endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT + 1);
+ acceptor.open(endpoint.protocol());
+ acceptor.bind(endpoint);
+
+ // Listener should report an error when we try to start it because another
+ // acceptor is bound to that port and address.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT + 1, server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ EXPECT_THROW(listener.start(), HttpListenerError);
+}
+
+// This test verifies that HTTP Request Timeout status is returned as
+// expected when the read part of the request contains the HTTP
+// version number. The timeout response should contain the same
+// HTTP version number as the partial request.
+TEST_F(HttpsListenerTest, requestTimeoutHttpVersionFound) {
+ // The part of the request specified here is correct but it is not
+ // a complete request.
+ const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length:";
+
+ testRequestTimeout(request, HttpVersion::HTTP_11());
+}
+
+// This test verifies that HTTP Request Timeout status is returned as
+// expected when the read part of the request does not contain
+// the HTTP version number. The timeout response should by default
+// contain HTTP/1.0 version number.
+TEST_F(HttpsListenerTest, requestTimeoutHttpVersionNotFound) {
+ // The part of the request specified here is correct but it is not
+ // a complete request.
+ const std::string request = "POST /foo/bar HTTP";
+
+ testRequestTimeout(request, HttpVersion::HTTP_10());
+}
+
+// This test verifies that injecting length value greater than the
+// output buffer length to the socket write callback does not cause
+// an exception.
+TEST_F(HttpsListenerTest, tooLongWriteBuffer) {
+ testWriteBufferIssues<HttpConnectionLongWriteBuffer>();
+}
+
+// This test verifies that changing the transaction before calling
+// the socket write callback does not cause an exception.
+TEST_F(HttpsListenerTest, transactionChangeDuringWrite) {
+ testWriteBufferIssues<HttpConnectionTransactionChange>();
+}
+
+}
diff --git a/src/lib/http/tests/url_unittests.cc b/src/lib/http/tests/url_unittests.cc
new file mode 100644
index 0000000..f024e61
--- /dev/null
+++ b/src/lib/http/tests/url_unittests.cc
@@ -0,0 +1,115 @@
+// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <http/url.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc::http;
+
+namespace {
+
+/// @brief Test fixture class for @c Url class.
+class UrlTest : public ::testing::Test {
+public:
+
+ /// @brief Test valid URL.
+ ///
+ /// @param text_url URL is the text form.
+ /// @param expected_scheme Expected scheme.
+ /// @param expected_hostname Expected hostname.
+ /// @param expected_port Expected port.
+ /// @param expected_path Expected path.
+ void testValidUrl(const std::string& text_url,
+ const Url::Scheme& expected_scheme,
+ const std::string& expected_hostname,
+ const unsigned expected_port,
+ const std::string& expected_path) {
+ Url url(text_url);
+ ASSERT_TRUE(url.isValid()) << url.getErrorMessage();
+ EXPECT_EQ(expected_scheme, url.getScheme());
+ EXPECT_EQ(expected_hostname, url.getStrippedHostname());
+ EXPECT_EQ(expected_port, url.getPort());
+ EXPECT_EQ(expected_path, url.getPath());
+ }
+
+ /// @brief Test invalid URL.
+ ///
+ /// @param text_url URL is the text form.
+ void testInvalidUrl(const std::string& text_url) {
+ Url url(text_url);
+ EXPECT_FALSE(url.isValid());
+ }
+};
+
+// URL contains scheme and hostname.
+TEST_F(UrlTest, schemeHostname) {
+ testValidUrl("http://example.org", Url::HTTP, "example.org", 0, "");
+}
+
+// URL contains scheme, hostname and slash.
+TEST_F(UrlTest, schemeHostnameSlash) {
+ testValidUrl("http://example.org/", Url::HTTP, "example.org", 0, "/");
+}
+
+// URL contains scheme, IPv6 address and slash.
+TEST_F(UrlTest, schemeIPv6AddressSlash) {
+ testValidUrl("http://[2001:db8:1::100]/", Url::HTTP, "2001:db8:1::100", 0, "/");
+}
+
+// URL contains scheme, IPv4 address and slash.
+TEST_F(UrlTest, schemeIPv4AddressSlash) {
+ testValidUrl("http://192.0.2.2/", Url::HTTP, "192.0.2.2", 0, "/");
+}
+
+// URL contains scheme, hostname and path.
+TEST_F(UrlTest, schemeHostnamePath) {
+ testValidUrl("http://example.org/some/path", Url::HTTP, "example.org", 0,
+ "/some/path");
+}
+
+// URL contains scheme, hostname and port.
+TEST_F(UrlTest, schemeHostnamePort) {
+ testValidUrl("http://example.org:8080/", Url::HTTP, "example.org", 8080, "/");
+}
+
+// URL contains scheme, hostname, port and slash.
+TEST_F(UrlTest, schemeHostnamePortSlash) {
+ testValidUrl("http://example.org:8080/", Url::HTTP, "example.org", 8080, "/");
+}
+
+// URL contains scheme, IPv6 address and port.
+TEST_F(UrlTest, schemeIPv6AddressPort) {
+ testValidUrl("http://[2001:db8:1::1]:8080/", Url::HTTP, "2001:db8:1::1", 8080, "/");
+}
+
+// URL contains scheme, hostname, port and path.
+TEST_F(UrlTest, schemeHostnamePortPath) {
+ testValidUrl("http://example.org:8080/path/", Url::HTTP, "example.org", 8080,
+ "/path/");
+}
+
+// URL contains https scheme, hostname, port and path.
+TEST_F(UrlTest, secureSchemeHostnamePortPath) {
+ testValidUrl("https://example.org:8080/path/", Url::HTTPS, "example.org", 8080,
+ "/path/");
+}
+
+// Tests various invalid URLS.
+TEST_F(UrlTest, invalidUrls) {
+ testInvalidUrl("example.org");
+ testInvalidUrl("file://example.org");
+ testInvalidUrl("http//example.org");
+ testInvalidUrl("http:/example.org");
+ testInvalidUrl("http://");
+ testInvalidUrl("http://[]");
+ testInvalidUrl("http://[2001:db8:1::1");
+ testInvalidUrl("http://example.org:");
+ testInvalidUrl("http://example.org:abc");
+}
+
+}
diff --git a/src/lib/http/url.cc b/src/lib/http/url.cc
new file mode 100644
index 0000000..427b11a
--- /dev/null
+++ b/src/lib/http/url.cc
@@ -0,0 +1,223 @@
+// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <http/url.h>
+#include <boost/lexical_cast.hpp>
+#include <sstream>
+
+#include <iostream>
+
+namespace isc {
+namespace http {
+
+Url::Url(const std::string& url)
+ : url_(url), valid_(false), error_message_(), scheme_(Url::HTTPS),
+ hostname_(), port_(0), path_() {
+ parse();
+}
+
+bool
+Url::operator<(const Url& url) const {
+ return (url_ < url.rawUrl());
+}
+
+Url::Scheme
+Url::getScheme() const {
+ checkValid();
+ return (scheme_);
+}
+
+std::string
+Url::getHostname() const {
+ checkValid();
+ return (hostname_);
+}
+
+std::string
+Url::getStrippedHostname() const {
+ std::string hostname = getHostname();
+ if ((hostname.length() >= 2) && (hostname.at(0) == '[')) {
+ return (hostname.substr(1, hostname.length() - 2));
+ }
+
+ return (hostname);
+}
+
+unsigned
+Url::getPort() const {
+ checkValid();
+ return (port_);
+}
+
+std::string
+Url::getPath() const {
+ checkValid();
+ return (path_);
+}
+
+std::string
+Url::toText() const {
+ std::ostringstream s;
+ s << (getScheme() == HTTP ? "http" : "https");
+ s << "://" << getHostname();
+
+ if (getPort() != 0) {
+ s << ":" << getPort();
+ }
+
+ s << getPath();
+
+ return (s.str());
+}
+
+void
+Url::checkValid() const {
+ if (!isValid()) {
+ isc_throw(InvalidOperation, "invalid URL " << url_ << ": " << error_message_);
+ }
+}
+
+void
+Url::parse() {
+ valid_ = false;
+ error_message_.clear();
+ scheme_ = Url::HTTPS;
+ hostname_.clear();
+ port_ = 0;
+ path_.clear();
+
+ std::ostringstream error;
+
+ // Retrieve scheme
+ size_t offset = url_.find(":");
+ if ((offset == 0) || (offset == std::string::npos)) {
+ error << "url " << url_ << " lacks http or https scheme";
+ error_message_ = error.str();
+ return;
+ }
+
+ // Validate scheme.
+ std::string scheme = url_.substr(0, offset);
+ if (scheme == "http") {
+ scheme_ = Url::HTTP;
+
+ } else if (scheme == "https") {
+ scheme_ = Url::HTTPS;
+
+ } else {
+ error << "invalid scheme " << scheme << " in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+
+ // Colon and two slashes should follow the scheme
+ if (url_.substr(offset, 3) != "://") {
+ error << "expected :// after scheme in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+
+ // Move forward to hostname.
+ offset += 3;
+ if (offset >= url_.length()) {
+ error << "hostname missing in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+
+ size_t offset2 = 0;
+
+ // IPv6 address is specified within [ ].
+ if (url_.at(offset) == '[') {
+ offset2 = url_.find(']', offset);
+ if (offset2 == std::string::npos) {
+ error << "expected ] after IPv6 address in " << url_;
+ error_message_ = error.str();
+ return;
+
+ } else if (offset2 == offset + 1) {
+ error << "expected IPv6 address within [] in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+
+ // Move one character beyond the ].
+ ++offset2;
+
+ } else {
+ // There is a normal hostname or IPv4 address. It is terminated
+ // by the colon (for port number), a slash (if no port number) or
+ // goes up to the end of the URL.
+ offset2 = url_.find(":", offset);
+ if (offset2 == std::string::npos) {
+ offset2 = url_.find("/", offset);
+ if (offset2 == std::string::npos) {
+ // No port number and no slash.
+ offset2 = url_.length();
+ }
+ }
+ }
+
+ // Extract the hostname.
+ hostname_ = url_.substr(offset, offset2 - offset);
+
+ // If there is no port number and no path, simply return and mark the
+ // URL as valid.
+ if (offset2 == url_.length()) {
+ valid_ = true;
+ return;
+ }
+
+ // If there is a port number, we need to read it and convert to
+ // numeric value.
+ if (url_.at(offset2) == ':') {
+ if (offset2 == url_.length() - 1) {
+ error << "expected port number after : in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+ // Move to the port number.
+ ++offset2;
+
+ // Port number may be terminated by a slash or by the end of URL.
+ size_t slash_offset = url_.find('/', offset2);
+ std::string port_str;
+ if (slash_offset == std::string::npos) {
+ port_str = url_.substr(offset2);
+ } else {
+ port_str = url_.substr(offset2, slash_offset - offset2);
+ }
+
+ try {
+ // Try to convert the port number to numeric value.
+ port_ = boost::lexical_cast<unsigned>(port_str);
+
+ } catch (...) {
+ error << "invalid port number " << port_str << " in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+
+ // Go to the end of the port section.
+ offset2 = slash_offset;
+ }
+
+ // If there is anything left in the URL, we consider it a path.
+ if (offset2 != std::string::npos) {
+ path_ = url_.substr(offset2);
+ if (path_.empty()) {
+ path_ = "/";
+ }
+ }
+
+ valid_ = true;
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/url.h b/src/lib/http/url.h
new file mode 100644
index 0000000..d6ec9e2
--- /dev/null
+++ b/src/lib/http/url.h
@@ -0,0 +1,131 @@
+// Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef KEA_URL_H
+#define KEA_URL_H
+
+#include <asiolink/io_address.h>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Represents an URL.
+///
+/// It parses the provided URL and allows for retrieving the parts
+/// of it after parsing.
+class Url {
+public:
+
+ /// @brief Scheme: https or http.
+ enum Scheme {
+ HTTP,
+ HTTPS
+ };
+
+ /// @brief Constructor.
+ ///
+ /// Parses provided URL.
+ ///
+ /// @param url URL.
+ explicit Url(const std::string& url);
+
+ /// @brief compares URLs lexically.
+ ///
+ /// Both URLs are compared as text.
+ ///
+ /// @param url URL to be compared with
+ /// @return true if the other operator is larger (in lexical sense)
+ bool operator<(const Url& url) const;
+
+ /// @brief Checks if the URL is valid.
+ ///
+ /// @return true if the URL is valid, false otherwise.
+ bool isValid() const {
+ return (valid_);
+ }
+
+ /// @brief Returns parsing error message.
+ std::string getErrorMessage() const {
+ return (error_message_);
+ }
+
+ /// @brief Returns parsed scheme.
+ ///
+ /// @throw InvalidOperation if URL is invalid.
+ Scheme getScheme() const;
+
+ /// @brief Returns hostname stripped from [ ] characters surrounding
+ /// IPv6 address.
+ ///
+ /// @throw InvalidOperation if URL is invalid.
+ std::string getStrippedHostname() const;
+
+ /// @brief Returns port number.
+ ///
+ /// @return Port number or 0 if URL doesn't contain port number.
+ /// @throw InvalidOperation if URL is invalid.
+ unsigned getPort() const;
+
+ /// @brief Returns path.
+ ///
+ /// @return URL path
+ /// @throw InvalidOperation if URL is invalid.
+ std::string getPath() const;
+
+ /// @brief Returns textual representation of the URL.
+ ///
+ /// @return Text version of the URL.
+ std::string toText() const;
+
+ /// @brief Returns the raw, unparsed URL string.
+ ///
+ /// @return Unparsed URL string.
+ const std::string& rawUrl() const {
+ return (url_);
+ }
+
+private:
+ /// @brief Returns hostname.
+ ///
+ /// @throw InvalidOperation if URL is invalid.
+ std::string getHostname() const;
+
+ /// @brief Returns boolean value indicating if the URL is valid.
+ void checkValid() const;
+
+ /// @brief Parses URL.
+ ///
+ /// This method doesn't throw an exception. Call @c isValid to see
+ /// if the URL is valid.
+ void parse();
+
+ /// @brief Holds specified URL.
+ std::string url_;
+
+ /// @brief A flag indicating if the URL is valid.
+ bool valid_;
+
+ /// @brief Holds error message after parsing.
+ std::string error_message_;
+
+ /// @brief Parsed scheme.
+ Scheme scheme_;
+
+ /// @brief Parsed hostname.
+ std::string hostname_;
+
+ /// @brief Parsed port number.
+ unsigned port_;
+
+ /// @brief Parsed path.
+ std::string path_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // endif