summaryrefslogtreecommitdiffstats
path: root/src/lib/http/tests
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib/http/tests/Makefile.am76
-rw-r--r--src/lib/http/tests/Makefile.in1392
-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/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
28 files changed, 10977 insertions, 0 deletions
diff --git a/src/lib/http/tests/Makefile.am b/src/lib/http/tests/Makefile.am
new file mode 100644
index 0000000..5b2b2a0
--- /dev/null
+++ b/src/lib/http/tests/Makefile.am
@@ -0,0 +1,76 @@
+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_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..5c5bc7a
--- /dev/null
+++ b/src/lib/http/tests/Makefile.in
@@ -0,0 +1,1392 @@
+# 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_cpp20.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_netconf.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
+@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)
+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-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_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_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_NETCONF = @HAVE_NETCONF@
+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@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+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_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+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@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-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`
+
+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-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-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..ef04c28
--- /dev/null
+++ b/src/lib/http/tests/client_mt_unittests.cc
@@ -0,0 +1,1042 @@
+// Copyright (C) 2021-2023 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 ? true : false,
+ num_threads, true)));
+ 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());
+ }
+
+ // 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);
+ }
+ }
+ }
+
+ client_->start();
+
+ // Verify the pool size and number of threads are as expected.
+ ASSERT_EQ(client_->getThreadPoolSize(), num_threads);
+ ASSERT_EQ(client_->getThreadCount(), num_threads);
+
+ // 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_, true, num_threads, true)));
+ ASSERT_TRUE(client_);
+
+ // 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);
+ }
+ }
+ }
+
+ client_->start();
+
+ // 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);
+
+ 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) {
+ HttpClientPtr client;
+
+ // Value of 0 for thread_pool_size means single-threaded.
+ ASSERT_NO_THROW_LOG(client.reset(new HttpClient(io_service_, false)));
+ 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_, false, 1)), InvalidOperation,
+ "HttpClient thread_pool_size must be zero "
+ "when Kea core multi-threading is disabled");
+ ASSERT_FALSE(client);
+
+ // Multi-threaded construction should work now.
+ ASSERT_NO_THROW_LOG(client.reset(new HttpClient(io_service_, true, 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_, true, 3)));
+
+ // Make sure destruction doesn't throw.
+ ASSERT_NO_THROW_LOG(client.reset());
+}
+
+// Verifies we can construct with deferred start.
+TEST_F(MultiThreadingHttpClientTest, deferredStart) {
+ HttpClientPtr client;
+ size_t thread_pool_size = 3;
+
+ // Create MT client with deferred start.
+ ASSERT_NO_THROW_LOG(client.reset(new HttpClient(io_service_, true, 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) {
+ HttpClientPtr client;
+ size_t thread_pool_size = 3;
+
+ // Create MT client with instant start.
+ ASSERT_NO_THROW_LOG(client.reset(new HttpClient(io_service_, true, 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/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..2c18090
--- /dev/null
+++ b/src/lib/http/tests/request_unittests.cc
@@ -0,0 +1,422 @@
+// Copyright (C) 2016-2023 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 = 0;
+ 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..2ab76f3
--- /dev/null
+++ b/src/lib/http/tests/server_client_unittests.cc
@@ -0,0 +1,2041 @@
+// Copyright (C) 2017-2023 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_, false);
+ 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_, false);
+
+ // 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_, false);
+
+ // 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_, false);
+
+ // 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_, false);
+
+ // 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_, false);
+
+ // 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_, false);
+
+ // 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_, false);
+
+ // 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_, false);
+
+ // 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_, false);
+ 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_, false);
+ 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..fc999f5
--- /dev/null
+++ b/src/lib/http/tests/tls_client_unittests.cc
@@ -0,0 +1,1398 @@
+// Copyright (C) 2017-2023 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_, false);
+ 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_, false);
+
+ // 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_, false);
+
+ // 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_, false);
+
+ // 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_, false);
+
+ // 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_, false);
+
+ // 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_, false);
+
+ // 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_, false);
+
+ // 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_, false);
+
+ // 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_, false);
+ 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_, false);
+ 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");
+}
+
+}