diff --git a/CMakeLists.txt b/CMakeLists.txt
index c5bdf21a9f15bdcfd30e3eb964ebc07041986145..b83c9c115eed47bdaaaa33b0bb3072e8a929cbcc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,10 +16,10 @@ option (OPENDHT_SYSTEMD "Install systemd module" OFF)
 option (OPENDHT_ARGON2 "Use included argon2 sources" OFF)
 option (OPENDHT_LTO "Build with LTO" OFF)
 option (OPENDHT_SANITIZE "Build with address sanitizer and stack protector" OFF)
-option (OPENDHT_PROXY_SERVER "Enable DHT proxy server, use Restbed and jsoncpp" OFF)
+option (OPENDHT_PROXY_SERVER "Enable DHT proxy server, use Restinio and jsoncpp" OFF)
 option (OPENDHT_PUSH_NOTIFICATIONS "Enable push notifications support" OFF)
 option (OPENDHT_PROXY_SERVER_IDENTITY "Allow clients to use the node identity" OFF)
-option (OPENDHT_PROXY_CLIENT "Enable DHT proxy client, use Restbed and jsoncpp" OFF)
+option (OPENDHT_PROXY_CLIENT "Enable DHT proxy client, use Restinio and jsoncpp" OFF)
 option (OPENDHT_INDEX "Build DHT indexation feature" OFF)
 option (OPENDHT_TESTS "Add unit tests executable" OFF)
 
@@ -58,14 +58,20 @@ if (Jsoncpp_FOUND)
 endif()
 
 if (OPENDHT_PROXY_SERVER OR OPENDHT_PROXY_CLIENT)
-    find_package(Restbed REQUIRED)
+    find_package(Restinio REQUIRED)
+    if (Restinio_FOUND)
+        find_library(FMT_LIBRARY fmt)
+        add_library(fmt SHARED IMPORTED)
+        find_library(HTTP_PARSER_LIBRARY http_parser)
+        add_library(http_parser SHARED IMPORTED)
+    endif()
     if (NOT Jsoncpp_FOUND)
         message(SEND_ERROR "Jsoncpp is required for DHT proxy support")
     endif()
 endif()
 
 # Build flags
-set (CMAKE_CXX_STANDARD 11)
+set (CMAKE_CXX_STANDARD 14)
 set (CMAKE_CXX_STANDARD_REQUIRED on)
 set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-return-type -Wall -Wextra -Wnon-virtual-dtor -pedantic-errors -fvisibility=hidden")
 if (OPENDHT_SANITIZE)
@@ -100,8 +106,8 @@ endif ()
 if (Nettle_INCLUDE_DIRS)
     include_directories (SYSTEM "${Nettle_INCLUDE_DIRS}")
 endif ()
-if (Restbed_INCLUDE_DIR)
-    include_directories (SYSTEM "${Restbed_INCLUDE_DIR}")
+if (Restinio_INCLUDE_DIR)
+    include_directories (SYSTEM "${Restinio_INCLUDE_DIR}")
 endif ()
 if (Jsoncpp_INCLUDE_DIRS)
     include_directories (SYSTEM "${Jsoncpp_INCLUDE_DIRS}")
@@ -153,6 +159,7 @@ list (APPEND opendht_SOURCES
     src/peer_discovery.cpp
     src/network_utils.cpp
     src/thread_pool.cpp
+    src/http.cpp
 )
 
 list (APPEND opendht_HEADERS
@@ -179,6 +186,7 @@ list (APPEND opendht_HEADERS
     include/opendht/peer_discovery.h
     include/opendht/thread_pool.h
     include/opendht/network_utils.h
+    include/opendht/http.h
     include/opendht.h
 )
 
@@ -255,8 +263,9 @@ if (OPENDHT_STATIC)
         target_include_directories(opendht-static SYSTEM PRIVATE ${argon2_INCLUDE_DIRS})
     endif ()
     target_link_libraries(opendht-static
-        PRIVATE ${Restbed_LIBRARY} ${argon2_LIBRARIES}
-        PUBLIC ${CMAKE_THREAD_LIBS_INIT} ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES} ${Jsoncpp_LIBRARIES})
+        PRIVATE  ${argon2_LIBRARIES}
+        PUBLIC ${CMAKE_THREAD_LIBS_INIT} ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES}
+               ${Jsoncpp_LIBRARIES} ${FMT_LIBRARY} ${HTTP_PARSER_LIBRARY})
     install (TARGETS opendht-static DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht)
 endif ()
 
@@ -274,7 +283,12 @@ if (OPENDHT_SHARED)
         target_link_libraries(opendht PRIVATE ${argon2_LIBRARIES})
         target_include_directories(opendht SYSTEM PRIVATE ${argon2_INCLUDE_DIRS})
     endif ()
-    target_link_libraries(opendht PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES} ${Restbed_LIBRARY} ${Jsoncpp_LIBRARIES})
+    target_link_libraries(opendht
+        PUBLIC ${CMAKE_THREAD_LIBS_INIT}
+        PRIVATE ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES}
+                ${Jsoncpp_LIBRARIES}
+                ${FMT_LIBRARY} ${HTTP_PARSER_LIBRARY})
+
     install (TARGETS opendht DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht)
 endif ()
 
diff --git a/README.md b/README.md
index bacb0950311fe53f213e37cb53e1ec918d77ba68..59f0ed1138d1690f5260c2763b4637b004d65ff7 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
     <a id="user-content-opendht-" class="anchor" href="/savoirfairelinux/opendht/blob/master/README.md#opendht-" aria-hidden="true"></a>OpenDHT
 </h1>
 
-A lightweight C++11 Distributed Hash Table implementation.
+A lightweight C++14 Distributed Hash Table implementation.
 
 OpenDHT provides an easy to use distributed in-memory data store.
 Every node in the network can read and write values to the store.
@@ -14,7 +14,7 @@ Values are distributed over the network, with redundancy.
  * High resilience to network disruption
  * Public key cryptography layer providing optional data signature and encryption (using GnuTLS)
  * IPv4 and IPv6 support
- * Clean and powerful C++11 map API
+ * Clean and powerful C++14 map API
  * Python 3 bindings
  * REST API
 
@@ -27,7 +27,7 @@ Build instructions: <https://github.com/savoirfairelinux/opendht/wiki/Build-the-
 
 #### How-to build a simple client app
 ```bash
-g++ main.cpp -std=c++11 -lopendht -lgnutls
+g++ main.cpp -std=c++14 -lopendht -lgnutls
 ```
 
 ## Examples
@@ -96,7 +96,7 @@ for r in results:
 - msgpack-c 1.2+, used for data serialization.
 - GnuTLS 3.3+, used for cryptographic operations.
 - Nettle 2.4+, a GnuTLS dependency for crypto.
-- (optional) restbed used for the REST API. commit fb84213e170bc171fecd825a8e47ed9f881a12cd (https://github.com/AmarOk1412/restbed/tree/async_read_until)
+- (optional) restinio used for the REST API.
 - (optional) jsoncpp 1.7.4-3+, used for the REST API.
 - Build tested with GCC 5.2+ (GNU/Linux, Windows with MinGW), Clang/LLVM (GNU/Linux, Android, macOS, iOS).
 - Build tested with Microsoft Visual Studio 2015
diff --git a/cmake/FindRestbed.cmake b/cmake/FindRestbed.cmake
deleted file mode 100644
index bdf58b818e7aad85edf88384bc161072503cd9d5..0000000000000000000000000000000000000000
--- a/cmake/FindRestbed.cmake
+++ /dev/null
@@ -1,16 +0,0 @@
-if(NOT Restbed_FOUND)
-    find_path (Restbed_INCLUDE_DIR restbed
-               HINTS
-               "/usr/include"
-               "/usr/local/include"
-               "/opt/local/include")
-    find_library(Restbed_LIBRARY restbed
-                 HINTS ${Restbed_ROOT_DIR} PATH_SUFFIXES lib)
-    include(FindPackageHandleStandardArgs)
-    find_package_handle_standard_args(Restbed DEFAULT_MSG Restbed_LIBRARY Restbed_INCLUDE_DIR)
-    if (Restbed_INCLUDE_DIR)
-        set(Restbed_FOUND TRUE)
-        set(Restbed_LIBRARIES ${Restbed_LIBRARY})
-        set(Restbed_INCLUDE_DIRS ${Restbed_INCLUDE_DIR})
-    endif()
-endif()
diff --git a/cmake/FindRestinio.cmake b/cmake/FindRestinio.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..a0887b3f33f4dd930e2d5d13aa027904f59232ce
--- /dev/null
+++ b/cmake/FindRestinio.cmake
@@ -0,0 +1,14 @@
+# header-only does not produce a library
+if(NOT Restinio_FOUND)
+    find_path (Restinio_INCLUDE_DIR restinio
+               HINTS
+               "/usr/include"
+               "/usr/local/include"
+               "/opt/local/include")
+    include(FindPackageHandleStandardArgs)
+    find_package_handle_standard_args(Restinio DEFAULT_MSG Restinio_INCLUDE_DIR)
+    if (Restinio_INCLUDE_DIR)
+        set(Restinio_FOUND TRUE)
+        set(Restinio_INCLUDE_DIRS ${Restinio_INCLUDE_DIR})
+    endif()
+endif()
diff --git a/configure.ac b/configure.ac
index 592365a6a9e1aca3f9fef005efd1201bc903a1dd..ffd7522051d4a0242d50bca1e15c1be67f0a3219 100644
--- a/configure.ac
+++ b/configure.ac
@@ -142,8 +142,8 @@ AS_IF([test "x$have_jsoncpp" = "xyes"], [
 ])
 
 AM_COND_IF([PROXY_CLIENT_OR_SERVER], [
-	AC_CHECK_LIB(restbed, exit,, AC_MSG_ERROR([Missing restbed files]))
-	LDFLAGS="${LDFLAGS} -lrestbed"
+	#AC_CHECK_LIB(<libname>, exit,, AC_MSG_ERROR([Missing <libname> files]))
+	#LDFLAGS="${LDFLAGS} -l<libname>"
 ])
 
 CXXFLAGS="${CXXFLAGS} -DMSGPACK_DISABLE_LEGACY_NIL -DMSGPACK_DISABLE_LEGACY_CONVERT"
diff --git a/docker/DockerfileDeps b/docker/DockerfileDeps
index 0af9f19684dee39c52a5de22c9e61f289f8fed0a..f6724bba226f23d102d6cdd6a9007a1194dd1643 100644
--- a/docker/DockerfileDeps
+++ b/docker/DockerfileDeps
@@ -1,18 +1,45 @@
 FROM ubuntu:16.04
 MAINTAINER Adrien Béraud <adrien.beraud@savoirfairelinux.com>
-RUN apt-get update && apt-get install -y build-essential cmake git wget libncurses5-dev libreadline-dev nettle-dev libgnutls28-dev libuv1-dev  cython3 python3-dev libcppunit-dev libjsoncpp-dev libasio-dev libssl-dev python3-setuptools && apt-get clean
+RUN apt-get update && apt-get install -y \
+        build-essential cmake git wget libncurses5-dev libreadline-dev nettle-dev \
+        libgnutls28-dev libuv1-dev cython3 python3-dev libcppunit-dev libjsoncpp-dev \
+        libasio-dev libssl-dev python3-setuptools \
+    && apt-get clean
 
-# build restbed from sources
-RUN git clone --recursive https://github.com/corvusoft/restbed.git \
-	&& cd restbed && mkdir build && cd build \
-	&& cmake -DBUILD_TESTS=NO -DBUILD_EXAMPLES=NO -DBUILD_SSL=NO -DBUILD_SHARED=YES -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib .. \
-	&& make -j8 install \
-	&& cd .. && rm -rf restbed
+#patch for https://github.com/Stiffstream/restinio-conan-example/issues/2
+RUN apt-get update && apt-get install -y \
+        python3-pip libasio-dev
+RUN pip3 install --upgrade cmake
+#install conan & add restinio remotes
+RUN pip3 install conan && \
+    conan remote add stiffstream https://api.bintray.com/conan/stiffstream/public && \
+    conan remote add public-conan https://api.bintray.com/conan/bincrafters/public-conan
+#setup restinio docker project
+RUN mkdir restinio-conan
+COPY conan/restinio/conanfile.txt restinio-conan/conanfile.txt
+COPY conan/restinio/conanfile.py restinio-conan/conanfile.py
+#build restinio from source
+RUN echo "*** Installing RESTinio & dependencies ***" \
+    && cd restinio-conan \
+    && conan source .  \
+    && conan install -o restinio:boost_libs=none --build=missing . \
+    && conan package . -pf /usr/local \
+    && cd ../ && rm -rf restinio*
+#installing dependencies
+RUN echo "*** Installing asio & fmt dependencies ***" \
+    && cp -r ~/.conan/data/asio/*/bincrafters/stable/package/*/include/* /usr/local/include/ \
+    && cp -r ~/.conan/data/fmt/*/bincrafters/stable/package/*/lib/* /usr/local/lib/ \
+    && cp -r ~/.conan/data/fmt/*/bincrafters/stable/package/*/include/* /usr/local/include/
+#build http_parser fork
+RUN echo "*** Building http_parser dependency for custom HTTP methods ***" \
+    && git clone https://github.com/eao197/http-parser.git \
+    && cd http-parser && make -j8 && make install PREFIX=/usr \
+    && cd ../ && rm -rf restinio-conan/
 
 #build msgpack from source
 RUN wget https://github.com/msgpack/msgpack-c/releases/download/cpp-2.1.5/msgpack-2.1.5.tar.gz \
-	&& tar -xzf msgpack-2.1.5.tar.gz \
-	&& cd msgpack-2.1.5 && mkdir build && cd build \
-	&& cmake -DMSGPACK_CXX11=ON -DMSGPACK_BUILD_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX=/usr .. \
-	&& make -j8 && make install \
-	&& cd ../.. && rm -rf msgpack-2.1.5 msgpack-2.1.5.tar.gz
+    && tar -xzf msgpack-2.1.5.tar.gz \
+    && cd msgpack-2.1.5 && mkdir build && cd build \
+    && cmake -DMSGPACK_CXX11=ON -DMSGPACK_BUILD_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX=/usr .. \
+    && make -j8 && make install \
+    && cd ../.. && rm -rf msgpack-2.1.5 msgpack-2.1.5.tar.gz
diff --git a/docker/DockerfileDepsLlvm b/docker/DockerfileDepsLlvm
index e407f5528a4076001cc54bd5b592cee0b2a80f1a..0064b774f22d7a8e0796f35e11edf3d34c3f20cc 100644
--- a/docker/DockerfileDepsLlvm
+++ b/docker/DockerfileDepsLlvm
@@ -1,23 +1,46 @@
 FROM ubuntu:16.04
 MAINTAINER Adrien Béraud <adrien.beraud@savoirfairelinux.com>
 RUN apt-get update \
-	&& apt-get install -y llvm llvm-dev clang make cmake git wget libncurses5-dev libreadline-dev nettle-dev libgnutls28-dev libuv1-dev libmsgpack-dev libjsoncpp-dev libasio-dev cython3 python3-dev python3-setuptools libcppunit-dev \
-	&& apt-get remove -y gcc g++ && apt-get autoremove -y && apt-get clean
+    && apt-get install -y llvm llvm-dev clang make cmake git wget libncurses5-dev libreadline-dev nettle-dev libgnutls28-dev libuv1-dev libmsgpack-dev libjsoncpp-dev libasio-dev cython3 python3-dev python3-setuptools libcppunit-dev python3-pip \
+    && apt-get remove -y gcc g++ && apt-get autoremove -y && apt-get clean
 
 ENV CC cc
 ENV CXX c++
 
-# build restbed from sources
-RUN git clone --recursive https://github.com/corvusoft/restbed.git \
-	&& cd restbed && mkdir build && cd build \
-	&& cmake -DBUILD_TESTS=NO -DBUILD_EXAMPLES=NO -DBUILD_SSL=NO -DBUILD_SHARED=YES -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib .. \
-	&& make -j8 install \
-	&& cd .. && rm -rf restbed
+#patch for https://github.com/Stiffstream/restinio-conan-example/issues/2
+RUN apt-get update && apt-get install -y \
+        python3-pip libasio-dev
+RUN pip3 install --upgrade cmake
+#install conan & add restinio remotes
+RUN pip3 install conan && \
+    conan remote add stiffstream https://api.bintray.com/conan/stiffstream/public && \
+    conan remote add public-conan https://api.bintray.com/conan/bincrafters/public-conan
+#setup restinio docker project
+RUN mkdir restinio-conan
+COPY conan/restinio/conanfile.txt restinio-conan/conanfile.txt
+COPY conan/restinio/conanfile.py restinio-conan/conanfile.py
+#build restinio from source
+RUN echo "*** Installing RESTinio & dependencies ***" \
+    && cd restinio-conan \
+    && conan source .  \
+    && conan install -o restinio:boost_libs=none --build=missing . \
+    && conan package . -pf /usr/local \
+    && cd ../ && rm -rf restinio*
+#installing dependencies
+RUN echo "*** Installing asio & fmt dependencies ***" \
+    && cp -r ~/.conan/data/asio/*/bincrafters/stable/package/*/include/* /usr/local/include/ \
+    && cp -r ~/.conan/data/fmt/*/bincrafters/stable/package/*/lib/* /usr/local/lib/ \
+    && cp -r ~/.conan/data/fmt/*/bincrafters/stable/package/*/include/* /usr/local/include/
+#build http_parser fork
+RUN echo "*** Building http_parser dependency for custom HTTP methods ***" \
+    && git clone https://github.com/eao197/http-parser.git \
+    && cd http-parser && make -j8 && make install PREFIX=/usr \
+    && cd ../ && rm -rf restinio-conan/
 
 #build msgpack from source
 RUN wget https://github.com/msgpack/msgpack-c/releases/download/cpp-2.1.5/msgpack-2.1.5.tar.gz \
-	&& tar -xzf msgpack-2.1.5.tar.gz \
-	&& cd msgpack-2.1.5 && mkdir build && cd build \
-	&& cmake -DMSGPACK_CXX11=ON -DMSGPACK_BUILD_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX=/usr .. \
-	&& make -j8 && make install \
-	&& cd ../.. && rm -rf msgpack-2.1.5 msgpack-2.1.5.tar.gz
+    && tar -xzf msgpack-2.1.5.tar.gz \
+    && cd msgpack-2.1.5 && mkdir build && cd build \
+    && cmake -DMSGPACK_CXX11=ON -DMSGPACK_BUILD_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX=/usr .. \
+    && make -j8 && make install \
+    && cd ../.. && rm -rf msgpack-2.1.5 msgpack-2.1.5.tar.gz
diff --git a/docker/conan/restinio/conanfile.py b/docker/conan/restinio/conanfile.py
new file mode 100644
index 0000000000000000000000000000000000000000..a434c5ac7d265bf24f9df2c8ded33358924427aa
--- /dev/null
+++ b/docker/conan/restinio/conanfile.py
@@ -0,0 +1,59 @@
+from conans import ConanFile, CMake, tools
+import os
+
+
+class SobjectizerConan(ConanFile):
+    name = "restinio"
+    version = "0.5.1"
+
+    license = "BSD-3-Clause"
+    url = "https://github.com/Stiffstream/restinio-conan"
+
+    description = (
+            "RESTinio is a header-only C++14 library that gives you "
+            "an embedded HTTP/Websocket server."
+    )
+
+    settings = "os", "compiler", "build_type", "arch"
+    options = {'boost_libs': ['none', 'static', 'shared']}
+    default_options = {'boost_libs': 'none'}
+    generators = "cmake"
+    source_subfolder = "restinio"
+    build_policy = "missing"
+
+    def requirements(self):
+        self.requires.add("http-parser/2.8.1@bincrafters/stable")
+        self.requires.add("fmt/5.3.0@bincrafters/stable")
+
+        if self.options.boost_libs == "none":
+            self.requires.add("asio/1.12.2@bincrafters/stable")
+        else:
+            self.requires.add("boost/1.69.0@conan/stable")
+            if self.options.boost_libs == "shared":
+                self.options["boost"].shared = True
+            else:
+                self.options["boost"].shared = False
+
+    def source(self):
+        source_url = "https://bitbucket.org/sobjectizerteam/restinio/downloads"
+        tools.get("{0}/restinio-{1}.zip".format(source_url, self.version))
+        extracted_dir = "restinio-" + self.version
+        os.rename(extracted_dir, self.source_subfolder)
+
+    def _configure_cmake(self):
+        cmake = CMake(self)
+        cmake.definitions['RESTINIO_INSTALL'] = True
+        cmake.definitions['RESTINIO_FIND_DEPS'] = False
+        cmake.definitions['RESTINIO_USE_BOOST_ASIO'] = self.options.boost_libs
+        cmake.configure(source_folder = self.source_subfolder + "/dev/restinio")
+        return cmake
+
+    def package(self):
+        cmake = self._configure_cmake()
+        self.output.info(cmake.definitions)
+        cmake.install()
+
+    def package_info(self):
+        self.info.header_only()
+        if self.options.boost_libs != "none":
+            self.cpp_info.defines.append("RESTINIO_USE_BOOST_ASIO")
diff --git a/docker/conan/restinio/conanfile.txt b/docker/conan/restinio/conanfile.txt
new file mode 100644
index 0000000000000000000000000000000000000000..472fd614635649ab6541e2c9aa28ba1474921424
--- /dev/null
+++ b/docker/conan/restinio/conanfile.txt
@@ -0,0 +1,11 @@
+[requires]
+restinio/0.5.1@stiffstream/stable
+
+[generators]
+cmake
+
+[options]
+
+[imports]
+bin, *.dll -> ./bin # Copies all dll files from packages bin folder to my "bin" folder
+lib, *.dylib* -> ./bin # Copies all dylib files from packages lib folder to my "bin" folder
diff --git a/include/opendht/dht_proxy_client.h b/include/opendht/dht_proxy_client.h
index 5dbd8eb9c9638cb3f624cad18780f2951ce47060..dbaedafcb3de1397ed995a4b7b7e200629189974 100644
--- a/include/opendht/dht_proxy_client.h
+++ b/include/opendht/dht_proxy_client.h
@@ -2,6 +2,7 @@
  *  Copyright (C) 2016-2019 Savoir-faire Linux Inc.
  *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
  *          Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *          Vsevolod Ivanov <vsevolod.ivanov@savoirfairelinux.com>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -20,23 +21,30 @@
 #pragma once
 
 #include <functional>
-#include <thread>
 #include <mutex>
 
 #include "callbacks.h"
 #include "def.h"
 #include "dht_interface.h"
-#include "scheduler.h"
 #include "proxy.h"
 
-namespace restbed {
-    class Request;
-}
+#include <restinio/all.hpp>
+#include <http_parser.h>
+#include <json/json.h>
+#include "http.h"
+
+#include <chrono>
+#include <vector>
+#include <functional>
 
 namespace Json {
     class Value;
 }
 
+namespace http {
+    class Client;
+}
+
 namespace dht {
 
 class OPENDHT_PUBLIC DhtProxyClient final : public DhtInterface {
@@ -44,7 +52,11 @@ public:
 
     DhtProxyClient();
 
-    explicit DhtProxyClient(std::function<void()> loopSignal, const std::string& serverHost, const std::string& pushClientId = "", const Logger& = {});
+    explicit DhtProxyClient(std::function<void()> loopSignal, const std::string& serverHost,
+                            const std::string& pushClientId = "",
+                            std::shared_ptr<dht::Logger> logger = {});
+
+    restinio::http_header_fields_t initHeaderFields();
 
     virtual void setPushNotificationToken(const std::string& token) {
 #ifdef OPENDHT_PUSH_NOTIFICATIONS
@@ -172,6 +184,10 @@ public:
     virtual size_t listen(const InfoHash& key, GetCallbackSimple cb, Value::Filter f={}, Where w={}) {
         return listen(key, bindGetCb(cb), std::forward<Value::Filter>(f), std::forward<Where>(w));
     }
+    /*
+     * This function relies on the cache implementation.
+     * It means that there are no true cancel here, it keeps the caching in higher priority.
+     */
     virtual bool cancelListen(const InfoHash& key, size_t token);
 
     /**
@@ -185,7 +201,6 @@ public:
         return periodic(buf, buflen, SockAddr(from, fromlen));
     }
 
-
     /**
      * Similar to Dht::get, but sends a Query to filter data remotely.
      * @param key the key for which to query data for.
@@ -269,6 +284,8 @@ private:
      */
     struct InfoState;
     void getProxyInfos();
+    void handleProxyStatus(const asio::error_code &ec,
+                           std::shared_ptr<InfoState> infoState);
     void onProxyInfos(const Json::Value& val, sa_family_t family);
     SockAddr parsePublicAddress(const Json::Value& val);
 
@@ -282,10 +299,13 @@ private:
         SUBSCRIBE,
         RESUBSCRIBE,
     };
-    void sendListen(const std::shared_ptr<restbed::Request> &request,
-                    const ValueCallback &, const Value::Filter &filter,
-                    const Sp<ListenState> &state,
-                    ListenMethod method = ListenMethod::LISTEN);
+    /**
+     * Send Listen with httpClient_
+     * Return a Connection Id
+     */
+    uint16_t sendListen(const restinio::http_request_header_t header,
+                        const ValueCallback &cb, const Value::Filter &filter,
+                        const Sp<ListenState> &state, ListenMethod method = ListenMethod::LISTEN);
 
     void doPut(const InfoHash&, Sp<Value>, DoneCallback, time_point created, bool permanent);
 
@@ -303,8 +323,24 @@ private:
     void cancelAllOperations();
 
     std::string serverHost_;
+    std::string serverHostIp_;
+    uint16_t serverHostPort_;
     std::string pushClientId_;
 
+    /*
+     * ASIO I/O Context for sockets in httpClient_
+     * Note: Each context is used in one thread only
+     */
+    asio::io_context httpContext_;
+    /*
+     * http::Client instance used on http io_context
+     */
+    std::unique_ptr<http::Client> httpClient_;
+    /*
+     * Thread for executing the http io_context.run() blocking call
+     */
+    std::thread httpClientThread_;
+
     mutable std::mutex lockCurrentProxyInfos_;
     NodeStatus statusIpv4_ {NodeStatus::Disconnected};
     NodeStatus statusIpv6_ {NodeStatus::Disconnected};
@@ -328,34 +364,22 @@ private:
     std::map<InfoHash, ProxySearch> searches_;
     mutable std::mutex searchLock_;
 
-    /**
-     * Store current put and get requests.
-     */
-    struct Operation
-    {
-        std::shared_ptr<restbed::Request> req;
-        std::thread thread;
-        std::shared_ptr<std::atomic_bool> finished;
-    };
-    std::vector<Operation> operations_;
-    std::mutex lockOperations_;
     /**
      * Callbacks should be executed in the main thread.
      */
     std::vector<std::function<void()>> callbacks_;
-    std::mutex lockCallbacks;
+    std::mutex lockCallbacks_;
 
     Sp<InfoState> infoState_;
-    std::thread statusThread_;
+    Sp<asio::steady_timer> statusTimer_;
     mutable std::mutex statusLock_;
 
-    Scheduler scheduler;
     /**
      * Retrieve if we can connect to the proxy (update statusIpvX_)
      */
     void confirmProxy();
-    Sp<Scheduler::Job> nextProxyConfirmation {};
-    Sp<Scheduler::Job> listenerRestart {};
+    Sp<asio::steady_timer> nextProxyConfirmationTimer_;
+    Sp<asio::steady_timer> listenerRestartTimer_;
 
     /**
      * Relaunch LISTEN requests if the client disconnect/reconnect.
@@ -377,11 +401,13 @@ private:
     const std::function<void()> loopSignal_;
 
 #ifdef OPENDHT_PUSH_NOTIFICATIONS
-    void fillBody(std::shared_ptr<restbed::Request> request, bool resubscribe);
+    std::string fillBody(bool resubscribe);
     void getPushRequest(Json::Value&) const;
 #endif // OPENDHT_PUSH_NOTIFICATIONS
 
     std::atomic_bool isDestroying_ {false};
+
+    std::shared_ptr<dht::Logger> logger_;
 };
 
 }
diff --git a/include/opendht/dht_proxy_server.h b/include/opendht/dht_proxy_server.h
index c48d9778948ad56734c9f739e9275adcaeb01724..dc6019e67d53f7a1a04d148bd127b70fe6706923 100644
--- a/include/opendht/dht_proxy_server.h
+++ b/include/opendht/dht_proxy_server.h
@@ -2,6 +2,7 @@
  *  Copyright (C) 2017-2019 Savoir-faire Linux Inc.
  *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
  *          Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *          Vsevolod Ivanov <vsevolod.ivanov@savoirfairelinux.com>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -26,16 +27,43 @@
 #include "scheduler.h"
 #include "sockaddr.h"
 #include "value.h"
+#include "dht_proxy_client.h"
 
-#include <thread>
 #include <memory>
 #include <mutex>
-#include <restbed>
+#include <restinio/all.hpp>
+#include "http.h"
 
 #ifdef OPENDHT_JSONCPP
 #include <json/json.h>
 #endif
 
+namespace http {
+    class Client;
+    class opendht_logger_t;
+    struct ListenerSession;
+    class ConnectionListener;
+}
+
+namespace restinio {
+    class opendht_logger_t;
+    struct custom_http_methods_t;
+}
+
+using RestRouter = restinio::router::express_router_t<>;
+struct RestRouterTraits : public restinio::default_traits_t
+{
+    using timer_manager_t = restinio::asio_timer_manager_t;
+    using http_methods_mapper_t = restinio::custom_http_methods_t;
+    using logger_t = restinio::opendht_logger_t;
+    using request_handler_t = RestRouter;
+    using connection_state_listener_t = http::ConnectionListener;
+};
+using ServerSettings = restinio::run_on_this_thread_settings_t<RestRouterTraits>;
+using RequestStatus = restinio::request_handling_status_t;
+using ResponseByParts = restinio::chunked_output_t;
+using ResponseByPartsBuilder = restinio::response_builder_t<ResponseByParts>;
+
 namespace Json {
     class Value;
 }
@@ -43,7 +71,6 @@ namespace Json {
 namespace dht {
 
 class DhtRunner;
-class ThreadPool;
 
 /**
  * Describes the REST API
@@ -59,7 +86,9 @@ public:
      * @note if the server fails to start (if port is already used or reserved),
      * it will fails silently
      */
-    DhtProxyServer(std::shared_ptr<DhtRunner> dht, in_port_t port = 8000, const std::string& pushServer = "");
+    DhtProxyServer(std::shared_ptr<DhtRunner> dht, in_port_t port = 8000,
+                   const std::string& pushServer = "",
+                   std::shared_ptr<dht::Logger> logger = {});
     virtual ~DhtProxyServer();
 
     DhtProxyServer(const DhtProxyServer& other) = delete;
@@ -123,6 +152,14 @@ public:
     void stop();
 
 private:
+    template <typename HttpResponse>
+    HttpResponse initHttpResponse(HttpResponse response) const;
+
+    ServerSettings makeHttpServerSettings(
+        const unsigned int max_pipelined_requests = 16);
+
+    std::unique_ptr<RestRouter> createRestRouter();
+
     /**
      * Return the PublicKey id, the node id and node stats
      * Method: GET "/"
@@ -130,7 +167,8 @@ private:
      * On error: HTTP 503, body: {"err":"xxxx"}
      * @param session
      */
-    void getNodeInfo(const std::shared_ptr<restbed::Session>& session) const;
+    RequestStatus getNodeInfo(restinio::request_handle_t request,
+                               restinio::router::route_params_t params) const;
 
     /**
      * Return ServerStats in JSON format
@@ -138,7 +176,8 @@ private:
      * Result: HTTP 200, body: Node infos in JSON format
      * @param session
      */
-    void getStats(const std::shared_ptr<restbed::Session>& session) const;
+    RequestStatus getStats(restinio::request_handle_t request,
+                           restinio::router::route_params_t params);
 
     /**
      * Return Values of an infoHash
@@ -150,7 +189,8 @@ private:
      * On error: HTTP 503, body: {"err":"xxxx"}
      * @param session
      */
-    void get(const std::shared_ptr<restbed::Session>& session) const;
+    RequestStatus get(restinio::request_handle_t request,
+                       restinio::router::route_params_t params);
 
     /**
      * Listen incoming Values of an infoHash.
@@ -162,7 +202,8 @@ private:
      * On error: HTTP 503, body: {"err":"xxxx"}
      * @param session
      */
-    void listen(const std::shared_ptr<restbed::Session>& session);
+    RequestStatus listen(restinio::request_handle_t request,
+                         restinio::router::route_params_t params);
 
     /**
      * Put a value on the DHT
@@ -173,7 +214,8 @@ private:
      * HTTP 400, body: {"err":"xxxx"} if bad json or HTTP 502 if put fails
      * @param session
      */
-    void put(const std::shared_ptr<restbed::Session>& session);
+    RequestStatus put(restinio::request_handle_t request,
+                      restinio::router::route_params_t params);
 
     void cancelPut(const InfoHash& key, Value::Id vid);
 
@@ -187,7 +229,8 @@ private:
      * HTTP 400, body: {"err":"xxxx"} if bad json
      * @param session
      */
-    void putSigned(const std::shared_ptr<restbed::Session>& session) const;
+    RequestStatus putSigned(restinio::request_handle_t request,
+                            restinio::router::route_params_t params) const;
 
     /**
      * Put a value to encrypt by the proxy on the DHT
@@ -198,7 +241,9 @@ private:
      * HTTP 400, body: {"err":"xxxx"} if bad json
      * @param session
      */
-    void putEncrypted(const std::shared_ptr<restbed::Session>& session) const;
+    RequestStatus putEncrypted(restinio::request_handle_t request,
+                               restinio::router::route_params_t params);
+
 #endif // OPENDHT_PROXY_SERVER_IDENTITY
 
     /**
@@ -211,7 +256,8 @@ private:
      * On error: HTTP 503, body: {"err":"xxxx"}
      * @param session
      */
-    void getFiltered(const std::shared_ptr<restbed::Session>& session) const;
+    RequestStatus getFiltered(restinio::request_handle_t request,
+                              restinio::router::route_params_t params);
 
     /**
      * Respond allowed Methods
@@ -220,13 +266,8 @@ private:
      * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS
      * @param session
      */
-    void handleOptionsMethod(const std::shared_ptr<restbed::Session>& session) const;
-
-    /**
-     * Remove finished listeners
-     * @param testSession if we remove the listener only if the session is closed
-     */
-    void removeClosedListeners(bool testSession = true);
+    RequestStatus options(restinio::request_handle_t request,
+                           restinio::router::route_params_t params);
 
 #ifdef OPENDHT_PUSH_NOTIFICATIONS
     /**
@@ -238,7 +279,9 @@ private:
      * so you need to refresh the operation each six hours.
      * @param session
      */
-    void subscribe(const std::shared_ptr<restbed::Session>& session);
+    RequestStatus subscribe(restinio::request_handle_t request,
+                            restinio::router::route_params_t params);
+
     /**
      * Unsubscribe to push notifications for an iOS or Android device.
      * Method: UNSUBSCRIBE "/{InfoHash: .*}"
@@ -246,7 +289,9 @@ private:
      * Return: nothing
      * @param session
      */
-    void unsubscribe(const std::shared_ptr<restbed::Session>& session);
+    RequestStatus unsubscribe(restinio::request_handle_t request,
+                              restinio::router::route_params_t params);
+
     /**
      * Send a push notification via a gorush push gateway
      * @param key of the device
@@ -262,41 +307,45 @@ private:
      */
     void cancelPushListen(const std::string& pushToken, const InfoHash& key, const std::string& clientId);
 
-
 #endif //OPENDHT_PUSH_NOTIFICATIONS
 
+    void asyncPrintStats();
+
     using clock = std::chrono::steady_clock;
     using time_point = clock::time_point;
 
-    std::thread server_thread {};
-    std::unique_ptr<restbed::Service> service_;
     std::shared_ptr<DhtRunner> dht_;
+    std::shared_ptr<dht::Logger> logger_;
     Json::StreamWriterBuilder jsonBuilder_;
 
-    std::mutex schedulerLock_;
-    std::condition_variable schedulerCv_;
-    Scheduler scheduler_;
-    std::thread schedulerThread_;
-    std::unique_ptr<ThreadPool> threadPool_;
+    std::thread httpServerThread_;
+    std::unique_ptr<restinio::http_server_t<RestRouterTraits>> httpServer_;
+    std::unique_ptr<http::Client> httpClient_;
 
-    Sp<Scheduler::Job> printStatsJob_;
     mutable std::mutex statsMutex_;
+    mutable ServerStats stats_;
     mutable NodeInfo nodeInfo_ {};
-
-    // Handle client quit for listen.
-    // NOTE: can be simplified when we will supports restbed 5.0
-    std::thread listenThread_;
-    struct SessionToHashToken {
-        std::shared_ptr<restbed::Session> session;
-        InfoHash hash;
-        std::future<size_t> token;
+    std::unique_ptr<asio::steady_timer> printStatsTimer_;
+
+    // Thread-safe access to listeners map.
+    std::shared_ptr<std::mutex> lockListener_;
+    // Shared with connection listener.
+    std::shared_ptr<std::map<restinio::connection_id_t,
+                             http::ListenerSession>> listeners_;
+    // Connection Listener observing conn state changes.
+    std::shared_ptr<http::ConnectionListener> connListener_;
+
+    struct PermanentPut {
+        time_point expiration;
+        std::string pushToken;
+        std::string clientId;
+        std::unique_ptr<asio::steady_timer> expireTimer;
+        std::unique_ptr<asio::steady_timer> expireNotifyTimer;
     };
-    std::vector<SessionToHashToken> currentListeners_;
-    std::mutex lockListener_;
-    std::atomic_bool stopListeners {false};
-
-    struct PermanentPut;
-    struct SearchPuts;
+    struct SearchPuts {
+        std::map<dht::Value::Id, PermanentPut> puts;
+    };
+    std::mutex lockSearchPuts_;
     std::map<InfoHash, SearchPuts> puts_;
 
     mutable std::atomic<size_t> requestNum_ {0};
@@ -304,11 +353,16 @@ private:
 
     const std::string pushServer_;
 
-    mutable ServerStats stats_;
-
 #ifdef OPENDHT_PUSH_NOTIFICATIONS
-    struct Listener;
-    struct PushListener;
+    struct Listener {
+        std::string clientId;
+        std::future<size_t> internalToken;
+        std::unique_ptr<asio::steady_timer> expireTimer;
+        std::unique_ptr<asio::steady_timer> expireNotifyTimer;
+    };
+    struct PushListener {
+        std::map<InfoHash, std::vector<Listener>> listeners;
+    };
     std::mutex lockPushListeners_;
     std::map<std::string, PushListener> pushListeners_;
     proxy::ListenToken tokenPushNotif_ {0};
diff --git a/include/opendht/dhtrunner.h b/include/opendht/dhtrunner.h
index 8fed0277c54d3fc680836eb28b7e5e5776b1824a..3a180ec40598290a9ea94ca9b3f0d999fb943689 100644
--- a/include/opendht/dhtrunner.h
+++ b/include/opendht/dhtrunner.h
@@ -65,7 +65,7 @@ public:
     };
 
     struct Context {
-        std::unique_ptr<Logger> logger {};
+        std::shared_ptr<Logger> logger {};
         std::unique_ptr<net::DatagramSocket> sock;
         std::shared_ptr<PeerDiscovery> peerDiscovery {};
         StatusCallback statusChangedCallback {};
@@ -518,6 +518,12 @@ private:
 
     /** PeerDiscovery Parameters */
     std::shared_ptr<PeerDiscovery> peerDiscovery_;
+
+    /**
+     * The Logger instance is used in enableProxy and other methods that
+     * would create instances of classes using a common logger.
+     */
+    std::shared_ptr<dht::Logger> logger_;
 };
 
 }
diff --git a/include/opendht/http.h b/include/opendht/http.h
new file mode 100644
index 0000000000000000000000000000000000000000..7c677f6ae1844606e3b618dc047f5f2c780c2cbb
--- /dev/null
+++ b/include/opendht/http.h
@@ -0,0 +1,217 @@
+/*
+ *  Copyright (C) 2016-2019 Savoir-faire Linux Inc.
+ *  Author: Vsevolod Ivanov <vsevolod.ivanov@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <asio.hpp>
+#include <json/json.h>
+#include <http_parser.h>
+#include <restinio/all.hpp>
+#include <opendht.h>
+#include <opendht/log.h>
+
+namespace http {
+
+class Connection
+{
+public:
+    Connection(const uint16_t id, asio::ip::tcp::socket socket);
+    ~Connection();
+
+    uint16_t id();
+    void start(asio::ip::tcp::resolver::iterator &r_iter);
+    bool is_open();
+    void close();
+
+private:
+    friend class Client;
+
+    uint16_t id_;
+    asio::ip::tcp::socket socket_;
+    asio::streambuf request_;
+    asio::streambuf response_;
+};
+
+/**
+ * Session value associated with a connection_id_t key.
+ */
+struct ListenerSession
+{
+    ListenerSession() = default;
+    dht::InfoHash hash;
+    std::future<size_t> token;
+    std::shared_ptr<restinio::response_builder_t<restinio::chunked_output_t>> response;
+};
+
+/**
+ * Request is the context of an active connection allowing it to parse responses
+ */
+struct Request
+{
+    std::string content;
+    std::shared_ptr<http_parser> parser;
+    std::shared_ptr<http_parser_settings> parser_settings;
+    std::shared_ptr<Connection> connection;
+};
+
+class ConnectionListener
+{
+public:
+    ConnectionListener();
+    ConnectionListener(std::shared_ptr<dht::DhtRunner> dht,
+        std::shared_ptr<std::map<restinio::connection_id_t, http::ListenerSession>> listeners,
+        std::shared_ptr<std::mutex> lock, std::shared_ptr<dht::Logger> logger);
+    ~ConnectionListener();
+
+    /**
+     * Connection state change used to handle Listeners disconnects.
+     * RESTinio >= 0.5.1 https://github.com/Stiffstream/restinio/issues/28
+     */
+    void state_changed(const restinio::connection_state::notice_t &notice) noexcept;
+
+private:
+    std::string to_str( restinio::connection_state::cause_t cause ) noexcept;
+
+    std::shared_ptr<dht::DhtRunner> dht_;
+    std::shared_ptr<std::mutex> lock_;
+    std::shared_ptr<std::map<restinio::connection_id_t,
+                             http::ListenerSession>> listeners_;
+    std::shared_ptr<dht::Logger> logger_;
+};
+
+class Client
+{
+public:
+    Client(asio::io_context &ctx, std::string host, uint16_t port,
+           std::shared_ptr<dht::Logger> logger = {});
+
+    asio::io_context& io_context();
+
+    void set_logger(std::shared_ptr<dht::Logger> logger);
+    void set_query_address(const std::string host, const uint16_t port);
+
+    bool active_connection(uint16_t conn_id);
+    void close_connection(uint16_t conn_id);
+
+    std::string create_request(const restinio::http_request_header_t header,
+                               const restinio::http_header_fields_t header_fields,
+                               const restinio::http_connection_header_t connection,
+                               const std::string body);
+
+    uint16_t post_request(std::string request,
+                          std::shared_ptr<http_parser> parser,
+                          std::shared_ptr<http_parser_settings> parser_s);
+
+private:
+    std::shared_ptr<Connection> create_connection();
+
+    void handle_connect(const asio::error_code &ec,
+                        asio::ip::tcp::resolver::iterator endpoint_it,
+                        std::shared_ptr<Connection> conn = {});
+
+    void handle_resolve(const asio::error_code &ec,
+                        asio::ip::tcp::resolver::iterator endpoint_it,
+                        std::shared_ptr<Connection> conn = {});
+
+    void handle_request(const asio::error_code &ec,
+                        std::shared_ptr<Connection> conn = {});
+
+    void handle_response(const asio::error_code &ec,
+                         std::shared_ptr<Connection> conn = {});
+
+    uint16_t port_;
+    std::string host_;
+
+    // contains the io_context
+    asio::ip::tcp::resolver resolver_;
+
+    uint16_t connId_ {1};
+    /*
+     * An association between an active connection and its context, a Request.
+     */
+    std::map<uint16_t, Request> requests_;
+
+    std::shared_ptr<dht::Logger> logger_;
+};
+
+} // namespace http
+
+namespace restinio
+{
+
+class opendht_logger_t
+{
+public:
+    opendht_logger_t(std::shared_ptr<dht::Logger> logger = {}){
+        if (logger)
+            m_logger = logger;
+    }
+
+    template <typename Builder>
+    void trace(Builder && msg_builder){
+        if (m_logger)
+            m_logger->d("[proxy:server] %s", msg_builder().c_str());
+    }
+
+    template <typename Builder>
+    void info(Builder && msg_builder){
+        if (m_logger)
+            m_logger->d("[proxy:server] %s", msg_builder().c_str());
+    }
+
+    template <typename Builder>
+    void warn(Builder && msg_builder){
+        if (m_logger)
+            m_logger->w("[proxy:server] %s", msg_builder().c_str());
+    }
+
+    template <typename Builder>
+    void error(Builder && msg_builder){
+        if (m_logger)
+            m_logger->e("[proxy:server] %s", msg_builder().c_str());
+    }
+
+private:
+    std::shared_ptr<dht::Logger> m_logger;
+};
+
+/* Custom HTTP-methods for RESTinio > 0.5.0.
+ * https://github.com/Stiffstream/restinio/issues/26
+ */
+constexpr const restinio::http_method_id_t method_listen{HTTP_LISTEN, "LISTEN"};
+constexpr const restinio::http_method_id_t method_stats{HTTP_STATS, "STATS"};
+constexpr const restinio::http_method_id_t method_sign{HTTP_SIGN, "SIGN"};
+constexpr const restinio::http_method_id_t method_encrypt{HTTP_ENCRYPT, "ENCRYPT"};
+
+struct custom_http_methods_t
+{
+    static constexpr restinio::http_method_id_t from_nodejs(int m) noexcept {
+        if(m == method_listen.raw_id())
+            return method_listen;
+        else if(m == method_stats.raw_id())
+            return method_stats;
+        else if(m == method_sign.raw_id())
+            return method_sign;
+        else if(m == method_encrypt.raw_id())
+            return method_encrypt;
+        else
+            return restinio::default_http_methods_t::from_nodejs(m);
+    }
+};
+
+} // namespace restinio
diff --git a/python/opendht.pyx b/python/opendht.pyx
index 1fa3c239db40db5501b092b9353e1ba2f03256ad..538276b85703bc5aee72faaa4b6d24747aec406f 100644
--- a/python/opendht.pyx
+++ b/python/opendht.pyx
@@ -1,5 +1,5 @@
 # distutils: language = c++
-# distutils: extra_compile_args = -std=c++11
+# distutils: extra_compile_args = -std=c++14
 # distutils: include_dirs = ../../include
 # distutils: library_dirs = ../../src
 # distutils: libraries = opendht gnutls
diff --git a/python/setup.py.in b/python/setup.py.in
index 2f67164a9e9024a5d2134b4d216c4c93a0fde4a4..09ff43d7a50d3c29534ea31066a6aebf2f237a64 100644
--- a/python/setup.py.in
+++ b/python/setup.py.in
@@ -50,8 +50,8 @@ setup(name="opendht",
           ["@CURRENT_SOURCE_DIR@/opendht.pyx"],
           include_dirs = ['@PROJECT_SOURCE_DIR@/include'],
           language="c++",
-          extra_compile_args=["-std=c++11"],
-          extra_link_args=["-std=c++11"],
+          extra_compile_args=["-std=c++14"],
+          extra_link_args=["-std=c++14"],
           libraries=["opendht"],
           library_dirs = ['@CURRENT_BINARY_DIR@', '@PROJECT_BINARY_DIR@']
       ))
diff --git a/src/dht_proxy_client.cpp b/src/dht_proxy_client.cpp
index 153a29614ec17eab8620d8ae4ff5271a53b81e08..ac0c05ec83ae37082f652932f575890079704553 100644
--- a/src/dht_proxy_client.cpp
+++ b/src/dht_proxy_client.cpp
@@ -2,6 +2,7 @@
  *  Copyright (C) 2016-2019 Savoir-faire Linux Inc.
  *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
  *          Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *          Vsevolod Ivanov <vsevolod.ivanov@savoirfairelinux.com>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -18,17 +19,10 @@
  */
 
 #include "dht_proxy_client.h"
-
 #include "dhtrunner.h"
 #include "op_cache.h"
 #include "utils.h"
 
-#include <restbed>
-#include <json/json.h>
-
-#include <chrono>
-#include <vector>
-
 namespace dht {
 
 struct DhtProxyClient::InfoState {
@@ -43,40 +37,71 @@ struct DhtProxyClient::ListenState {
 
 struct DhtProxyClient::Listener
 {
+    Listener(OpValueCache&& c, Value::Filter&& f):
+        cache(std::move(c)), filter(std::move(f))
+    {}
+
+    uint16_t connId {0};
+    unsigned callbackId;
     OpValueCache cache;
     ValueCallback cb;
     Value::Filter filter;
-    Sp<restbed::Request> req;
-    std::thread thread;
-    unsigned callbackId;
     Sp<ListenState> state;
-    Sp<Scheduler::Job> refreshJob;
-    Listener(OpValueCache&& c, const Sp<restbed::Request>& r, Value::Filter&& f)
-        : cache(std::move(c)), filter(std::move(f)),req(r) {}
+    Sp<asio::steady_timer> refreshTimer;
+
 };
 
 struct PermanentPut {
+    PermanentPut(const Sp<Value>& v, Sp<asio::steady_timer>&& j,
+                 const Sp<std::atomic_bool>& o):
+        value(v), refreshTimer(std::move(j)), ok(o)
+    {}
+
     Sp<Value> value;
-    Sp<Scheduler::Job> refreshJob;
+    Sp<asio::steady_timer> refreshTimer;
     Sp<std::atomic_bool> ok;
-    PermanentPut(const Sp<Value>& v, Sp<Scheduler::Job>&& j, const Sp<std::atomic_bool>& o)
-        : value(v), refreshJob(std::move(j)), ok(o) {}
 };
 
 struct DhtProxyClient::ProxySearch {
     SearchCache ops {};
-    Sp<Scheduler::Job> opExpirationJob {};
+    Sp<asio::steady_timer> opExpirationTimer;
     std::map<size_t, Listener> listeners {};
     std::map<Value::Id, PermanentPut> puts {};
 };
 
 DhtProxyClient::DhtProxyClient() {}
 
-DhtProxyClient::DhtProxyClient(std::function<void()> signal, const std::string& serverHost, const std::string& pushClientId, const Logger& l)
-: DhtInterface(l), serverHost_(serverHost), pushClientId_(pushClientId), loopSignal_(signal)
+DhtProxyClient::DhtProxyClient(std::function<void()> signal, const std::string &serverHost,
+    const std::string &pushClientId, std::shared_ptr<dht::Logger> logger):
+        serverHost_(serverHost), pushClientId_(pushClientId), loopSignal_(signal),
+        logger_(logger)
 {
+    // build http client
+    auto hostAndPort = splitPort(serverHost_);
+    serverHostIp_ = hostAndPort.first;
+    serverHostPort_ = std::atoi(hostAndPort.second.c_str());
+    httpClient_ = std::make_unique<http::Client>(
+        httpContext_, serverHostIp_, serverHostPort_, logger);
+    // run http client
+    httpClientThread_ = std::thread([this](){
+        try {
+            if (logger_)
+                logger_->d("[proxy:client] starting io context");
+            // Ensures the httpContext_ won't run out of work
+            auto work = asio::make_work_guard(httpContext_);
+            httpContext_.run();
+            if (logger_)
+                logger_->d("[proxy:client] http client io context stopped");
+        }
+        catch(const std::exception &ex){
+            if (logger_)
+                logger_->e("[proxy:client] error starting io context");
+        }
+    });
+
     if (serverHost_.find("://") == std::string::npos)
         serverHost_ = proxy::HTTP_PROTO + serverHost_;
+
     if (!serverHost_.empty())
         startProxy();
 }
@@ -84,17 +109,27 @@ DhtProxyClient::DhtProxyClient(std::function<void()> signal, const std::string&
 void
 DhtProxyClient::confirmProxy()
 {
-    if (serverHost_.empty()) return;
+    if (serverHost_.empty())
+        return;
     getConnectivityStatus();
 }
 
 void
 DhtProxyClient::startProxy()
 {
-    if (serverHost_.empty()) return;
-    DHT_LOG.w("Staring proxy client to %s", serverHost_.c_str());
-    nextProxyConfirmation = scheduler.add(scheduler.time(), std::bind(&DhtProxyClient::confirmProxy, this));
-    listenerRestart = std::make_shared<Scheduler::Job>(std::bind(&DhtProxyClient::restartListeners, this));
+    if (serverHost_.empty())
+        return;
+
+    if (logger_)
+        logger_->d("[proxy:client] staring proxy with %s", serverHost_.c_str());
+
+    nextProxyConfirmationTimer_ = std::make_shared<asio::steady_timer>(
+        httpContext_, std::chrono::steady_clock::now());
+    nextProxyConfirmationTimer_->async_wait(std::bind(&DhtProxyClient::confirmProxy, this));
+
+    listenerRestartTimer_ = std::make_shared<asio::steady_timer>(httpContext_);
+    listenerRestartTimer_->async_wait(std::bind(&DhtProxyClient::restartListeners, this));
+
     loopSignal_();
 }
 
@@ -105,8 +140,10 @@ DhtProxyClient::~DhtProxyClient()
     cancelAllListeners();
     if (infoState_)
         infoState_->cancel = true;
-    if (statusThread_.joinable())
-        statusThread_.join();
+    if (statusTimer_)
+        statusTimer_->cancel();
+    if (httpClientThread_.joinable())
+        httpClientThread_.join();
 }
 
 std::vector<Sp<Value>>
@@ -130,49 +167,30 @@ DhtProxyClient::getLocalById(const InfoHash& k, Value::Id id) const {
 void
 DhtProxyClient::cancelAllOperations()
 {
-    std::lock_guard<std::mutex> lock(lockOperations_);
-    auto operation = operations_.begin();
-    while (operation != operations_.end()) {
-        if (operation->thread.joinable()) {
-            // Close connection to stop operation?
-            if (operation->req) {
-                try {
-                    restbed::Http::close(operation->req);
-                } catch (const std::exception& e) {
-                    DHT_LOG.w("Error closing socket: %s", e.what());
-                }
-                operation->req.reset();
-            }
-            operation->thread.join();
-            operation = operations_.erase(operation);
-        } else {
-            ++operation;
-        }
-    }
+    if (!httpContext_.stopped())
+        httpContext_.stop();
 }
 
 void
 DhtProxyClient::cancelAllListeners()
 {
     std::lock_guard<std::mutex> lock(searchLock_);
-    DHT_LOG.w("Cancelling all listeners for %zu searches", searches_.size());
+    if (logger_)
+        logger_->d("[proxy:client] [listeners:cancel:all] [%zu searches]", searches_.size());
     for (auto& s: searches_) {
         s.second.ops.cancelAll([&](size_t token){
             auto l = s.second.listeners.find(token);
             if (l == s.second.listeners.end())
                 return;
-            if (l->second.thread.joinable()) {
-                // Close connection to stop listener?
+            if (httpClient_->active_connection(l->second.connId)){
                 l->second.state->cancel = true;
-                if (l->second.req) {
-                    try {
-                        restbed::Http::close(l->second.req);
-                    } catch (const std::exception& e) {
-                        DHT_LOG.w("Error closing socket: %s", e.what());
-                    }
-                    l->second.req.reset();
+                try {
+                    httpClient_->close_connection(l->second.connId);
+                } catch (const std::exception& e) {
+                    if (logger_)
+                        logger_->e("[proxy:client] [listeners:cancel:all] error closing socket: %s", e.what());
                 }
-                l->second.thread.join();
+                l->second.connId = 0;
             }
             s.second.listeners.erase(token);
         });
@@ -222,134 +240,145 @@ time_point
 DhtProxyClient::periodic(const uint8_t*, size_t, SockAddr)
 {
     // Exec all currently stored callbacks
-    scheduler.syncTime();
     decltype(callbacks_) callbacks;
     {
-        std::lock_guard<std::mutex> lock(lockCallbacks);
+        std::lock_guard<std::mutex> lock(lockCallbacks_);
         callbacks = std::move(callbacks_);
     }
     for (auto& callback : callbacks)
         callback();
     callbacks.clear();
+    return time_point::max();
+}
 
-    // Remove finished operations
-    {
-        std::lock_guard<std::mutex> lock(lockOperations_);
-        auto operation = operations_.begin();
-        while (operation != operations_.end()) {
-            if (*(operation->finished)) {
-                if (operation->thread.joinable()) {
-                    // Close connection to stop operation?
-                    if (operation->req) {
-                        try {
-                            restbed::Http::close(operation->req);
-                        } catch (const std::exception& e) {
-                            DHT_LOG.w("Error closing socket: %s", e.what());
-                        }
-                        operation->req.reset();
-                    }
-                    operation->thread.join();
-                }
-                operation = operations_.erase(operation);
-            } else {
-                ++operation;
-            }
-        }
-    }
-    return scheduler.run();
+restinio::http_header_fields_t
+DhtProxyClient::initHeaderFields(){
+    restinio::http_header_fields_t header_fields;
+    header_fields.append_field(restinio::http_field_t::host,
+        (serverHostIp_ + ":" + std::to_string(serverHostPort_)).c_str());
+    header_fields.append_field(restinio::http_field_t::user_agent, "RESTinio client");
+    header_fields.append_field(restinio::http_field_t::accept, "*/*");
+    header_fields.append_field(restinio::http_field_t::content_type, "application/json");
+    return header_fields;
 }
 
 void
-DhtProxyClient::get(const InfoHash& key, GetCallback cb, DoneCallback donecb, Value::Filter&& f, Where&& w)
+DhtProxyClient::get(const InfoHash& key, GetCallback cb, DoneCallback donecb,
+                    Value::Filter&& f, Where&& w)
 {
-    DHT_LOG.d(key, "[search %s]: get", key.to_c_str());
-    restbed::Uri uri(serverHost_ + "/" + key.toString());
-    auto req = std::make_shared<restbed::Request>(uri);
-    Value::Filter filter = w.empty() ? f : f.chain(w.getFilter());
-
-    auto finished = std::make_shared<std::atomic_bool>(false);
-    Operation o;
-    o.req = req;
-    o.finished = finished;
-    o.thread = std::thread([=](){
-        // Try to contact the proxy and set the status to connected when done.
-        // will change the connectivity status
-        struct GetState{ std::atomic_bool ok {true}; std::atomic_bool stop {false}; };
-        auto state = std::make_shared<GetState>();
-        try {
-            restbed::Http::async(req,
-                [=](const std::shared_ptr<restbed::Request>& req,
-                    const std::shared_ptr<restbed::Response>& reply) {
-                auto code = reply->get_status_code();
-
-                if (code == 200) {
-                    try {
-                        while (restbed::Http::is_open(req) and not *finished and not state->stop) {
-                            restbed::Http::fetch("\n", reply);
-                            if (*finished or state->stop)
-                                break;
-                            std::string body;
-                            reply->get_body(body);
-                            reply->set_body(""); // Reset the body for the next fetch
-
-                            std::string err;
-                            Json::Value json;
-                            Json::CharReaderBuilder rbuilder;
-                            auto* char_data = reinterpret_cast<const char*>(&body[0]);
-                            auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-                            if (reader->parse(char_data, char_data + body.size(), &json, &err)) {
-                                auto value = std::make_shared<Value>(json);
-                                if ((not filter or filter(*value)) and cb) {
-                                    {
-                                        std::lock_guard<std::mutex> lock(lockCallbacks);
-                                        callbacks_.emplace_back([cb, value, state]() {
-                                            if (not state->stop and not cb({value}))
-                                                state->stop = true;
-                                        });
-                                    }
-                                    loopSignal_();
-                                }
-                            } else {
-                                state->ok = false;
-                            }
-                        }
-                    } catch (std::runtime_error& e) { }
-                } else {
-                    state->ok = false;
+    if (logger_)
+        logger_->d("[proxy:client] [get] [search %s]", key.to_c_str());
+    restinio::http_request_header_t header;
+    header.request_target("/" + key.toString());
+    header.method(restinio::http_method_get());
+    auto header_fields = this->initHeaderFields();
+    auto request = httpClient_->create_request(header, header_fields,
+        restinio::http_connection_header_t::keep_alive, ""/*body*/);
+    if (logger_)
+        logger_->d(request.c_str());
+
+    struct GetContext {
+        GetCallback cb;
+        DoneCallbackSimple donecb; // wrapper
+        Value::Filter filter;
+        std::atomic_bool ok {true};
+        std::atomic_bool stop {false};
+        std::shared_ptr<dht::Logger> logger;
+    };
+    auto context = std::make_shared<GetContext>();
+    context->filter = w.empty() ? f : f.chain(w.getFilter());
+    context->cb = [this, context, cb]
+        (const std::vector<dht::Sp<dht::Value>>& values) -> bool {
+        {
+            std::lock_guard<std::mutex> lock(lockCallbacks_);
+            callbacks_.emplace_back([context, cb, values](){
+                if (not context->stop and not cb(values)){
+                    context->stop = true;
                 }
-            }).wait();
-        } catch(const std::exception& e) {
-            state->ok = false;
+            });
         }
-        if (donecb) {
-            {
-                std::lock_guard<std::mutex> lock(lockCallbacks);
-                callbacks_.emplace_back([=](){
-                    donecb(state->ok, {});
-                    state->stop = true;
-                });
+        loopSignal_();
+        return context->ok;
+    };
+    // keeping context data alive
+    context->donecb = [this, context, donecb](bool ok){
+        {
+            std::lock_guard<std::mutex> lock(lockCallbacks_);
+            callbacks_.emplace_back([=](){
+                donecb(context->ok, {});
+                context->stop = true;
+            });
+        }
+        loopSignal_();
+    };
+    if (logger_)
+        context->logger = logger_;
+
+    auto parser = std::make_shared<http_parser>();
+    http_parser_init(parser.get(), HTTP_RESPONSE);
+    parser->data = static_cast<void*>(context.get());
+
+    auto parser_s = std::make_shared<http_parser_settings>();
+    http_parser_settings_init(parser_s.get());
+    parser_s->on_status = [](http_parser *parser, const char *at, size_t length) -> int {
+        auto context = static_cast<GetContext*>(parser->data);
+        if (parser->status_code != 200){
+            if (context->logger)
+                context->logger->e("[proxy:client] [get] status error: %i", parser->status_code);
+            context->ok = true;
+        }
+        return 0;
+    };
+    parser_s->on_body = [](http_parser *parser, const char *at, size_t length) -> int {
+        auto context = static_cast<GetContext*>(parser->data);
+        try{
+            Json::Value json;
+            std::string err;
+            Json::CharReaderBuilder rbuilder;
+            auto body = std::string(at, length);
+            auto* char_data = static_cast<const char*>(&body[0]);
+            auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
+            if (!reader->parse(char_data, char_data + body.size(), &json, &err)){
+                context->ok = false;
+                return 1;
+            }
+            auto value = std::make_shared<Value>(json);
+            if ((not context->filter or context->filter(*value)) and context->cb){
+                context->cb({value});
             }
-            loopSignal_();
+        } catch(const std::exception& e) {
+            if (context->logger)
+                context->logger->e("[proxy:client] [get] body parsing error: %s", e.what());
+            context->ok = false;
+            return 1;
         }
-        if (!state->ok) {
-            // Connection failed, update connectivity
-            opFailed();
+        return 0;
+    };
+    parser_s->on_message_complete = [](http_parser *parser) -> int {
+        auto context = static_cast<GetContext*>(parser->data);
+        try {
+            if (context->donecb)
+                context->donecb(context->ok);
+        } catch(const std::exception& e) {
+            if (context->logger)
+                context->logger->e("[proxy:client] [get] message complete parsing error: %i",
+                                   parser->status_code);
+            return 1;
         }
-        *finished = true;
-    });
-    {
-        std::lock_guard<std::mutex> lock(lockOperations_);
-        operations_.emplace_back(std::move(o));
-    }
+        return 0;
+    };
+    httpClient_->post_request(request, parser, parser_s);
 }
 
 void
-DhtProxyClient::put(const InfoHash& key, Sp<Value> val, DoneCallback cb, time_point created, bool permanent)
+DhtProxyClient::put(const InfoHash& key, Sp<Value> val, DoneCallback cb,
+                    time_point created, bool permanent)
 {
-    DHT_LOG.d(key, "[search %s]: put", key.to_c_str());
-    scheduler.syncTime();
-    if (not val) {
-        if (cb) cb(false, {});
+    if (logger_)
+        logger_->d("[proxy:client] [put] [search %s]", key.to_c_str());
+    if (not val){
+        if (cb)
+            cb(false, {});
         return;
     }
     if (val->id == Value::INVALID_ID) {
@@ -360,26 +389,35 @@ DhtProxyClient::put(const InfoHash& key, Sp<Value> val, DoneCallback cb, time_po
     if (permanent) {
         std::lock_guard<std::mutex> lock(searchLock_);
         auto id = val->id;
-        auto& search = searches_[key];
-        auto nextRefresh = scheduler.time() + proxy::OP_TIMEOUT - proxy::OP_MARGIN;
+        auto &search = searches_[key];
+        auto refreshTimer = std::make_shared<asio::steady_timer>(httpContext_,
+            std::chrono::steady_clock::now() + proxy::OP_TIMEOUT - proxy::OP_MARGIN);
         auto ok = std::make_shared<std::atomic_bool>(false);
+        // define refresh timer handler
+        refreshTimer->async_wait([this, key, id, ok](const asio::error_code &ec){
+            if (ec){
+                if (logger_)
+                    logger_->e("[proxy:client] [listener:refresh] error key=%s", key.toString().c_str());
+                return;
+            }
+            std::lock_guard<std::mutex> lock(searchLock_);
+            auto s = searches_.find(key);
+            if (s != searches_.end()) {
+                auto p = s->second.puts.find(id);
+                if (p != s->second.puts.end()) {
+                    doPut(key, p->second.value, [ok]
+                    (bool result, const std::vector<std::shared_ptr<dht::Node> >&){
+                        *ok = result;
+                    }, time_point::max(), true);
+                    p->second.refreshTimer->expires_at(std::chrono::steady_clock::now() +
+                        proxy::OP_TIMEOUT - proxy::OP_MARGIN);
+                }
+            }
+        });
         search.puts.erase(id);
         search.puts.emplace(std::piecewise_construct,
             std::forward_as_tuple(id),
-            std::forward_as_tuple(val, scheduler.add(nextRefresh, [this, key, id, ok]{
-                std::lock_guard<std::mutex> lock(searchLock_);
-                auto s = searches_.find(key);
-                if (s != searches_.end()) {
-                    auto p = s->second.puts.find(id);
-                    if (p != s->second.puts.end()) {
-                        doPut(key, p->second.value,
-                        [ok](bool result, const std::vector<std::shared_ptr<dht::Node> >&){
-                            *ok = result;
-                        }, time_point::max(), true);
-                        scheduler.edit(p->second.refreshJob, scheduler.time() + proxy::OP_TIMEOUT - proxy::OP_MARGIN);
-                    }
-                }
-            }), ok));
+            std::forward_as_tuple(val, std::move(refreshTimer), ok));
     }
     doPut(key, val, std::move(cb), created, permanent);
 }
@@ -387,10 +425,12 @@ DhtProxyClient::put(const InfoHash& key, Sp<Value> val, DoneCallback cb, time_po
 void
 DhtProxyClient::doPut(const InfoHash& key, Sp<Value> val, DoneCallback cb, time_point /*created*/, bool permanent)
 {
-    DHT_LOG.d(key, "[search %s] performing put of %s", key.to_c_str(), val->toString().c_str());
-    restbed::Uri uri(serverHost_ + "/" + key.toString());
-    auto req = std::make_shared<restbed::Request>(uri);
-    req->set_method("POST");
+    if (logger_)
+        logger_->d("[proxy:client] [put] [search %s] executing for %s", key.to_c_str(), val->toString().c_str());
+    restinio::http_request_header_t header;
+    header.request_target("/" + key.toString());
+    header.method(restinio::http_method_post());
+    auto header_fields = this->initHeaderFields();
 
     auto json = val->toJson();
     if (permanent) {
@@ -409,65 +449,60 @@ DhtProxyClient::doPut(const InfoHash& key, Sp<Value> val, DoneCallback cb, time_
     Json::StreamWriterBuilder wbuilder;
     wbuilder["commentStyle"] = "None";
     wbuilder["indentation"] = "";
-    auto body = Json::writeString(wbuilder, json) + "\n";
-    req->set_body(body);
-    req->set_header("Content-Length", std::to_string(body.size()));
-
-    auto finished = std::make_shared<std::atomic_bool>(false);
-    Operation o;
-    o.req = req;
-    o.finished = finished;
-    o.thread = std::thread([=](){
-        auto ok = std::make_shared<std::atomic_bool>(true);
-        try {
-            restbed::Http::async(req,
-                [ok](const std::shared_ptr<restbed::Request>& /*req*/,
-                            const std::shared_ptr<restbed::Response>& reply) {
-                auto code = reply->get_status_code();
-
-                if (code == 200) {
-                    restbed::Http::fetch("\n", reply);
-                    std::string body;
-                    reply->get_body(body);
-                    reply->set_body(""); // Reset the body for the next fetch
-
-                    try {
-                        std::string err;
-                        Json::Value json;
-                        Json::CharReaderBuilder rbuilder;
-                        auto* char_data = reinterpret_cast<const char*>(&body[0]);
-                        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-                        if (not reader->parse(char_data, char_data + body.size(), &json, &err))
-                            *ok = false;
-                    } catch (...) {
-                        *ok = false;
-                    }
-                } else {
-                    *ok = false;
-                }
-            }).wait();
-        } catch(const std::exception& e) {
-            *ok = false;
+    auto body = Json::writeString(wbuilder, json);
+    auto request = httpClient_->create_request(header, header_fields,
+        restinio::http_connection_header_t::close, body);
+    if (logger_)
+        logger_->d("%s", request.c_str());
+
+    struct GetContext {
+        DoneCallbackSimple donecb; // wrapper
+        std::atomic_bool ok {false};
+        std::shared_ptr<dht::Logger> logger;
+    };
+    auto context = std::make_shared<GetContext>();
+    // keeping context data alive
+    context->donecb = [this, context, cb](bool ok){
+        {
+            std::lock_guard<std::mutex> lock(lockCallbacks_);
+            callbacks_.emplace_back([=](){
+                cb(context->ok, {});
+            });
         }
-        if (cb) {
-            {
-                std::lock_guard<std::mutex> lock(lockCallbacks);
-                callbacks_.emplace_back([=](){
-                    cb(*ok, {});
-                });
-            }
-            loopSignal_();
+        loopSignal_();
+    };
+    if (logger_)
+        context->logger = logger_;
+
+    auto parser = std::make_shared<http_parser>();
+    http_parser_init(parser.get(), HTTP_RESPONSE);
+    parser->data = static_cast<void*>(context.get());
+
+    auto parser_s = std::make_shared<http_parser_settings>();
+    http_parser_settings_init(parser_s.get());
+    parser_s->on_status = [](http_parser *parser, const char *at, size_t length) -> int {
+        GetContext* context = static_cast<GetContext*>(parser->data);
+        if (parser->status_code == 200){
+            context->ok = true;
+        } else {
+            if (context->logger)
+                context->logger->e("[proxy:client] [put] status error: %i", parser->status_code);
         }
-        if (!ok) {
-            // Connection failed, update connectivity
-            opFailed();
+        return 0;
+    };
+    parser_s->on_message_complete = [](http_parser * parser) -> int {
+        auto context = static_cast<GetContext*>(parser->data);
+        try {
+            if (context->donecb)
+                context->donecb(context->ok);
+        } catch(const std::exception& e) {
+            if (context->logger)
+                context->logger->e("[proxy:client] [put] message complete error: %s", e.what());
+            return 1;
         }
-        *finished = true;
-    });
-    {
-        std::lock_guard<std::mutex> lock(lockOperations_);
-        operations_.emplace_back(std::move(o));
-    }
+        return 0;
+    };
+    httpClient_->post_request(request, parser, parser_s);
 }
 
 /**
@@ -509,7 +544,8 @@ DhtProxyClient::cancelPut(const InfoHash& key, const Value::Id& id)
     auto search = searches_.find(key);
     if (search == searches_.end())
         return false;
-    DHT_LOG.d(key, "[search %s] cancel put", key.to_c_str());
+    if (logger_)
+        logger_->d("[proxy:client] [put:cancel] [search %s]", key.to_c_str());
     return search->second.puts.erase(id) > 0;
 }
 
@@ -522,14 +558,14 @@ DhtProxyClient::getNodesStats(sa_family_t af) const
 void
 DhtProxyClient::getProxyInfos()
 {
-    DHT_LOG.d("Requesting proxy server node information");
+    if (logger_)
+        logger_->d("[proxy:client] [info] requesting proxy server node information");
     std::lock_guard<std::mutex> l(statusLock_);
 
     auto infoState = std::make_shared<InfoState>();
     if (infoState_)
         infoState_->cancel = true;
     infoState_ = infoState;
-
     {
         std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
         if (statusIpv4_ == NodeStatus::Disconnected)
@@ -537,85 +573,125 @@ DhtProxyClient::getProxyInfos()
         if (statusIpv6_ == NodeStatus::Disconnected)
             statusIpv6_ = NodeStatus::Connecting;
     }
-
-    // A node can have a Ipv4 and a Ipv6. So, we need to retrieve all public ips
-    auto serverHost = serverHost_;
-
     // Try to contact the proxy and set the status to connected when done.
     // will change the connectivity status
-    if (statusThread_.joinable()) {
-        try {
-            statusThread_.detach();
-            statusThread_ = {};
-        } catch (const std::exception& e) {
-            DHT_LOG.e("Error detaching thread: %s", e.what());
+    if (!statusTimer_)
+        statusTimer_ = std::make_shared<asio::steady_timer>(httpContext_);
+
+    statusTimer_->expires_at(std::chrono::steady_clock::now());
+    statusTimer_->async_wait(std::bind(&DhtProxyClient::handleProxyStatus, this,
+        std::placeholders::_1, infoState));
+}
+
+void
+DhtProxyClient::handleProxyStatus(const asio::error_code &ec,
+                                  std::shared_ptr<InfoState> infoState)
+{
+    if (ec){
+        if (logger_){
+            logger_->e("[proxy:client] [status] handling error: %s", ec.message().c_str());
+            return;
         }
     }
-    statusThread_ = std::thread([this, serverHost, infoState]{
-        try {
-            auto endpointStr = serverHost;
-            auto protocol = std::string(proxy::HTTP_PROTO);
-            auto protocolIdx = serverHost.find("://");
-            if (protocolIdx != std::string::npos) {
-                protocol = endpointStr.substr(0, protocolIdx + 3);
-                endpointStr = endpointStr.substr(protocolIdx + 3);
-            }
-            auto hostAndService = splitPort(endpointStr);
-            auto resolved_proxies = SockAddr::resolve(hostAndService.first, hostAndService.second);
-            std::vector<std::future<Sp<restbed::Response>>> reqs;
-            reqs.reserve(resolved_proxies.size());
-            for (const auto& resolved_proxy: resolved_proxies) {
-                auto server = resolved_proxy.toString();
-                if (resolved_proxy.getFamily() == AF_INET6) {
-                    // HACK restbed seems to not correctly handle directly http://[ipv6]
-                    // See https://github.com/Corvusoft/restbed/issues/290.
-                    server = endpointStr;
+    auto serverHost = serverHost_;
+    try {
+        // A node can have a Ipv4 and a Ipv6. So, we need to retrieve all public ips
+        auto endpointStr = serverHost;
+        auto protocol = std::string(proxy::HTTP_PROTO);
+        auto protocolIdx = serverHost.find("://");
+        if (protocolIdx != std::string::npos) {
+            protocol = endpointStr.substr(0, protocolIdx + 3);
+            endpointStr = endpointStr.substr(protocolIdx + 3);
+        }
+        auto hostAndService = splitPort(endpointStr);
+        auto resolvedProxies = SockAddr::resolve(hostAndService.first, hostAndService.second);
+
+        for (const auto& resolvedProxy: resolvedProxies){
+            auto server = resolvedProxy.toString();
+            // make an http header
+            restinio::http_request_header_t header;
+            header.request_target("/");
+            header.method(restinio::http_method_get());
+            auto header_fields = this->initHeaderFields();
+            auto request = httpClient_->create_request(header, header_fields,
+                restinio::http_connection_header_t::keep_alive, ""/*body*/);
+            if (logger_)
+                logger_->d("[proxy:client] [status] sending request:\n%s", request.c_str());
+            // initalise the parser callback data
+            struct GetContext {
+                unsigned int family;
+                std::function<void(Json::Value infos)> cb; // wrapper
+                std::atomic_bool ok {true};
+                std::shared_ptr<InfoState> infoState;
+                std::function<void(const Json::Value&, sa_family_t)> proxyInfo;
+                std::shared_ptr<dht::Logger> logger;
+            };
+            auto context = std::make_shared<GetContext>();
+            context->infoState = infoState;
+            context->family = resolvedProxy.getFamily();
+            context->proxyInfo = std::bind(&DhtProxyClient::onProxyInfos, this,
+                std::placeholders::_1, std::placeholders::_2);
+            // keeping context data alive
+            context->cb = [this, context](Json::Value infos){
+                if (context->family == AF_INET) 
+                    context->infoState->ipv4++;
+                else if (context->family == AF_INET6)
+                    context->infoState->ipv6++;
+                if (not context->infoState->cancel)
+                    context->proxyInfo(infos, context->family);
+            };
+            if (logger_)
+                context->logger = logger_;
+
+            // initialize the parser
+            auto parser = std::make_shared<http_parser>();
+            http_parser_init(parser.get(), HTTP_RESPONSE);
+            parser->data = static_cast<void*>(context.get());
+
+            // init the parser callbacks
+            auto parser_s = std::make_shared<http_parser_settings>();
+            http_parser_settings_init(parser_s.get());
+            parser_s->on_status = [](http_parser *parser, const char *at, size_t length) -> int {
+                auto context = static_cast<GetContext*>(parser->data);
+                if (parser->status_code != 200){
+                    if (context->logger)
+                        context->logger->e("[proxy:client] [status] error: %i", parser->status_code);
+                    context->ok = true;
                 }
-                restbed::Uri uri(protocol + server + "/");
-                auto req = std::make_shared<restbed::Request>(uri);
-                if (infoState->cancel)
-                    return;
-                reqs.emplace_back(restbed::Http::async(req,
-                    [this, resolved_proxy, infoState](
-                                const std::shared_ptr<restbed::Request>&,
-                                const std::shared_ptr<restbed::Response>& reply)
-                {
-                    auto code = reply->get_status_code();
+                return 0;
+            };
+            parser_s->on_body = [](http_parser *parser, const char *at, size_t length) -> int {
+                auto context = static_cast<GetContext*>(parser->data);
+                try{
+                    std::string err;
                     Json::Value proxyInfos;
-                    if (code == 200) {
-                        restbed::Http::fetch("\n", reply);
-                        auto& state = *infoState;
-                        if (state.cancel) return;
-                        std::string body;
-                        reply->get_body(body);
-
-                        std::string err;
-                        Json::CharReaderBuilder rbuilder;
-                        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-                        try {
-                            reader->parse(body.data(), body.data() + body.size(), &proxyInfos, &err);
-                        } catch (...) {
-                            return;
-                        }
-                        auto family = resolved_proxy.getFamily();
-                        if      (family == AF_INET)  state.ipv4++;
-                        else if (family == AF_INET6) state.ipv6++;
-                        if (not state.cancel)
-                            onProxyInfos(proxyInfos, family);
+                    Json::CharReaderBuilder rbuilder;
+                    auto body = std::string(at, length);
+                    auto* char_data = static_cast<const char*>(&body[0]);
+                    auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
+                    if (!reader->parse(char_data, char_data + body.size(), &proxyInfos, &err)){
+                        context->ok = false;
+                        return 1;
                     }
-                }));
-            }
-            for (auto& r : reqs)
-                r.get();
-            reqs.clear();
-        } catch (const std::exception& e) {
-            DHT_LOG.e("Error sending proxy info request: %s", e.what());
+                    context->cb(proxyInfos);
+                }
+                catch (const std::exception& e) {
+                    if (context->logger)
+                        context->logger->e("[proxy:client] [status] body error: %s", e.what());
+                    context->ok = false;
+                    return 1;
+                }
+                return 0;
+            };
+            if (context->infoState->cancel)
+                return;
+            httpClient_->post_request(request, parser, parser_s);
         }
-        const auto& state = *infoState;
-        if (state.cancel) return;
-        if (state.ipv4 == 0) onProxyInfos(Json::Value{}, AF_INET);
-        if (state.ipv6 == 0) onProxyInfos(Json::Value{}, AF_INET6);
-    });
+    }
+    catch (const std::exception& e) {
+        if (logger_)
+            logger_->e("[proxy:client] [info] error sending request: %s", e.what());
+    }
 }
 
 void
@@ -627,10 +703,14 @@ DhtProxyClient::onProxyInfos(const Json::Value& proxyInfos, sa_family_t family)
     auto oldStatus = std::max(statusIpv4_, statusIpv6_);
     auto& status = family == AF_INET ? statusIpv4_ : statusIpv6_;
     if (not proxyInfos.isMember("node_id")) {
-        DHT_LOG.e("Proxy info request failed for %s", family == AF_INET ? "IPv4" : "IPv6");
+        if (logger_)
+            logger_->e("[proxy:client] [info] request failed for %s",
+                       family == AF_INET ? "ipv4" : "ipv6");
         status = NodeStatus::Disconnected;
     } else {
-        DHT_LOG.d("Got proxy reply for %s", family == AF_INET ? "IPv4" : "IPv6");
+        if (logger_)
+            logger_->d("[proxy:client] [info] got proxy reply for %s",
+                       family == AF_INET ? "ipv4" : "ipv6");
         try {
             myid = InfoHash(proxyInfos["node_id"].asString());
             stats4_ = NodeStats(proxyInfos["ipv4"]);
@@ -649,19 +729,21 @@ DhtProxyClient::onProxyInfos(const Json::Value& proxyInfos, sa_family_t family)
             else if (publicFamily == AF_INET6)
                 publicAddressV6_ = publicIp;
         } catch (const std::exception& e) {
-            DHT_LOG.w("Error processing proxy infos: %s", e.what());
+            if (logger_)
+                logger_->e("[proxy:client] [info] error processing: %s", e.what());
         }
     }
-
     auto newStatus = std::max(statusIpv4_, statusIpv6_);
     if (newStatus == NodeStatus::Connected) {
         if (oldStatus == NodeStatus::Disconnected || oldStatus == NodeStatus::Connecting) {
-            scheduler.edit(listenerRestart, scheduler.time());
+            listenerRestartTimer_->expires_at(std::chrono::steady_clock::now());
         }
-        scheduler.edit(nextProxyConfirmation, scheduler.time() + std::chrono::minutes(15));
+        nextProxyConfirmationTimer_->expires_at(std::chrono::steady_clock::now() +
+            std::chrono::minutes(15));
     }
     else if (newStatus == NodeStatus::Disconnected) {
-        scheduler.edit(nextProxyConfirmation, scheduler.time() + std::chrono::minutes(1));
+        nextProxyConfirmationTimer_->expires_at(std::chrono::steady_clock::now() +
+            std::chrono::minutes(1));
     }
     loopSignal_();
 }
@@ -688,28 +770,31 @@ DhtProxyClient::getPublicAddress(sa_family_t family)
 
 size_t
 DhtProxyClient::listen(const InfoHash& key, ValueCallback cb, Value::Filter filter, Where where) {
-    DHT_LOG.d(key, "[search %s]: listen", key.to_c_str());
+    if (logger_)
+        logger_->d("[proxy:client] [listen] [search %s]", key.to_c_str());
+
     auto& search = searches_[key];
     auto query = std::make_shared<Query>(Select{}, where);
-    auto token = search.ops.listen(cb, query, filter, [this, key, filter](Sp<Query> /*q*/, ValueCallback cb, SyncCallback /*scb*/) -> size_t {
-        scheduler.syncTime();
-        restbed::Uri uri(serverHost_ + "/" + key.toString());
+    auto token = search.ops.listen(cb, query, filter, [this, key, filter](
+                                   Sp<Query>, ValueCallback cb, SyncCallback) -> size_t {
         std::lock_guard<std::mutex> lock(searchLock_);
         auto search = searches_.find(key);
         if (search == searches_.end()) {
-            DHT_LOG.e(key, "[search %s] listen: search not found", key.to_c_str());
+            if (logger_)
+                logger_->e("[proxy:client] [listen] [search %s] search not found", key.to_c_str());
             return 0;
         }
-        DHT_LOG.d(key, "[search %s] sending %s", key.to_c_str(), deviceKey_.empty() ? "listen" : "subscribe");
+        if (logger_)
+            logger_->d("[proxy:client] [listen] [search %s] sending %s", key.to_c_str(),
+                  deviceKey_.empty() ? "listen" : "subscribe");
 
-        auto req = std::make_shared<restbed::Request>(uri);
         auto token = ++listenerToken_;
         auto l = search->second.listeners.find(token);
         if (l == search->second.listeners.end()) {
             auto f = filter;
             l = search->second.listeners.emplace(std::piecewise_construct,
                     std::forward_as_tuple(token),
-                    std::forward_as_tuple(std::move(cb), req, std::move(f))).first;
+                    std::forward_as_tuple(std::move(cb), std::move(f))).first;
         } else {
             if (l->second.state)
                 l->second.state->cancel = true;
@@ -731,18 +816,30 @@ DhtProxyClient::listen(const InfoHash& key, ValueCallback cb, Value::Filter filt
             }
             return l->second.cache.onValue(values, expired);
         };
-
         auto vcb = l->second.cb;
-        l->second.req = req;
 
         if (not deviceKey_.empty()) {
-            // Relaunch push listeners even if a timeout is not received (if the proxy crash for any reason)
-            l->second.refreshJob = scheduler.add(scheduler.time() + proxy::OP_TIMEOUT - proxy::OP_MARGIN, [this, key, token, state] {
+            /*
+             * Relaunch push listeners even if a timeout is not received
+             * (if the proxy crash for any reason)
+             */
+            if (!l->second.refreshTimer)
+                l->second.refreshTimer = std::make_shared<asio::steady_timer>(httpContext_);
+            l->second.refreshTimer->expires_at(std::chrono::steady_clock::now() +
+                    proxy::OP_TIMEOUT - proxy::OP_MARGIN);
+            l->second.refreshTimer->async_wait(
+                [this, key, token, state](const asio::error_code &ec)
+            {
+                if (ec){
+                    if (logger_)
+                        logger_->d("[proxy:client] [listen] refresh error key=%s", key.toString().c_str());
+                    return;
+                }
                 if (state->cancel)
                     return;
                 std::lock_guard<std::mutex> lock(searchLock_);
                 auto s = searches_.find(key);
-                if (s != searches_.end()) {
+                if (s != searches_.end()){
                     auto l = s->second.listeners.find(token);
                     if (l != s->second.listeners.end()) {
                         resubscribe(key, l->second);
@@ -750,10 +847,19 @@ DhtProxyClient::listen(const InfoHash& key, ValueCallback cb, Value::Filter filt
                 }
             });
         }
-        l->second.thread = std::thread([this, req, vcb, filter, state]() {
-            sendListen(req, vcb, filter, state,
-                    deviceKey_.empty() ? ListenMethod::LISTEN : ListenMethod::SUBSCRIBE);
-        });
+        ListenMethod method;
+        restinio::http_request_header_t header;
+        if (deviceKey_.empty()){ // listen
+            method = ListenMethod::LISTEN;
+            header.method(restinio::http_method_get());
+            header.request_target("/" + key.toString() + "/listen");
+        }
+        else {
+            method = ListenMethod::SUBSCRIBE;
+            header.method(restinio::http_method_subscribe());
+            header.request_target("/" + key.toString());
+        }
+        l->second.connId = sendListen(header, vcb, filter, state, method);
         return token;
     });
     return token;
@@ -761,103 +867,126 @@ DhtProxyClient::listen(const InfoHash& key, ValueCallback cb, Value::Filter filt
 
 bool
 DhtProxyClient::cancelListen(const InfoHash& key, size_t gtoken) {
-    scheduler.syncTime();
-    DHT_LOG.d(key, "[search %s]: cancelListen %zu", key.to_c_str(), gtoken);
+    if (logger_)
+        logger_->d(key, "[proxy:client] [search %s] cancel listen %zu", key.to_c_str(), gtoken);
     auto it = searches_.find(key);
     if (it == searches_.end())
         return false;
     auto& ops = it->second.ops;
-    bool canceled = ops.cancelListen(gtoken, scheduler.time());
-    if (not it->second.opExpirationJob) {
-        it->second.opExpirationJob = scheduler.add(time_point::max(), [this,key](){
+    bool canceled = ops.cancelListen(gtoken, std::chrono::steady_clock::now());
+    // on new listener set the expiration to the max,
+    // in case a user redo a listen right after cancel, we won't impact the network.
+    if (!it->second.opExpirationTimer) {
+        it->second.opExpirationTimer = std::make_shared<asio::steady_timer>(httpContext_);
+        it->second.opExpirationTimer->expires_at(time_point::max());
+        it->second.opExpirationTimer->async_wait([this, key](const asio::error_code ec){
+            if (ec){
+                if (logger_)
+                    logger_->d("[proxy:client] [listen %s] error in cancel", key.toString().c_str());
+                return false;
+            }
             auto it = searches_.find(key);
             if (it != searches_.end()) {
-                auto next = it->second.ops.expire(scheduler.time(), [this,key](size_t ltoken){
+                auto next = it->second.ops.expire(std::chrono::steady_clock::now(),
+                                                 [this, key](size_t ltoken){
                     doCancelListen(key, ltoken);
                 });
                 if (next != time_point::max()) {
-                    scheduler.edit(it->second.opExpirationJob, next);
+                    if (!it->second.opExpirationTimer){
+                        it->second.opExpirationTimer = std::make_shared<
+                            asio::steady_timer>(httpContext_);
+                    }
+                    it->second.opExpirationTimer->expires_at(next);
                 }
             }
         });
     }
-    scheduler.edit(it->second.opExpirationJob, ops.getExpiration());
+    // Let it expire when it is due.
+    it->second.opExpirationTimer->expires_at(ops.getExpiration());
     loopSignal_();
     return canceled;
 }
 
-void DhtProxyClient::sendListen(const std::shared_ptr<restbed::Request> &req,
-                                const ValueCallback &cb,
-                                const Value::Filter &filter,
-                                const Sp<ListenState> &state,
-                                ListenMethod method) {
-    auto settings = std::make_shared<restbed::Settings>();
-    if (method != ListenMethod::LISTEN) {
-        req->set_method("SUBSCRIBE");
-    } else {
-        std::chrono::milliseconds timeout(std::numeric_limits<int>::max());
-        settings->set_connection_timeout(timeout); // Avoid the client to close the socket after 5 seconds.
-        req->set_method("LISTEN");
-    }
-    try {
+uint16_t
+DhtProxyClient::sendListen(const restinio::http_request_header_t header,
+                           const ValueCallback &cb, const Value::Filter &filter,
+                           const Sp<ListenState> &state, ListenMethod method)
+{
+    auto headers = this->initHeaderFields();
+    auto conn = restinio::http_connection_header_t::close;
+    if (method == ListenMethod::LISTEN)
+        conn = restinio::http_connection_header_t::keep_alive;
+    std::string body;
 #ifdef OPENDHT_PUSH_NOTIFICATIONS
-        if (method != ListenMethod::LISTEN)
-        fillBody(req, method == ListenMethod::RESUBSCRIBE);
-    #endif
-        restbed::Http::async(req,
-              [this, filter, cb, state](const std::shared_ptr<restbed::Request>& req,
-                                        const std::shared_ptr<restbed::Response>& reply)
-        {
-            auto code = reply->get_status_code();
-            if (code == 200) {
-                try {
-                    while (restbed::Http::is_open(req) and not state->cancel) {
-                        restbed::Http::fetch("\n", reply);
-                        if (state->cancel)
-                            break;
-                        std::string body;
-                        reply->get_body(body);
-                        reply->set_body(""); // Reset the body for the next fetch
-
-                        Json::Value json;
-                        std::string err;
-                        Json::CharReaderBuilder rbuilder;
-                        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-                        if (reader->parse(body.data(), body.data() + body.size(), &json, &err)) {
-                            if (json.size() == 0) {
-                                // Empty value, it's the end
-                                break;
-                            }
-                            auto expired = json.get("expired", Json::Value(false)).asBool();
-                            auto value = std::make_shared<Value>(json);
-                            if ((not filter or filter(*value)) and cb) {
-                                {
-                                    std::lock_guard<std::mutex> lock(lockCallbacks);
-                                    callbacks_.emplace_back([cb, value, state, expired]() {
-                                        if (not state->cancel and not cb({value}, expired))
-                                            state->cancel = true;
-                                    });
-                                }
-                                loopSignal_();
-                            }
-                        }
-                    }
-                } catch (const std::exception& e) {
-                    if (not state->cancel) {
-                        DHT_LOG.w("Listen closed by the proxy server: %s", e.what());
-                        state->ok = false;
-                    }
-                }
-            } else {
-                state->ok = false;
+    if (method != ListenMethod::LISTEN)
+        body = fillBody(method == ListenMethod::RESUBSCRIBE);
+#endif
+    auto request = httpClient_->create_request(header, headers, conn, body);
+    if (logger_)
+        logger_->d(request.c_str());
+
+    struct ListenContext {
+        std::shared_ptr<Logger> logger;
+        ValueCallback cb; // wrapper
+        Value::Filter filter;
+        std::shared_ptr<ListenState> state;
+    };
+    auto context = std::make_shared<ListenContext>();
+    if (logger_)
+        context->logger = logger_;
+    // keeping context data alive
+    context->cb = [context, cb](const std::vector<std::shared_ptr<Value>>& values, bool expired){
+        return cb(values, expired);
+    };
+    context->state = state;
+    context->filter = filter;
+
+    auto parser = std::make_shared<http_parser>();
+    http_parser_init(parser.get(), HTTP_RESPONSE);
+    parser->data = static_cast<void*>(context.get());
+
+    auto parser_s = std::make_shared<http_parser_settings>();
+    http_parser_settings_init(parser_s.get());
+    parser_s->on_status = [](http_parser *parser, const char *at, size_t length) -> int {
+        auto context = static_cast<ListenContext*>(parser->data);
+        if (parser->status_code != 200){
+            if (context->logger)
+                context->logger->e("[proxy:client] [listen] status error: %i", parser->status_code);
+            context->state->ok = false;
+        }
+        return 0;
+    };
+    parser_s->on_body = [](http_parser *parser, const char *at, size_t length) -> int {
+        auto context = static_cast<ListenContext*>(parser->data);
+        try {
+            Json::Value json;
+            std::string err;
+            Json::CharReaderBuilder rbuilder;
+            auto body = std::string(at, length);
+            auto* char_data = static_cast<const char*>(&body[0]);
+            auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
+            if (!reader->parse(char_data, char_data + body.size(), &json, &err)){
+                context->state->ok = false;
+                return 1;
             }
-        }, settings).get();
-    } catch (const std::exception& e) {
-        state->ok = false;
-    }
-    auto& s = *state;
-    if (not s.ok and not s.cancel)
-        opFailed();
+            if (json.size() == 0){ // it's the end
+                context->state->cancel = true;
+            }
+            auto value = std::make_shared<Value>(json);
+            auto expired = json.get("expired", Json::Value(false)).asBool();
+            if ((not context->filter or context->filter(*value)) and context->cb){
+                context->cb({value}, expired);
+            }
+        } catch(const std::exception& e) {
+            if (context->logger)
+                context->logger->e("[proxy:client] [listen] error in parsing: %s", e.what());
+            context->state->ok = false;
+            return 1;
+        }
+        return 0;
+    };
+    auto connId = httpClient_->post_request(request, parser, parser_s);
+    return connId;
 }
 
 bool
@@ -873,19 +1002,17 @@ DhtProxyClient::doCancelListen(const InfoHash& key, size_t ltoken)
     if (it == search->second.listeners.end())
         return false;
 
-    DHT_LOG.d(key, "[search %s] cancel listen", key.to_c_str());
+    if (logger_)
+        logger_->d("[proxy:client] [listen:cancel] [search %s]", key.to_c_str());
 
     auto& listener = it->second;
     listener.state->cancel = true;
     if (not deviceKey_.empty()) {
-        // First, be sure to have a token
-        if (listener.thread.joinable()) {
-            listener.thread.join();
-        }
         // UNSUBSCRIBE
-        restbed::Uri uri(serverHost_ + "/" + key.toString());
-        auto req = std::make_shared<restbed::Request>(uri);
-        req->set_method("UNSUBSCRIBE");
+        restinio::http_request_header_t header;
+        header.request_target("/" + key.toString());
+        header.method(restinio::http_method_unsubscribe());
+        auto header_fields = this->initHeaderFields();
         // fill request body
         Json::Value body;
         body["key"] = deviceKey_;
@@ -895,41 +1022,64 @@ DhtProxyClient::doCancelListen(const InfoHash& key, size_t ltoken)
         wbuilder["indentation"] = "";
         auto content = Json::writeString(wbuilder, body) + "\n";
         std::replace(content.begin(), content.end(), '\n', ' ');
-        req->set_body(content);
-        req->set_header("Content-Length", std::to_string(content.size()));
-        try {
-            restbed::Http::async(req, [](const std::shared_ptr<restbed::Request>&, const std::shared_ptr<restbed::Response>&){});
-        } catch (const std::exception& e) {
-            DHT_LOG.w(key, "[search %s] cancelListen: Http::async failed: %s", key.to_c_str(), e.what());
-        }
+        // build the request
+        auto request = httpClient_->create_request(header, header_fields,
+            restinio::http_connection_header_t::keep_alive, content);
+        if (logger_)
+            logger_->d(request.c_str());
+        // define context
+        struct UnsubscribeContext {
+            InfoHash key;
+            std::shared_ptr<dht::Logger> logger;
+        };
+        auto context = std::make_shared<UnsubscribeContext>();
+        context->key = key;
+        if (logger_)
+            context->logger = logger_;
+        // define parser
+        auto parser = std::make_shared<http_parser>();
+        http_parser_init(parser.get(), HTTP_RESPONSE);
+        parser->data = static_cast<void*>(context.get());
+        // define callbacks
+        auto parser_s = std::make_shared<http_parser_settings>();
+        http_parser_settings_init(parser_s.get());
+        parser_s->on_status = [](http_parser *parser, const char *at, size_t length) -> int {
+            auto context = static_cast<UnsubscribeContext*>(parser->data);
+            if (parser->status_code != 200){
+                if (context->logger)
+                    context->logger->e("[proxy:client] [search %s] cancel listen failed: %i",
+                                       context->key.to_c_str(), parser->status_code);
+            }
+            return 0;
+        };
+        httpClient_->post_request(request, parser, parser_s);
     } else {
         // Just stop the request
-        if (listener.thread.joinable()) {
-            // Close connection to stop listener
-            if (listener.req) {
-                try {
-                    restbed::Http::close(listener.req);
-                } catch (const std::exception& e) {
-                    DHT_LOG.w("Error closing socket: %s", e.what());
-                }
-                listener.req.reset();
+        if (httpClient_->active_connection(listener.connId)){
+            try {
+                httpClient_->close_connection(listener.connId);
+            }
+            catch (const std::exception& e){
+                if (logger_)
+                    logger_->e("[proxy:client] [listen:cancel] error closing socket: %s", e.what());
             }
-            listener.thread.join();
         }
     }
     search->second.listeners.erase(it);
-    DHT_LOG.d(key, "[search %s] cancelListen: %zu listener remaining", key.to_c_str(), search->second.listeners.size());
-    if (search->second.listeners.empty()) {
+    if (logger_)
+        logger_->d("[proxy:client] [listen:cancel] [search %s] %zu listener remaining",
+                   key.to_c_str(), search->second.listeners.size());
+    if (search->second.listeners.empty()){
         searches_.erase(search);
     }
-
     return true;
 }
 
 void
 DhtProxyClient::opFailed()
 {
-    DHT_LOG.e("Proxy request failed");
+    if (logger_)
+        logger_->e("[proxy:client] proxy request failed");
     {
         std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
         statusIpv4_ = NodeStatus::Disconnected;
@@ -942,7 +1092,8 @@ DhtProxyClient::opFailed()
 void
 DhtProxyClient::getConnectivityStatus()
 {
-    if (!isDestroying_) getProxyInfos();
+    if (!isDestroying_)
+        getProxyInfos();
 }
 
 void
@@ -950,7 +1101,8 @@ DhtProxyClient::restartListeners()
 {
     if (isDestroying_) return;
     std::lock_guard<std::mutex> lock(searchLock_);
-    DHT_LOG.d("Refresh permanent puts");
+    if (logger_)
+        logger_->d("[proxy:client] [listeners:restart] refresh permanent puts");
     for (auto& search : searches_) {
         for (auto& put : search.second.puts) {
             if (!*put.second.ok) {
@@ -959,12 +1111,18 @@ DhtProxyClient::restartListeners()
                 [ok](bool result, const std::vector<std::shared_ptr<dht::Node> >&){
                     *ok = result;
                 }, time_point::max(), true);
-                scheduler.edit(put.second.refreshJob, scheduler.time() + proxy::OP_TIMEOUT - proxy::OP_MARGIN);
+                if (!put.second.refreshTimer){
+                    put.second.refreshTimer = std::make_shared<
+                        asio::steady_timer>(httpContext_);
+                }
+                put.second.refreshTimer->expires_at(std::chrono::steady_clock::now() +
+                    proxy::OP_TIMEOUT - proxy::OP_MARGIN);
             }
         }
     }
     if (not deviceKey_.empty()) {
-        DHT_LOG.d("resubscribe due to a connectivity change");
+        if (logger_)
+            logger_->d("[proxy:client] [listeners:restart] resubscribe due to a connectivity change");
         // Connectivity changed, refresh all subscribe
         for (auto& search : searches_)
             for (auto& listener : search.second.listeners)
@@ -972,19 +1130,21 @@ DhtProxyClient::restartListeners()
                     resubscribe(search.first, listener.second);
         return;
     }
-    DHT_LOG.d("Restarting listeners");
+    if (logger_)
+        logger_->d("[proxy:client] [listeners:restart] restarting listeners");
     for (auto& search: searches_) {
         for (auto& l: search.second.listeners) {
             auto& listener = l.second;
             if (auto state = listener.state)
                 state->cancel = true;
-            if (listener.req) {
+            if (httpClient_->active_connection(listener.connId)){
                 try {
-                    restbed::Http::close(listener.req);
+                    httpClient_->close_connection(listener.connId);
                 } catch (const std::exception& e) {
-                    DHT_LOG.w("Error closing socket: %s", e.what());
+                    if (logger_)
+                        logger_->e("[proxy:client] [listeners:restart] error closing socket: %s", e.what());
                 }
-                listener.req.reset();
+                l.second.connId = 0;
             }
         }
     }
@@ -992,21 +1152,17 @@ DhtProxyClient::restartListeners()
         for (auto& l: search.second.listeners) {
             auto& listener = l.second;
             auto state = listener.state;
-            if (listener.thread.joinable()) {
-                listener.thread.join();
-            }
             // Redo listen
             state->cancel = false;
             state->ok = true;
             auto filter = listener.filter;
             auto cb = listener.cb;
-            restbed::Uri uri(serverHost_ + "/" + search.first.toString());
-            auto req = std::make_shared<restbed::Request>(uri);
-            req->set_method("LISTEN");
-            listener.req = req;
-            listener.thread = std::thread([this, req, cb, filter, state]() {
-                sendListen(req, cb, filter, state);
-            });
+            // define header
+            restinio::http_request_header_t header;
+            header.method(restinio::http_method_get());
+            header.request_target("/" + search.first.toString() + "/listen");
+            // send listen
+            listener.connId = sendListen(header, cb, filter, state, ListenMethod::LISTEN);
         }
     }
 }
@@ -1015,7 +1171,6 @@ void
 DhtProxyClient::pushNotificationReceived(const std::map<std::string, std::string>& notification)
 {
 #ifdef OPENDHT_PUSH_NOTIFICATIONS
-    scheduler.syncTime();
     {
         // If a push notification is received, the proxy is up and running
         std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
@@ -1033,7 +1188,11 @@ DhtProxyClient::pushNotificationReceived(const std::map<std::string, std::string
                 // Refresh put
                 auto vid = std::stoull(vidIt->second);
                 auto& put = search.puts.at(vid);
-                scheduler.edit(put.refreshJob, scheduler.time());
+                if (!put.refreshTimer){
+                    put.refreshTimer = std::make_shared<
+                        asio::steady_timer>(httpContext_);
+                }
+                put.refreshTimer->expires_at(std::chrono::steady_clock::now());
                 loopSignal_();
             } else {
                 // Refresh listen
@@ -1046,7 +1205,8 @@ DhtProxyClient::pushNotificationReceived(const std::map<std::string, std::string
             for (auto& list : search.listeners) {
                 if (list.second.state->cancel)
                     continue;
-                DHT_LOG.d(key, "[search %s] handling push notification", key.to_c_str());
+                if (logger_)
+                    logger_->d("[proxy:client] [push:received] [search %s] handling", key.to_c_str());
                 auto expired = notification.find("exp");
                 auto token = list.first;
                 auto state = list.second.state;
@@ -1057,7 +1217,8 @@ DhtProxyClient::pushNotificationReceived(const std::map<std::string, std::string
                     get(key, [cb](const std::vector<Sp<Value>>& vals) {
                         return cb(vals, false);
                     }, [cb, oldValues](bool /*ok*/) {
-                        // Decrement old values refcount to expire values not present in the new list
+                        // Decrement old values refcount to expire values not
+                        // present in the new list
                         cb(oldValues, true);
                     }, std::move(filter));
                 } else {
@@ -1069,14 +1230,17 @@ DhtProxyClient::pushNotificationReceived(const std::map<std::string, std::string
                         ids.emplace_back(std::stoull(substr));
                     }
                     {
-                        std::lock_guard<std::mutex> lock(lockCallbacks);
+                        std::lock_guard<std::mutex> lock(lockCallbacks_);
                         callbacks_.emplace_back([this, key, token, state, ids]() {
-                            if (state->cancel) return;
+                            if (state->cancel)
+                                return;
                             std::lock_guard<std::mutex> lock(searchLock_);
                             auto s = searches_.find(key);
-                            if (s == searches_.end()) return;
+                            if (s == searches_.end())
+                                return;
                             auto l = s->second.listeners.find(token);
-                            if (l == s->second.listeners.end()) return;
+                            if (l == s->second.listeners.end())
+                                return;
                             if (not state->cancel and not l->second.cache.onValuesExpired(ids))
                                 state->cancel = true;
                         });
@@ -1086,7 +1250,8 @@ DhtProxyClient::pushNotificationReceived(const std::map<std::string, std::string
             }
         }
     } catch (const std::exception& e) {
-        DHT_LOG.e("Error handling push notification: %s", e.what());
+        if (logger_)
+            logger_->e("[proxy:client] [push:received] error handling: %s", e.what());
     }
 #else
     (void) notification;
@@ -1097,34 +1262,35 @@ void
 DhtProxyClient::resubscribe(const InfoHash& key, Listener& listener)
 {
 #ifdef OPENDHT_PUSH_NOTIFICATIONS
-    if (deviceKey_.empty()) return;
-    scheduler.syncTime();
-    DHT_LOG.d(key, "[search %s] resubscribe push listener", key.to_c_str());
+    if (deviceKey_.empty())
+        return;
+    if (logger_)
+        logger_->d("[proxy:client] [resubscribe] [search %s] resubscribe push listener", key.to_c_str());
     // Subscribe
     auto state = listener.state;
-    if (listener.thread.joinable()) {
-        state->cancel = true;
-        if (listener.req) {
-            try {
-                restbed::Http::close(listener.req);
-            } catch (const std::exception& e) {
-                DHT_LOG.w("Error closing socket: %s", e.what());
-            }
-            listener.req.reset();
+    state->cancel = true;
+    if (listener.connId) {
+        try {
+            httpClient_->close_connection(listener.connId);
+        } catch (const std::exception& e) {
+            if (logger_)
+                logger_->e("[proxy:client] [resubscribe] error closing socket: %s", e.what());
         }
-        listener.thread.join();
     }
     state->cancel = false;
     state->ok = true;
-    auto req = std::make_shared<restbed::Request>(restbed::Uri {serverHost_ + "/" + key.toString()});
-    req->set_method("SUBSCRIBE");
-    listener.req = req;
-    scheduler.edit(listener.refreshJob, scheduler.time() + proxy::OP_TIMEOUT - proxy::OP_MARGIN);
+
+    restinio::http_request_header_t header;
+    header.method(restinio::http_method_subscribe());
+    header.request_target("/" + key.toString());
+    if (!listener.refreshTimer){
+        listener.refreshTimer = std::make_shared<asio::steady_timer>(httpContext_);
+    }
+    listener.refreshTimer->expires_at(std::chrono::steady_clock::now() +
+                                      proxy::OP_TIMEOUT - proxy::OP_MARGIN);
     auto vcb = listener.cb;
     auto filter = listener.filter;
-    listener.thread = std::thread([this, req, vcb, filter, state]() {
-        sendListen(req, vcb, filter, state, ListenMethod::RESUBSCRIBE);
-    });
+    listener.connId = sendListen(header, vcb, filter, state, ListenMethod::RESUBSCRIBE);
 #else
     (void) key;
     (void) listener;
@@ -1145,8 +1311,8 @@ DhtProxyClient::getPushRequest(Json::Value& body) const
 #endif
 }
 
-void
-DhtProxyClient::fillBody(std::shared_ptr<restbed::Request> req, bool resubscribe)
+std::string
+DhtProxyClient::fillBody(bool resubscribe)
 {
     // Fill body with
     // {
@@ -1163,8 +1329,7 @@ DhtProxyClient::fillBody(std::shared_ptr<restbed::Request> req, bool resubscribe
     wbuilder["indentation"] = "";
     auto content = Json::writeString(wbuilder, body) + "\n";
     std::replace(content.begin(), content.end(), '\n', ' ');
-    req->set_body(content);
-    req->set_header("Content-Length", std::to_string(content.size()));
+    return content;
 }
 #endif // OPENDHT_PUSH_NOTIFICATIONS
 
diff --git a/src/dht_proxy_server.cpp b/src/dht_proxy_server.cpp
index 744ba986dc7cc1a3abcc14e4a44a319cf49d2f68..3e029b42300fc461931c2a4788526e08a5eeed8d 100644
--- a/src/dht_proxy_server.cpp
+++ b/src/dht_proxy_server.cpp
@@ -2,6 +2,7 @@
  *  Copyright (C) 2017-2019 Savoir-faire Linux Inc.
  *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
  *          Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *          Vsevolod Ivanov <vsevolod.ivanov@savoirfairelinux.com>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -19,7 +20,6 @@
 
 #include "dht_proxy_server.h"
 
-#include "thread_pool.h"
 #include "default_types.h"
 #include "dhtrunner.h"
 
@@ -35,123 +35,75 @@ using namespace std::placeholders;
 
 namespace dht {
 
-struct DhtProxyServer::PermanentPut {
-    time_point expiration;
-    std::string pushToken;
-    std::string clientId;
-    Sp<Scheduler::Job> expireJob;
-    Sp<Scheduler::Job> expireNotifyJob;
-};
-struct DhtProxyServer::SearchPuts {
-    std::map<dht::Value::Id, PermanentPut> puts;
-};
+constexpr char RESP_MSG_DESTINATION_NOT_FOUND[] = "{\"err\":\"No destination found\"}";
+constexpr char RESP_MSG_NO_TOKEN[] = "{\"err\":\"No token\"}";
+constexpr char RESP_MSG_JSON_NOT_ENABLED[] = "{\"err\":\"JSON not enabled on this instance\"}";
+constexpr char RESP_MSG_JSON_INCORRECT[] = "{\"err:\":\"Incorrect JSON\"}";
+constexpr char RESP_MSG_SERVICE_UNAVAILABLE[] = "{\"err\":\"Incorrect DhtRunner\"}";
+constexpr char RESP_MSG_INTERNAL_SERVER_ERRROR[] = "{\"err\":\"Internal server error\"}";
+constexpr char RESP_MSG_MISSING_PARAMS[] = "{\"err\":\"Missing parameters\"}";
+constexpr char RESP_MSG_PUT_FAILED[] = "{\"err\":\"Put failed\"}";
 
 constexpr const std::chrono::minutes PRINT_STATS_PERIOD {2};
-constexpr const size_t IO_THREADS_MAX {64};
 
-
-DhtProxyServer::DhtProxyServer(std::shared_ptr<DhtRunner> dht, in_port_t port , const std::string& pushServer)
-: dht_(dht), threadPool_(new ThreadPool(IO_THREADS_MAX)), pushServer_(pushServer)
+DhtProxyServer::DhtProxyServer(std::shared_ptr<DhtRunner> dht, in_port_t port,
+                              const std::string& pushServer,
+                              std::shared_ptr<dht::Logger> logger
+):
+        dht_(dht), logger_(logger), lockListener_(std::make_shared<std::mutex>()),
+        listeners_(std::make_shared<std::map<restinio::connection_id_t, http::ListenerSession>>()),
+        connListener_(std::make_shared<http::ConnectionListener>(
+                        dht, listeners_, lockListener_, logger)),
+        pushServer_(pushServer)
 {
     if (not dht_)
         throw std::invalid_argument("A DHT instance must be provided");
-    // NOTE in c++14, use make_unique
-    service_ = std::unique_ptr<restbed::Service>(new restbed::Service());
 
-    std::cout << "Running DHT proxy server on port " << port << std::endl;
-    if (not pushServer.empty()) {
+    if (logger_)
+        logger_->d("[proxy:server] [init] running on %i", port);
+    if (not pushServer.empty()){
 #ifdef OPENDHT_PUSH_NOTIFICATIONS
-        std::cout << "Using push notification server: " << pushServer << std::endl;
+        if (logger_)
+            logger_->d("[proxy:server] [init] using push server %s", pushServer.c_str());
 #else
-        std::cerr << "Push server defined but built OpenDHT built without push notification support" << std::endl;
+        if (logger_)
+            logger_->e("[proxy:server] [init] opendht built without push notification support");
 #endif
     }
 
     jsonBuilder_["commentStyle"] = "None";
     jsonBuilder_["indentation"] = "";
 
-    server_thread = std::thread([this, port]() {
-        // Create endpoints
-        auto resource = std::make_shared<restbed::Resource>();
-        resource->set_path("/");
-        resource->set_method_handler("GET", std::bind(&DhtProxyServer::getNodeInfo, this, _1));
-        resource->set_method_handler("STATS", std::bind(&DhtProxyServer::getStats, this, _1));
-        service_->publish(resource);
-        resource = std::make_shared<restbed::Resource>();
-        resource->set_path("/{hash: .*}");
-        resource->set_method_handler("GET", std::bind(&DhtProxyServer::get, this, _1));
-        resource->set_method_handler("LISTEN", [this](const Sp<restbed::Session>& session) mutable { listen(session); } );
-#ifdef OPENDHT_PUSH_NOTIFICATIONS
-        resource->set_method_handler("SUBSCRIBE", [this](const Sp<restbed::Session>& session) mutable { subscribe(session); } );
-        resource->set_method_handler("UNSUBSCRIBE", [this](const Sp<restbed::Session>& session) mutable { unsubscribe(session); } );
-#endif //OPENDHT_PUSH_NOTIFICATIONS
-        resource->set_method_handler("POST", [this](const Sp<restbed::Session>& session) mutable { put(session); });
-#ifdef OPENDHT_PROXY_SERVER_IDENTITY
-        resource->set_method_handler("SIGN", std::bind(&DhtProxyServer::putSigned, this, _1));
-        resource->set_method_handler("ENCRYPT", std::bind(&DhtProxyServer::putEncrypted, this, _1));
-#endif // OPENDHT_PROXY_SERVER_IDENTITY
-        resource->set_method_handler("OPTIONS", std::bind(&DhtProxyServer::handleOptionsMethod, this, _1));
-        service_->publish(resource);
-        resource = std::make_shared<restbed::Resource>();
-        resource->set_path("/{hash: .*}/{value: .*}");
-        resource->set_method_handler("GET", std::bind(&DhtProxyServer::getFiltered, this, _1));
-        service_->publish(resource);
-
-        // Start server
-        auto settings = std::make_shared<restbed::Settings>();
-        settings->set_default_header("Content-Type", "application/json");
-        settings->set_default_header("Connection", "keep-alive");
-        settings->set_default_header("Access-Control-Allow-Origin", "*");
-        std::chrono::milliseconds timeout(std::numeric_limits<int>::max());
-        settings->set_connection_timeout(timeout); // there is a timeout, but really huge
-        settings->set_port(port);
-        auto maxThreads = std::thread::hardware_concurrency() - 1;
-        settings->set_worker_limit(maxThreads > 1 ? maxThreads : 1);
-        lastStatsReset_ = clock::now();
+    // build http server
+    auto settings = makeHttpServerSettings();
+    settings.port(port);
+    httpServer_.reset(new restinio::http_server_t<RestRouterTraits>(
+        restinio::own_io_context(),
+        std::forward<ServerSettings>(settings)
+    ));
+    // build http client
+    auto pushHostPort = splitPort(pushServer_);
+    uint16_t pushPort = std::atoi(pushHostPort.second.c_str());
+    httpClient_.reset(new http::Client(httpServer_->io_context(),
+                                       pushHostPort.first, pushPort, logger_));
+    // run http server
+    httpServerThread_ = std::thread([this](){
         try {
-            service_->start(settings);
-        } catch(std::system_error& e) {
-            std::cerr << "Error running server on port " << port << ": " << e.what() << std::endl;
-        }
-    });
-
-    listenThread_ = std::thread([this]() {
-        while (not service_->is_up() and not stopListeners) {
-            std::this_thread::sleep_for(std::chrono::seconds(1));
-        }
-        while (service_->is_up() and not stopListeners) {
-            removeClosedListeners();
-            std::this_thread::sleep_for(std::chrono::seconds(1));
+            httpServer_->open_async([]{/*Ok.*/}, [](std::exception_ptr ex){
+                std::rethrow_exception(ex);
+            });
+            httpServer_->io_context().run();
         }
-        // Remove last listeners
-        removeClosedListeners(false);
-    });
-    schedulerThread_ = std::thread([this]() {
-        while (not service_->is_up() and not stopListeners) {
-            std::this_thread::sleep_for(std::chrono::seconds(1));
-        }
-        while (service_->is_up()  and not stopListeners) {
-            std::unique_lock<std::mutex> lock(schedulerLock_);
-            auto next = scheduler_.run();
-            if (next == time_point::max())
-                schedulerCv_.wait(lock);
-            else
-                schedulerCv_.wait_until(lock, next);
+        catch(const std::exception &ex){
+            if (logger_)
+                logger_->e("[proxy:server] error starting: %s", ex.what());
         }
     });
     dht->forwardAllMessages(true);
-    printStatsJob_ = scheduler_.add(scheduler_.time() + PRINT_STATS_PERIOD, [this] {
-        if (stopListeners) return;
-        if (service_->is_up())
-            updateStats();
-        // Refresh stats cache
-        auto newInfo = dht_->getNodeInfo();
-        {
-            std::lock_guard<std::mutex> lck(statsMutex_);
-            nodeInfo_ = std::move(newInfo);
-        }
-        scheduler_.edit(printStatsJob_, scheduler_.time() + PRINT_STATS_PERIOD);
-    });
+
+    printStatsTimer_ = std::make_unique<asio::steady_timer>(
+        httpServer_->io_context(), PRINT_STATS_PERIOD);
+    printStatsTimer_->async_wait(std::bind(&DhtProxyServer::asyncPrintStats, this));
 }
 
 DhtProxyServer::~DhtProxyServer()
@@ -159,30 +111,49 @@ DhtProxyServer::~DhtProxyServer()
     stop();
 }
 
+ServerSettings
+DhtProxyServer::makeHttpServerSettings(const unsigned int max_pipelined_requests)
+{
+    using namespace std::chrono;
+    auto settings = ServerSettings();
+    /**
+     * If max_pipelined_requests is greater than 1 then RESTinio will continue
+     * to read from the socket after parsing the first request.
+     * In that case, RESTinio can detect the disconnection
+     * and calls state listener as expected.
+     * https://github.com/Stiffstream/restinio/issues/28
+     */
+    settings.max_pipelined_requests(max_pipelined_requests);
+    // one less to detect the listener disconnect
+    settings.concurrent_accepts_count(max_pipelined_requests - 1);
+    settings.separate_accept_and_create_connect(true);
+    settings.logger(logger_);
+    settings.protocol(restinio::asio_ns::ip::tcp::v6());
+    settings.request_handler(this->createRestRouter());
+    // time limits                                              // ~ 0.8 month
+    std::chrono::milliseconds timeout_request(std::numeric_limits<int>::max());
+    settings.read_next_http_message_timelimit(timeout_request);
+    settings.write_http_response_timelimit(60s);
+    settings.handle_request_timeout(timeout_request);
+    // socket options
+    settings.socket_options_setter([](auto & options){
+        options.set_option(asio::ip::tcp::no_delay{true});
+    });
+    settings.connection_state_listener(connListener_);
+    return settings;
+}
+
 void
 DhtProxyServer::stop()
 {
-    if (printStatsJob_)
-        printStatsJob_->cancel();
-    service_->stop();
-    {
-        std::lock_guard<std::mutex> lock(lockListener_);
-        auto listener = currentListeners_.begin();
-        while (listener != currentListeners_.end()) {
-            listener->session->close();
-            ++listener;
-        }
-    }
-    stopListeners = true;
-    schedulerCv_.notify_all();
-    // listenThreads_ will stop because there is no more sessions
-    if (listenThread_.joinable())
-        listenThread_.join();
-    if (schedulerThread_.joinable())
-        schedulerThread_.join();
-    if (server_thread.joinable())
-        server_thread.join();
-    threadPool_->stop();
+    if (logger_)
+        logger_->d("[proxy:server] closing http server async operations");
+    httpServer_->io_context().reset();
+    httpServer_->io_context().stop();
+    if (httpServerThread_.joinable())
+        httpServerThread_.join();
+    if (logger_)
+        logger_->d("[proxy:server] http server closed");
 }
 
 void
@@ -197,362 +168,428 @@ DhtProxyServer::updateStats() const
     stats_.pushListenersCount = pushListeners_.size();
 #endif
     stats_.putCount = puts_.size();
-    stats_.listenCount = currentListeners_.size();
+    stats_.listenCount = listeners_->size();
     stats_.nodeInfo = nodeInfo_;
 }
 
 void
-DhtProxyServer::getNodeInfo(const Sp<restbed::Session>& session) const
+DhtProxyServer::asyncPrintStats()
 {
-    requestNum_++;
-    const auto request = session->get_request();
-    int content_length = std::stoi(request->get_header("Content-Length", "0"));
-    session->fetch(content_length,
-        [this](const Sp<restbed::Session>& s, const restbed::Bytes& /*b*/) mutable
-        {
-            try {
-                if (dht_) {
-                    Json::Value result;
-                    {
-                        std::lock_guard<std::mutex> lck(statsMutex_);
-                        if (nodeInfo_.ipv4.good_nodes == 0 && nodeInfo_.ipv6.good_nodes == 0) {
-                            // NOTE: we want to avoid the disconnected state as much as possible
-                            // So, if the node is disconnected, we should force the update of the cache
-                            // and reconnect as soon as possible
-                            // This should not happen much
-                            nodeInfo_ = dht_->getNodeInfo();
-                        }
-                        result = nodeInfo_.toJson();
-                    }
-                    result["public_ip"] = s->get_origin(); // [ipv6:ipv4]:port or ipv4:port
-                    auto output = Json::writeString(jsonBuilder_, result) + "\n";
-                    s->close(restbed::OK, output);
-                }
-                else
-                    s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
-            } catch (...) {
-                s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
-            }
-        }
-    );
+    if (httpServer_->io_context().stopped())
+        return;
+
+    if (dht_){
+        updateStats();
+        // Refresh stats cache
+        auto newInfo = dht_->getNodeInfo();
+        std::lock_guard<std::mutex> lck(statsMutex_);
+        nodeInfo_ = std::move(newInfo);
+        auto json = nodeInfo_.toJson();
+        auto str = Json::writeString(jsonBuilder_, json);
+        if (logger_)
+            logger_->d("[proxy:server] [stats] %s", str.c_str());
+    }
+    printStatsTimer_->expires_at(printStatsTimer_->expiry() + PRINT_STATS_PERIOD);
+    printStatsTimer_->async_wait(std::bind(&DhtProxyServer::asyncPrintStats, this));
 }
 
-void
-DhtProxyServer::getStats(const Sp<restbed::Session>& session) const
+template <typename HttpResponse>
+HttpResponse DhtProxyServer::initHttpResponse(HttpResponse response) const
+{
+    response.append_header("Server", "RESTinio");
+    response.append_header(restinio::http_field::content_type, "application/json");
+    response.append_header(restinio::http_field::access_control_allow_origin, "*");
+    response.connection_keep_alive();
+    return response;
+}
+
+std::unique_ptr<RestRouter>
+DhtProxyServer::createRestRouter()
+{
+    using namespace std::placeholders;
+    auto router = std::make_unique<RestRouter>();
+    router->http_get("/", std::bind(&DhtProxyServer::getNodeInfo, this, _1, _2));
+    // LEGACY STATS ROUTE
+    router->add_handler(restinio::custom_http_methods_t::from_nodejs(restinio::method_stats.raw_id()),
+                        "/", std::bind(&DhtProxyServer::getStats, this, _1, _2));
+    // }
+    router->http_get("/stats", std::bind(&DhtProxyServer::getStats, this, _1, _2));
+    router->http_get("/:hash", std::bind(&DhtProxyServer::get, this, _1, _2));
+    router->http_post("/:hash", std::bind(&DhtProxyServer::put, this, _1, _2));
+    // LEGACY LISTEN ROUTE
+    router->add_handler(restinio::custom_http_methods_t::from_nodejs(restinio::method_listen.raw_id()),
+                        "/:hash", std::bind(&DhtProxyServer::listen, this, _1, _2));
+    // }
+    router->http_get("/:hash/listen", std::bind(&DhtProxyServer::listen, this, _1, _2));
+#ifdef OPENDHT_PUSH_NOTIFICATIONS
+    router->add_handler(restinio::http_method_subscribe(),
+                        "/:hash", std::bind(&DhtProxyServer::subscribe, this, _1, _2));
+    router->add_handler(restinio::http_method_unsubscribe(),
+                        "/:hash", std::bind(&DhtProxyServer::unsubscribe, this, _1, _2));
+#endif //OPENDHT_PUSH_NOTIFICATIONS
+#ifdef OPENDHT_PROXY_SERVER_IDENTITY
+    // LEGACY SIGN ROUTE
+    router->add_handler(restinio::custom_http_methods_t::from_nodejs(restinio::method_sign.raw_id()),
+                        "/:hash", std::bind(&DhtProxyServer::putSigned, this, _1, _2));
+    // }
+    router->http_post("/:hash/sign", std::bind(&DhtProxyServer::putSigned, this, _1, _2));
+    // LEGACY ENCRYPT ROUTE
+    router->add_handler(restinio::custom_http_methods_t::from_nodejs(restinio::method_encrypt.raw_id()),
+                        "/:hash", std::bind(&DhtProxyServer::putEncrypted, this, _1, _2));
+    // }
+    router->http_post("/:hash/encrypt", std::bind(&DhtProxyServer::putEncrypted, this, _1, _2));
+#endif // OPENDHT_PROXY_SERVER_IDENTITY
+    router->add_handler(restinio::http_method_options(),
+                        "/:hash", std::bind(&DhtProxyServer::options, this, _1, _2));
+    router->http_get("/:hash/:value", std::bind(&DhtProxyServer::getFiltered, this, _1, _2));
+    return router;
+}
+
+RequestStatus
+DhtProxyServer::getNodeInfo(restinio::request_handle_t request,
+                            restinio::router::route_params_t params) const
+{
+    Json::Value result;
+    std::lock_guard<std::mutex> lck(statsMutex_);
+    if (nodeInfo_.ipv4.good_nodes == 0 &&
+        nodeInfo_.ipv6.good_nodes == 0){
+        nodeInfo_ = this->dht_->getNodeInfo();
+    }
+    result = nodeInfo_.toJson();
+    // [ipv6:ipv4]:port or ipv4:port
+    result["public_ip"] = request->remote_endpoint().address().to_string();
+    auto output = Json::writeString(jsonBuilder_, result) + "\n";
+
+    auto response = this->initHttpResponse(request->create_response());
+    response.append_body(output);
+    return response.done();
+}
+
+RequestStatus
+DhtProxyServer::getStats(restinio::request_handle_t request,
+                         restinio::router::route_params_t params)
 {
     requestNum_++;
-    const auto request = session->get_request();
-    int content_length = std::stoi(request->get_header("Content-Length", "0"));
-    session->fetch(content_length,
-        [this](const Sp<restbed::Session>& s, const restbed::Bytes& /*b*/) mutable
-        {
-            try {
-                if (dht_) {
+    try {
+        if (dht_){
 #ifdef OPENDHT_JSONCPP
-                    auto output = Json::writeString(jsonBuilder_, stats_.toJson()) + "\n";
-                    s->close(restbed::OK, output);
+            auto output = Json::writeString(jsonBuilder_, stats_.toJson()) + "\n";
+            auto response = this->initHttpResponse(request->create_response());
+            response.append_body(output);
+            response.done();
 #else
-                    s->close(restbed::NotFound, "{\"err\":\"JSON not enabled on this instance\"}");
+            auto response = this->initHttpResponse(
+                request->create_response(restinio::status_not_found()));
+            response.set_body(RESP_MSG_JSON_NOT_ENABLED);
+            return response.done();
 #endif
-                }
-                else
-                    s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
-            } catch (...) {
-                s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
-            }
+        } else {
+            auto response = this->initHttpResponse(
+                request->create_response(restinio::status_service_unavailable()));
+            response.set_body(RESP_MSG_SERVICE_UNAVAILABLE);
+            return response.done();
         }
-    );
+    } catch (...){
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_internal_server_error()));
+        response.set_body(RESP_MSG_INTERNAL_SERVER_ERRROR);
+        return response.done();
+    }
+    return restinio::request_handling_status_t::accepted;
 }
 
-void
-DhtProxyServer::get(const Sp<restbed::Session>& session) const
+RequestStatus
+DhtProxyServer::get(restinio::request_handle_t request,
+                    restinio::router::route_params_t params)
 {
     requestNum_++;
-    const auto request = session->get_request();
-    int content_length = std::stoi(request->get_header("Content-Length", "0"));
-    auto hash = request->get_path_parameter("hash");
-    session->fetch(content_length,
-        [=](const Sp<restbed::Session>& s, const restbed::Bytes& /*b* */)
-        {
-            try {
-                if (dht_) {
-                    InfoHash infoHash(hash);
-                    if (!infoHash) {
-                        infoHash = InfoHash::get(hash);
-                    }
-                    s->yield(restbed::OK, "", [=](const Sp<restbed::Session>&) {});
-                    dht_->get(infoHash, [this,s](const Sp<Value>& value) {
-                        if (s->is_closed()) return false;
-                        // Send values as soon as we get them
-                        auto output = Json::writeString(jsonBuilder_, value->toJson()) + "\n";
-                        s->yield(output, [](const Sp<restbed::Session>& /*session*/){ });
-                        return true;
-                    }, [s](bool /*ok* */) {
-                        // Communication is finished
-                        if (not s->is_closed()) {
-                            s->close();
-                        }
-                    });
-                } else {
-                    s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
-                }
-            } catch (...) {
-                s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
-            }
-        }
-    );
+    dht::InfoHash infoHash(params["hash"].to_string());
+    if (!infoHash)
+        infoHash = dht::InfoHash::get(params["hash"].to_string());
+
+    if (!dht_){
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_service_unavailable()));
+        response.set_body(RESP_MSG_SERVICE_UNAVAILABLE);
+        return response.done();
+    }
+
+    auto response = std::make_shared<ResponseByPartsBuilder>(
+        this->initHttpResponse(request->create_response<ResponseByParts>()));
+    response->flush();
+    try {
+        dht_->get(infoHash, [this, response](const dht::Sp<dht::Value>& value){
+            auto output = Json::writeString(jsonBuilder_, value->toJson()) + "\n";
+            response->append_chunk(output);
+            response->flush();
+            return true;
+        },
+        [response] (bool /*ok*/){
+            response->done();
+        });
+    } catch (const std::exception& e){
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_internal_server_error()));
+        response.set_body(RESP_MSG_INTERNAL_SERVER_ERRROR);
+        return response.done();
+    }
+    return restinio::request_handling_status_t::accepted;
 }
 
-void
-DhtProxyServer::listen(const Sp<restbed::Session>& session)
+RequestStatus
+DhtProxyServer::listen(restinio::request_handle_t request,
+                       restinio::router::route_params_t params)
 {
     requestNum_++;
-    const auto request = session->get_request();
-    int content_length = std::stoi(request->get_header("Content-Length", "0"));
-    auto hash = request->get_path_parameter("hash");
-    InfoHash infoHash(hash);
+    dht::InfoHash infoHash(params["hash"].to_string());
     if (!infoHash)
-        infoHash = InfoHash::get(hash);
-    session->fetch(content_length,
-        [=](const Sp<restbed::Session>& s, const restbed::Bytes& /*b* */)
-        {
-            try {
-                if (dht_) {
-                    InfoHash infoHash(hash);
-                    if (!infoHash) {
-                        infoHash = InfoHash::get(hash);
-                    }
-                    s->yield(restbed::OK);
-                    // Handle client deconnection
-                    // NOTE: for now, there is no handler, so we test the session in a thread
-                    // will be the case in restbed 5.0
-                    SessionToHashToken listener;
-                    listener.session = session;
-                    listener.hash = infoHash;
-                    // cache the session to avoid an incrementation of the shared_ptr's counter
-                    // else, the session->close() will not close the socket.
-                    auto cacheSession = std::weak_ptr<restbed::Session>(s);
-                    listener.token = dht_->listen(infoHash, [this,cacheSession](const std::vector<Sp<Value>>& values, bool expired) {
-                        auto s = cacheSession.lock();
-                        if (!s) return false;
-                        // Send values as soon as we get them
-                        if (!s->is_closed()) {
-                            for (const auto& value : values) {
-                                auto val = value->toJson();
-                                if (expired)
-                                    val["expired"] = true;
-                                auto output = Json::writeString(jsonBuilder_, val) + "\n";
-                                s->yield(output, [](const Sp<restbed::Session>&){ });
-                            }
-                        }
-                        return !s->is_closed();
-                    });
-                    {
-                        std::lock_guard<std::mutex> lock(lockListener_);
-                        currentListeners_.emplace_back(std::move(listener));
-                    }
-                } else {
-                    session->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
-                }
-            } catch (...) {
-                s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
+        infoHash = dht::InfoHash::get(params["hash"].to_string());
+
+    if (!dht_){
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_service_unavailable()));
+        response.set_body(RESP_MSG_SERVICE_UNAVAILABLE);
+        return response.done();
+    }
+    auto response = std::make_shared<ResponseByPartsBuilder>(
+        this->initHttpResponse(request->create_response<ResponseByParts>()));
+    response->flush();
+    try {
+        std::lock_guard<std::mutex> lock(*lockListener_);
+        // save the listener to handle a disconnect
+        auto &session = (*listeners_)[request->connection_id()];
+        session.hash = infoHash;
+        session.response = response;
+        session.token = dht_->listen(infoHash, [this, response]
+                (const std::vector<dht::Sp<dht::Value>>& values, bool expired){
+            for (const auto& value: values){
+                auto jsonVal = value->toJson();
+                if (expired)
+                    jsonVal["expired"] = true;
+                auto output = Json::writeString(jsonBuilder_, jsonVal) + "\n";
+                response->append_chunk(output);
+                response->flush();
             }
-        }
-    );
+            return true;
+        });
+
+    } catch (const std::exception& e){
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_internal_server_error()));
+        response.set_body(RESP_MSG_INTERNAL_SERVER_ERRROR);
+        return response.done();
+    }
+    return restinio::request_handling_status_t::accepted;
 }
 
 #ifdef OPENDHT_PUSH_NOTIFICATIONS
 
-struct DhtProxyServer::Listener {
-    std::string clientId;
-    std::future<size_t> internalToken;
-    Sp<Scheduler::Job> expireJob;
-    Sp<Scheduler::Job> expireNotifyJob;
-};
-struct DhtProxyServer::PushListener {
-    std::map<InfoHash, std::vector<Listener>> listeners;
-    bool isAndroid;
-};
-
-void
-DhtProxyServer::subscribe(const std::shared_ptr<restbed::Session>& session)
+RequestStatus
+DhtProxyServer::subscribe(restinio::request_handle_t request,
+                          restinio::router::route_params_t params)
 {
     requestNum_++;
-    const auto request = session->get_request();
-    int content_length = std::stoi(request->get_header("Content-Length", "0"));
-    auto hash = request->get_path_parameter("hash");
-    InfoHash infoHash(hash);
+
+    dht::InfoHash infoHash(params["hash"].to_string());
     if (!infoHash)
-        infoHash = InfoHash::get(hash);
-    session->fetch(content_length,
-        [=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& b) mutable
-        {
-            try {
-                std::string err;
-                Json::Value root;
-                Json::CharReaderBuilder rbuilder;
-                auto* char_data = reinterpret_cast<const char*>(b.data());
-                auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-                if (!reader->parse(char_data, char_data + b.size(), &root, &err)) {
-                    s->close(restbed::BAD_REQUEST, "{\"err\":\"Incorrect JSON\"}");
-                    return;
-                }
-                auto pushToken = root["key"].asString();
-                if (pushToken.empty()) {
-                    s->close(restbed::BAD_REQUEST, "{\"err\":\"No token\"}");
-                    return;
+        infoHash = dht::InfoHash::get(params["hash"].to_string());
+
+    if (!dht_){
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_service_unavailable()));
+        response.set_body(RESP_MSG_SERVICE_UNAVAILABLE);
+        return response.done();
+    }
+    try {
+        std::string err;
+        Json::Value root;
+        Json::CharReaderBuilder rbuilder;
+        auto* char_data = reinterpret_cast<const char*>(request->body().data());
+        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
+        if (!reader->parse(char_data, char_data + request->body().size(), &root, &err)){
+            auto response = this->initHttpResponse(
+                request->create_response(restinio::status_bad_request()));
+            response.set_body(RESP_MSG_JSON_INCORRECT);
+            return response.done();
+        }
+        auto pushToken = root["key"].asString();
+        if (pushToken.empty()){
+            auto response = this->initHttpResponse(
+                request->create_response(restinio::status_bad_request()));
+            response.set_body(RESP_MSG_NO_TOKEN);
+            return response.done();
+        }
+        auto platform = root["platform"].asString();
+        auto isAndroid = platform == "android";
+        auto clientId = root.isMember("client_id") ? root["client_id"].asString() : std::string();
+
+        if (logger_)
+            logger_->d("[proxy:server] [subscribe %s] [client %s]", infoHash.toString().c_str(), clientId.c_str());
+        // ================ Search for existing listener ===================
+        // start the timer
+        auto timeout = std::chrono::steady_clock::now() + proxy::OP_TIMEOUT;
+        std::lock_guard<std::mutex> lock(lockPushListeners_);
+
+        // Insert new or return existing push listeners of a token
+        auto pushListener = pushListeners_.emplace(pushToken, PushListener{}).first;
+        auto pushListeners = pushListener->second.listeners.emplace(infoHash, std::vector<Listener>{}).first;
+
+        for (auto &listener: pushListeners->second){
+            if (logger_)
+                logger_->d("[proxy:server] [subscribe] found [client %s]", listener.clientId.c_str());
+            // Found -> Resubscribe
+            if (listener.clientId == clientId){
+                // Reset timers
+                listener.expireTimer->expires_at(timeout);
+                listener.expireNotifyTimer->expires_at(timeout - proxy::OP_MARGIN);
+                // Send response header
+                auto response = std::make_shared<ResponseByPartsBuilder>(
+                    this->initHttpResponse(request->create_response<ResponseByParts>()));
+                response->flush();
+                // No Refresh
+                if (!root.isMember("refresh") or !root["refresh"].asBool()){
+                    dht_->get(infoHash, [this, response](const dht::Sp<dht::Value>& value){
+                        auto output = Json::writeString(jsonBuilder_, value->toJson()) + "\n";
+                        response->append_chunk(output);
+                        response->flush();
+                        return true;
+                    },
+                    [response] (bool){
+                        response->done();
+                    });
+                // Refresh
+                } else {
+                    response->append_chunk("{}\n");
+                    response->done();
                 }
-                auto platform = root["platform"].asString();
-                auto isAndroid = platform == "android";
-                auto clientId = root.isMember("client_id") ? root["client_id"].asString() : std::string();
-
-                std::cout << "Subscribe " << infoHash << " client:" << clientId << std::endl;
-
-                {
-                    std::lock(schedulerLock_, lockListener_);
-                    std::lock_guard<std::mutex> lk1(lockListener_, std::adopt_lock);
-                    std::lock_guard<std::mutex> lk2(schedulerLock_, std::adopt_lock);
-                    scheduler_.syncTime();
-                    auto timeout = scheduler_.time() + proxy::OP_TIMEOUT;
-                    // Check if listener is already present and refresh timeout if launched
-                    // One push listener per pushToken.infoHash.clientId
-                    auto pushListener = pushListeners_.emplace(pushToken, PushListener{}).first;
-                    auto listeners = pushListener->second.listeners.emplace(infoHash, std::vector<Listener>{}).first;
-                    for (auto& listener: listeners->second) {
-                        if (listener.clientId == clientId) {
-                            scheduler_.edit(listener.expireJob, timeout);
-                            scheduler_.edit(listener.expireNotifyJob, timeout - proxy::OP_MARGIN);
-                            s->yield(restbed::OK);
-
-                            if (!root.isMember("refresh") or !root["refresh"].asBool()) {
-                                dht_->get(
-                                    infoHash,
-                                    [this, s](const Sp<Value> &value) {
-                                        if (s->is_closed())
-                                            return false;
-                                        // Send values as soon as we get them
-                                        auto output = Json::writeString(jsonBuilder_, value->toJson()) + "\n";
-                                        s->yield(output, [](const Sp<restbed::Session>
-                                                                & /*session*/) {});
-                                        return true;
-                                    },
-                                    [s](bool /*ok* */) {
-                                        // Communication is finished
-                                        if (not s->is_closed()) {
-                                            s->close("{}\n");
-                                        }
-                                    });
-                            } else {
-                                // Communication is finished
-                                if (not s->is_closed()) {
-                                    s->close("{}\n");
-                                }
-                            }
-                            schedulerCv_.notify_one();
-                            return;
-                        }
+                return restinio::request_handling_status_t::accepted;
+            }
+        }
+        // =========== No existing listener for an infoHash ============
+        // Add new listener to list of listeners
+        pushListeners->second.emplace_back(Listener{});
+        auto &listener = pushListeners->second.back();
+        listener.clientId = clientId;
+
+        // Add listen on dht
+        listener.internalToken = dht_->listen(infoHash,
+            [this, infoHash, pushToken, isAndroid, clientId]
+            (const std::vector<std::shared_ptr<Value>>& values, bool expired){
+                // Build message content
+                Json::Value json;
+                json["key"] = infoHash.toString();
+                json["to"] = clientId;
+                if (expired and values.size() < 2){
+                    std::stringstream ss;
+                    for(size_t i = 0; i < values.size(); ++i){
+                        if(i != 0) ss << ",";
+                        ss << values[i]->id;
                     }
-                    listeners->second.emplace_back(Listener{});
-                    auto& listener = listeners->second.back();
-                    listener.clientId = clientId;
-
-                    // New listener
-                    pushListener->second.isAndroid = isAndroid;
-
-                    // The listener is not found, so add it.
-                    listener.internalToken = dht_->listen(infoHash,
-                        [this, infoHash, pushToken, isAndroid, clientId](const std::vector<std::shared_ptr<Value>>& values, bool expired) {
-                            threadPool_->run([this, infoHash, pushToken, isAndroid, clientId, values, expired]() {
-                                // Build message content
-                                Json::Value json;
-                                json["key"] = infoHash.toString();
-                                json["to"] = clientId;
-                                if (expired and values.size() < 3) {
-                                    std::stringstream ss;
-                                    for(size_t i = 0; i < values.size(); ++i) {
-                                        if(i != 0) ss << ",";
-                                        ss << values[i]->id;
-                                    }
-                                    json["exp"] = ss.str();
-                                }
-                                sendPushNotification(pushToken, std::move(json), isAndroid);
-                            });
-                            return true;
-                        }
-                    );
-                    listener.expireJob = scheduler_.add(timeout,
-                        [this, clientId, infoHash, pushToken] {
-                            cancelPushListen(pushToken, infoHash, clientId);
-                        }
-                    );
-                    listener.expireNotifyJob = scheduler_.add(timeout - proxy::OP_MARGIN,
-                        [this, infoHash, pushToken, isAndroid, clientId] {
-                            std::cout << "Listener: sending refresh " << infoHash << std::endl;
-                            Json::Value json;
-                            json["timeout"] = infoHash.toString();
-                            json["to"] = clientId;
-                            sendPushNotification(pushToken, std::move(json), isAndroid);
-                        }
-                    );
+                    json["exp"] = ss.str();
                 }
-                schedulerCv_.notify_one();
-                s->close(restbed::OK, "{}\n");
-            } catch (...) {
-                s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
+                sendPushNotification(pushToken, std::move(json), isAndroid);
+                return true;
             }
-        }
-    );
+        );
+        // Init & set timers
+        auto &ctx = httpServer_->io_context();
+        listener.expireTimer = std::make_unique<asio::steady_timer>(ctx, timeout);
+        listener.expireNotifyTimer = std::make_unique<asio::steady_timer>(ctx,
+                                        timeout - proxy::OP_MARGIN);
+        // Launch timers
+        listener.expireTimer->async_wait(std::bind(
+            &DhtProxyServer::cancelPushListen, this, pushToken, infoHash, clientId));
+
+        listener.expireNotifyTimer->async_wait(
+        [this, infoHash, pushToken, isAndroid, clientId](const asio::error_code &ec){
+            if (logger_)
+                logger_->d("[proxy:server] [subscribe] sending refresh %s", infoHash.toString().c_str());
+            if (ec){
+                if (logger_)
+                    logger_->d("[proxy:server] [subscribe] error sending refresh: %s", ec.message().c_str());
+            }
+            Json::Value json;
+            json["timeout"] = infoHash.toString();
+            json["to"] = clientId;
+            sendPushNotification(pushToken, std::move(json), isAndroid);
+        });
+        auto response = this->initHttpResponse(request->create_response());
+        response.set_body("{}\n");
+        return response.done();
+    }
+    catch (...) {
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_internal_server_error()));
+        response.set_body(RESP_MSG_INTERNAL_SERVER_ERRROR);
+        return response.done();
+    }
+    return restinio::request_handling_status_t::accepted;
 }
 
-void
-DhtProxyServer::unsubscribe(const std::shared_ptr<restbed::Session>& session)
+RequestStatus
+DhtProxyServer::unsubscribe(restinio::request_handle_t request,
+                            restinio::router::route_params_t params)
 {
     requestNum_++;
-    const auto request = session->get_request();
-    int content_length = std::stoi(request->get_header("Content-Length", "0"));
-    auto hash = request->get_path_parameter("hash");
-    InfoHash infoHash(hash);
+
+    dht::InfoHash infoHash(params["hash"].to_string());
     if (!infoHash)
-        infoHash = InfoHash::get(hash);
-    session->fetch(content_length,
-        [=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& b)
-        {
-            try {
-                std::string err;
-                Json::Value root;
-                Json::CharReaderBuilder rbuilder;
-                auto* char_data = reinterpret_cast<const char*>(b.data());
-                auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-                if (!reader->parse(char_data, char_data + b.size(), &root, &err)) {
-                    s->close(restbed::BAD_REQUEST, "{\"err\":\"Incorrect JSON\"}");
-                    return;
-                }
-                auto pushToken = root["key"].asString();
-                if (pushToken.empty()) return;
-                auto clientId = root["client_id"].asString();
-
-                cancelPushListen(pushToken, infoHash, clientId);
-                s->close(restbed::OK);
-            } catch (...) {
-                s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
-            }
+        infoHash = dht::InfoHash::get(params["hash"].to_string());
+
+    if (!dht_){
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_service_unavailable()));
+        response.set_body(RESP_MSG_SERVICE_UNAVAILABLE);
+        return response.done();
+    }
+
+    try {
+        std::string err;
+        Json::Value root;
+        Json::CharReaderBuilder rbuilder;
+        auto* char_data = reinterpret_cast<const char*>(request->body().data());
+        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
+
+        if (!reader->parse(char_data, char_data + request->body().size(), &root, &err)){
+            auto response = this->initHttpResponse(
+                request->create_response(restinio::status_bad_request()));
+            response.set_body(RESP_MSG_JSON_INCORRECT);
+            return response.done();
         }
-    );
+        auto pushToken = root["key"].asString();
+        if (pushToken.empty())
+            return restinio::request_handling_status_t::rejected;
+        auto clientId = root["client_id"].asString();
+
+        cancelPushListen(pushToken, infoHash, clientId);
+        auto response = this->initHttpResponse(request->create_response());
+        return response.done();
+    }
+    catch (...) {
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_internal_server_error()));
+        response.set_body(RESP_MSG_INTERNAL_SERVER_ERRROR);
+        return response.done();
+    }
 }
 
 void
 DhtProxyServer::cancelPushListen(const std::string& pushToken, const dht::InfoHash& key, const std::string& clientId)
 {
-    std::cout << "cancelPushListen: " << key << " clientId:" << clientId << std::endl;
-    std::lock_guard<std::mutex> lock(lockListener_);
+    if (logger_)
+        logger_->d("[proxy:server] [listen:push %s] cancelled for  %s",
+                   key.toString().c_str(), clientId.c_str());
+    std::lock_guard<std::mutex> lock(*lockListener_);
+
     auto pushListener = pushListeners_.find(pushToken);
     if (pushListener == pushListeners_.end())
         return;
     auto listeners = pushListener->second.listeners.find(key);
     if (listeners == pushListener->second.listeners.end())
         return;
-    for (auto listener = listeners->second.begin(); listener != listeners->second.end();) {
-        if (listener->clientId == clientId) {
+
+    for (auto listener = listeners->second.begin(); listener != listeners->second.end();){
+        if (listener->clientId == clientId){
             if (dht_)
                 dht_->cancelListen(key, std::move(listener->internalToken));
             listener = listeners->second.erase(listener);
@@ -560,12 +597,10 @@ DhtProxyServer::cancelPushListen(const std::string& pushToken, const dht::InfoHa
             ++listener;
         }
     }
-    if (listeners->second.empty()) {
+    if (listeners->second.empty())
         pushListener->second.listeners.erase(listeners);
-    }
-    if (pushListener->second.listeners.empty()) {
+    if (pushListener->second.listeners.empty())
         pushListeners_.erase(pushListener);
-    }
 }
 
 void
@@ -573,9 +608,16 @@ DhtProxyServer::sendPushNotification(const std::string& token, Json::Value&& jso
 {
     if (pushServer_.empty())
         return;
-    restbed::Uri uri(proxy::HTTP_PROTO + pushServer_ + "/api/push");
-    auto req = std::make_shared<restbed::Request>(uri);
-    req->set_method("POST");
+
+    restinio::http_request_header_t header;
+    header.request_target("/api/push");
+    header.method(restinio::http_method_post());
+
+    restinio::http_header_fields_t header_fields;
+    header_fields.append_field(restinio::http_field_t::host, pushServer_.c_str());
+    header_fields.append_field(restinio::http_field_t::user_agent, "RESTinio client");
+    header_fields.append_field(restinio::http_field_t::accept, "*/*");
+    header_fields.append_field(restinio::http_field_t::content_type, "application/json");
 
     // NOTE: see https://github.com/appleboy/gorush
     Json::Value notification(Json::objectValue);
@@ -596,16 +638,32 @@ DhtProxyServer::sendPushNotification(const std::string& token, Json::Value&& jso
     Json::StreamWriterBuilder wbuilder;
     wbuilder["commentStyle"] = "None";
     wbuilder["indentation"] = "";
-    auto valueStr = Json::writeString(wbuilder, content);
-
-    req->set_header("Content-Type", "application/json");
-    req->set_header("Accept", "*/*");
-    req->set_header("Host", pushServer_);
-    req->set_header("Content-Length", std::to_string(valueStr.length()));
-    req->set_body(valueStr);
-
-    // Send request.
-    restbed::Http::async(req, {});
+    auto body = Json::writeString(wbuilder, content);
+
+    auto parser = std::make_shared<http_parser>();
+    http_parser_init(parser.get(), HTTP_RESPONSE);
+
+    struct PushContext {
+        std::shared_ptr<Logger> logger;
+    };
+    auto context = std::make_shared<PushContext>();
+    if (logger_)
+        context->logger = logger_;
+    parser->data = static_cast<void*>(context.get());
+
+    auto parser_s = std::make_shared<http_parser_settings>();
+    http_parser_settings_init(parser_s.get());
+    parser_s->on_status = []( http_parser * parser, const char * at, size_t length ) -> int {
+        auto context = static_cast<PushContext*>(parser->data);
+        if (parser->status_code == 200)
+            return 0;
+        if (context->logger)
+            context->logger->e("[proxy:server] [notification] error send push: %i", parser->status_code);
+        return 1;
+    };
+    auto request = httpClient_->create_request(header, header_fields,
+        restinio::http_connection_header_t::close, body);
+    httpClient_->post_request(request, parser, parser_s);
 }
 
 #endif //OPENDHT_PUSH_NOTIFICATIONS
@@ -613,7 +671,8 @@ DhtProxyServer::sendPushNotification(const std::string& token, Json::Value&& jso
 void
 DhtProxyServer::cancelPut(const InfoHash& key, Value::Id vid)
 {
-    std::cout << "cancelPut " << key << " " << vid << std::endl;
+    if (logger_)
+        logger_->d("[proxy:server] [put %s] cancel put %i", key.toString().c_str(), vid);
     auto sPuts = puts_.find(key);
     if (sPuts == puts_.end())
         return;
@@ -623,288 +682,323 @@ DhtProxyServer::cancelPut(const InfoHash& key, Value::Id vid)
         return;
     if (dht_)
         dht_->cancelPut(key, vid);
-    if (put->second.expireNotifyJob)
-        put->second.expireNotifyJob->cancel();
+    if (put->second.expireNotifyTimer)
+        put->second.expireNotifyTimer->cancel();
     sPutsMap.erase(put);
     if (sPutsMap.empty())
         puts_.erase(sPuts);
 }
 
-void
-DhtProxyServer::put(const std::shared_ptr<restbed::Session>& session)
+RequestStatus
+DhtProxyServer::put(restinio::request_handle_t request,
+                    restinio::router::route_params_t params)
 {
     requestNum_++;
-    const auto request = session->get_request();
-    int content_length = std::stoi(request->get_header("Content-Length", "0"));
-    auto hash = request->get_path_parameter("hash");
-    InfoHash infoHash(hash);
+    dht::InfoHash infoHash(params["hash"].to_string());
     if (!infoHash)
-        infoHash = InfoHash::get(hash);
-
-    session->fetch(content_length,
-        [=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& b)
-        {
-            try {
-                if (dht_) {
-                    if(b.empty()) {
-                        std::string response("{\"err\":\"Missing parameters\"}");
-                        s->close(restbed::BAD_REQUEST, response);
-                    } else {
-                        std::string err;
-                        Json::Value root;
-                        Json::CharReaderBuilder rbuilder;
-                        auto* char_data = reinterpret_cast<const char*>(b.data());
-                        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-                        if (reader->parse(char_data, char_data + b.size(), &root, &err)) {
-                            // Build the Value from json
-                            auto value = std::make_shared<Value>(root);
-                            bool permanent = root.isMember("permanent");
-                            std::cout << "Got put " << infoHash << " " << *value << " " << (permanent ? "permanent" : "") << std::endl;
-
-                            if (permanent) {
-                                std::string pushToken, clientId, platform;
-                                auto& pVal = root["permanent"];
-                                if (pVal.isObject()) {
-                                    pushToken = pVal["key"].asString();
-                                    clientId = pVal["client_id"].asString();
-                                    platform = pVal["platform"].asString();
-                                }
-                                std::unique_lock<std::mutex> lock(schedulerLock_);
-                                scheduler_.syncTime();
-                                auto timeout = scheduler_.time() + proxy::OP_TIMEOUT;
-                                auto vid = value->id;
-                                auto sPuts = puts_.emplace(infoHash, SearchPuts{}).first;
-                                auto r = sPuts->second.puts.emplace(vid, PermanentPut{});
-                                auto& pput = r.first->second;
-                                if (r.second) {
-                                    pput.expireJob = scheduler_.add(timeout, [this, infoHash, vid]{
-                                        std::cout << "Permanent put expired: " << infoHash << " " << vid << std::endl;
-                                        cancelPut(infoHash, vid);
-                                    });
-#ifdef OPENDHT_PUSH_NOTIFICATIONS
-                                    if (not pushToken.empty()) {
-                                        bool isAndroid = platform == "android";
-                                        pput.expireNotifyJob = scheduler_.add(timeout - proxy::OP_MARGIN,
-                                            [this, infoHash, vid, pushToken, clientId, isAndroid]
-                                        {
-                                            std::cout << "Permanent put refresh: " << infoHash << " " << vid << std::endl;
-                                            Json::Value json;
-                                            json["timeout"] = infoHash.toString();
-                                            json["to"] = clientId;
-                                            json["vid"] = std::to_string(vid);
-                                            sendPushNotification(pushToken, std::move(json), isAndroid);
-                                        });
-                                    }
-#endif
-                                } else {
-                                    scheduler_.edit(pput.expireJob, timeout);
-                                    if (pput.expireNotifyJob)
-                                        scheduler_.edit(pput.expireNotifyJob, timeout - proxy::OP_MARGIN);
-                                }
-                                lock.unlock();
-                                schedulerCv_.notify_one();
-                            }
+        infoHash = dht::InfoHash::get(params["hash"].to_string());
+
+    if (!dht_){
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_service_unavailable()));
+        response.set_body(RESP_MSG_SERVICE_UNAVAILABLE);
+        return response.done();
+    }
+    else if (request->body().empty()){
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_bad_request()));
+        response.set_body(RESP_MSG_MISSING_PARAMS);
+        return response.done();
+    }
 
-                            dht_->put(infoHash, value, [s, value](bool ok) {
-                                if (ok) {
-                                    Json::StreamWriterBuilder wbuilder;
-                                    wbuilder["commentStyle"] = "None";
-                                    wbuilder["indentation"] = "";
-                                    if (s->is_open())
-                                        s->close(restbed::OK, Json::writeString(wbuilder, value->toJson()) + "\n");
-                                } else {
-                                    if (s->is_open())
-                                        s->close(restbed::BAD_GATEWAY, "{\"err\":\"put failed\"}");
-                                }
-                            }, time_point::max(), permanent);
-                        } else {
-                            s->close(restbed::BAD_REQUEST, "{\"err\":\"Incorrect JSON\"}");
+    try {
+        std::string err;
+        Json::Value root;
+        Json::CharReaderBuilder rbuilder;
+        auto* char_data = reinterpret_cast<const char*>(request->body().data());
+        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
+
+        if (reader->parse(char_data, char_data + request->body().size(), &root, &err)){
+            auto value = std::make_shared<dht::Value>(root);
+            bool permanent = root.isMember("permanent");
+            if (logger_)
+                logger_->d("[proxy:server] [put %s] %s %s", infoHash.toString().c_str(),
+                          value->toString().c_str(), (permanent ? "permanent" : ""));
+            if (permanent){
+                std::string pushToken, clientId, platform;
+                auto& pVal = root["permanent"];
+                if (pVal.isObject()){
+                    pushToken = pVal["key"].asString();
+                    clientId = pVal["client_id"].asString();
+                    platform = pVal["platform"].asString();
+                }
+                std::unique_lock<std::mutex> lock(lockSearchPuts_);
+                auto timeout = std::chrono::steady_clock::now() + proxy::OP_TIMEOUT;
+                auto vid = value->id;
+                auto sPuts = puts_.emplace(infoHash, SearchPuts{}).first;
+                auto r = sPuts->second.puts.emplace(vid, PermanentPut{});
+                auto& pput = r.first->second;
+                if (r.second){
+                    auto &ctx = httpServer_->io_context();
+                    pput.expireTimer = std::make_unique<asio::steady_timer>(ctx, timeout);
+                    pput.expireTimer->async_wait([this, infoHash, vid](const asio::error_code &ec){
+                        if (logger_)
+                            logger_->d("[proxy:server] [put %s] permanent expired: %i", infoHash.toString().c_str(), vid);
+                        if (ec){
+                            if (logger_)
+                                logger_->e("[proxy:server] error in permanent put: %s", ec.message().c_str());
                         }
+                        cancelPut(infoHash, vid);
+                    });
+#ifdef OPENDHT_PUSH_NOTIFICATIONS
+                    if (not pushToken.empty()){
+                        bool isAndroid = platform == "android";
+                        pput.expireNotifyTimer = std::make_unique<asio::steady_timer>(ctx,
+                                                    timeout - proxy::OP_MARGIN);
+                        pput.expireNotifyTimer->async_wait(
+                        [this, infoHash, vid, pushToken, clientId, isAndroid](const asio::error_code &ec)
+                        {
+                            if (logger_)
+                                logger_->d("[proxy:server] [put %s] refresh: %i", infoHash.toString().c_str(), vid);
+                            if (ec){
+                                if (logger_)
+                                    logger_->e("[proxy:server] error in refresh put: %s", ec.message().c_str());
+                            }
+                            Json::Value json;
+                            json["timeout"] = infoHash.toString();
+                            json["to"] = clientId;
+                            json["vid"] = std::to_string(vid);
+                            sendPushNotification(pushToken, std::move(json), isAndroid);
+                        });
                     }
+#endif
                 } else {
-                    s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
+                    pput.expireTimer->expires_at(timeout);
+                    if (pput.expireNotifyTimer)
+                        pput.expireNotifyTimer->expires_at(timeout - proxy::OP_MARGIN);
                 }
-            } catch (const std::exception& e) {
-                std::cout << "Error performing put: " << e.what() << std::endl;
-                s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
+                lock.unlock();
             }
+            dht_->put(infoHash, value, [this, request, value](bool ok){
+                if (ok){
+                    auto output = Json::writeString(jsonBuilder_, value->toJson()) + "\n";
+                    auto response = this->initHttpResponse(request->create_response());
+                    response.append_body(output);
+                    response.done();
+                } else {
+                    auto response = this->initHttpResponse(request->create_response(
+                        restinio::status_bad_gateway()));
+                    response.set_body(RESP_MSG_PUT_FAILED);
+                    response.done();
+                }
+            }, dht::time_point::max(), permanent);
+        } else {
+            auto response = this->initHttpResponse(
+                request->create_response(restinio::status_bad_request()));
+            response.set_body(RESP_MSG_JSON_INCORRECT);
+            return response.done();
         }
-    );
+    } catch (const std::exception& e){
+        if (logger_)
+            logger_->d("[proxy:server] error in put: %s", e.what());
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_internal_server_error()));
+        response.set_body(RESP_MSG_INTERNAL_SERVER_ERRROR);
+        return response.done();
+    }
+    return restinio::request_handling_status_t::accepted;
 }
 
 #ifdef OPENDHT_PROXY_SERVER_IDENTITY
-void
-DhtProxyServer::putSigned(const std::shared_ptr<restbed::Session>& session) const
+
+RequestStatus DhtProxyServer::putSigned(restinio::request_handle_t request,
+                                        restinio::router::route_params_t params) const
 {
     requestNum_++;
-    const auto request = session->get_request();
-    int content_length = std::stoi(request->get_header("Content-Length", "0"));
-    auto hash = request->get_path_parameter("hash");
-    InfoHash infoHash(hash);
+    dht::InfoHash infoHash(params["hash"].to_string());
     if (!infoHash)
-        infoHash = InfoHash::get(hash);
-
-    session->fetch(content_length,
-        [=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& b)
-        {
-            try {
-                if (dht_) {
-                    if(b.empty()) {
-                        std::string response("{\"err\":\"Missing parameters\"}");
-                        s->close(restbed::BAD_REQUEST, response);
-                    } else {
-                        std::string err;
-                        Json::Value root;
-                        Json::CharReaderBuilder rbuilder;
-                        auto* char_data = reinterpret_cast<const char*>(b.data());
-                        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-                        if (reader->parse(char_data, char_data + b.size(), &root, &err)) {
-                            auto value = std::make_shared<Value>(root);
-
-                            Json::StreamWriterBuilder wbuilder;
-                            wbuilder["commentStyle"] = "None";
-                            wbuilder["indentation"] = "";
-                            auto output = Json::writeString(wbuilder, value->toJson()) + "\n";
-                            dht_->putSigned(infoHash, value);
-                            s->close(restbed::OK, output);
-                        } else {
-                            s->close(restbed::BAD_REQUEST, "{\"err\":\"Incorrect JSON\"}");
-                        }
-                    }
+        infoHash = dht::InfoHash::get(params["hash"].to_string());
+
+    if (!dht_){
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_service_unavailable()));
+        response.set_body(RESP_MSG_SERVICE_UNAVAILABLE);
+        return response.done();
+    }
+    else if (request->body().empty()){
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_bad_request()));
+        response.set_body(RESP_MSG_MISSING_PARAMS);
+        return response.done();
+    }
+
+    try {
+        std::string err;
+        Json::Value root;
+        Json::CharReaderBuilder rbuilder;
+        auto* char_data = reinterpret_cast<const char*>(request->body().data());
+        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
+
+        if (reader->parse(char_data, char_data + request->body().size(), &root, &err)){
+
+            auto value = std::make_shared<Value>(root);
+
+            dht_->putSigned(infoHash, value, [this, request, value](bool ok){
+                if (ok){
+                    auto output = Json::writeString(jsonBuilder_, value->toJson()) + "\n";
+                    auto response = this->initHttpResponse(request->create_response());
+                    response.append_body(output);
+                    response.done();
                 } else {
-                    s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
+                    auto response = this->initHttpResponse(request->create_response(
+                        restinio::status_bad_gateway()));
+                    response.set_body(RESP_MSG_PUT_FAILED);
+                    response.done();
                 }
-            } catch (...) {
-                s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
-            }
+            });
+        } else {
+            auto response = this->initHttpResponse(
+                request->create_response(restinio::status_bad_request()));
+            response.set_body(RESP_MSG_JSON_INCORRECT);
+            return response.done();
         }
-    );
+    } catch (const std::exception& e){
+        if (logger_)
+            logger_->d("[proxy:server] error in put: %s", e.what());
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_internal_server_error()));
+        response.set_body(RESP_MSG_INTERNAL_SERVER_ERRROR);
+        return response.done();
+    }
+    return restinio::request_handling_status_t::accepted;
 }
 
-void
-DhtProxyServer::putEncrypted(const std::shared_ptr<restbed::Session>& session) const
+RequestStatus
+DhtProxyServer::putEncrypted(restinio::request_handle_t request,
+                             restinio::router::route_params_t params)
 {
     requestNum_++;
-    const auto request = session->get_request();
-    int content_length = std::stoi(request->get_header("Content-Length", "0"));
-    auto hash = request->get_path_parameter("hash");
-    InfoHash key(hash);
-    if (!key)
-        key = InfoHash::get(hash);
-
-    session->fetch(content_length,
-        [=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& b)
-        {
-            try {
-                if (dht_) {
-                    if(b.empty()) {
-                        std::string response("{\"err\":\"Missing parameters\"}");
-                        s->close(restbed::BAD_REQUEST, response);
-                    } else {
-                        std::string err;
-                        Json::Value root;
-                        Json::CharReaderBuilder rbuilder;
-                        auto* char_data = reinterpret_cast<const char*>(b.data());
-                        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-                        bool parsingSuccessful = reader->parse(char_data, char_data + b.size(), &root, &err);
-                        InfoHash to(root["to"].asString());
-                        if (parsingSuccessful && to) {
-                            auto value = std::make_shared<Value>(root);
-                            Json::StreamWriterBuilder wbuilder;
-                            wbuilder["commentStyle"] = "None";
-                            wbuilder["indentation"] = "";
-                            auto output = Json::writeString(wbuilder, value->toJson()) + "\n";
-                            dht_->putEncrypted(key, to, value);
-                            s->close(restbed::OK, output);
-                        } else {
-                            if(!parsingSuccessful)
-                                s->close(restbed::BAD_REQUEST, "{\"err\":\"Incorrect JSON\"}");
-                            else
-                                s->close(restbed::BAD_REQUEST, "{\"err\":\"No destination found\"}");
-                        }
-                    }
+    dht::InfoHash infoHash(params["hash"].to_string());
+    if (!infoHash)
+        infoHash = dht::InfoHash::get(params["hash"].to_string());
+
+    if (!dht_){
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_service_unavailable()));
+        response.set_body(RESP_MSG_SERVICE_UNAVAILABLE);
+        return response.done();
+    }
+    else if (request->body().empty()){
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_bad_request()));
+        response.set_body(RESP_MSG_MISSING_PARAMS);
+        return response.done();
+    }
+
+    try {
+        std::string err;
+        Json::Value root;
+        Json::CharReaderBuilder rbuilder;
+        auto* char_data = reinterpret_cast<const char*>(request->body().data());
+        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
+
+        if (reader->parse(char_data, char_data + request->body().size(), &root, &err)){
+            InfoHash to(root["to"].asString());
+            if (!to){
+                auto response = this->initHttpResponse(
+                    request->create_response(restinio::status_bad_request()));
+                response.set_body(RESP_MSG_DESTINATION_NOT_FOUND);
+                return response.done();
+            }
+            auto value = std::make_shared<Value>(root);
+            dht_->putEncrypted(infoHash, to, value, [this, request, value](bool ok){
+                if (ok){
+                    auto output = Json::writeString(jsonBuilder_, value->toJson()) + "\n";
+                    auto response = this->initHttpResponse(request->create_response());
+                    response.append_body(output);
+                    response.done();
                 } else {
-                    s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
+                    auto response = this->initHttpResponse(request->create_response(
+                        restinio::status_bad_gateway()));
+                    response.set_body(RESP_MSG_PUT_FAILED);
+                    response.done();
                 }
-            } catch (...) {
-                s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
-            }
+            });
+        } else {
+            auto response = this->initHttpResponse(
+                request->create_response(restinio::status_bad_request()));
+            response.set_body(RESP_MSG_JSON_INCORRECT);
+            return response.done();
         }
-    );
+    } catch (const std::exception& e){
+        if (logger_)
+            logger_->d("[proxy:server] error in put: %s", e.what());
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_internal_server_error()));
+        response.set_body(RESP_MSG_INTERNAL_SERVER_ERRROR);
+        return response.done();
+    }
+    return restinio::request_handling_status_t::accepted;
 }
+
 #endif // OPENDHT_PROXY_SERVER_IDENTITY
 
-void
-DhtProxyServer::handleOptionsMethod(const std::shared_ptr<restbed::Session>& session) const
+RequestStatus
+DhtProxyServer::options(restinio::request_handle_t request,
+                        restinio::router::route_params_t params)
 {
-    requestNum_++;
+    this->requestNum_++;
 #ifdef OPENDHT_PROXY_SERVER_IDENTITY
-    const auto allowed = "OPTIONS, GET, POST, LISTEN, SIGN, ENCRYPT";
+    const auto methods = "OPTIONS, GET, POST, LISTEN, SIGN, ENCRYPT";
 #else
-    const auto allowed = "OPTIONS, GET, POST, LISTEN";
-#endif //OPENDHT_PROXY_SERVER_IDENTITY
-    session->close(restbed::OK, {{"Access-Control-Allow-Methods", allowed},
-                                 {"Access-Control-Allow-Headers", "content-type"},
-                                 {"Access-Control-Max-Age", "86400"}});
+    const auto methods = "OPTIONS, GET, POST, LISTEN";
+#endif
+    auto response = initHttpResponse(request->create_response());
+    response.append_header(restinio::http_field::access_control_allow_methods, methods);
+    response.append_header(restinio::http_field::access_control_allow_headers, "content-type");
+    response.append_header(restinio::http_field::access_control_max_age, "86400");
+    return response.done();
 }
 
-void
-DhtProxyServer::getFiltered(const std::shared_ptr<restbed::Session>& session) const
+RequestStatus
+DhtProxyServer::getFiltered(restinio::request_handle_t request,
+                            restinio::router::route_params_t params)
 {
     requestNum_++;
-    const auto request = session->get_request();
-    int content_length = std::stoi(request->get_header("Content-Length", "0"));
-    auto hash = request->get_path_parameter("hash");
-    auto value = request->get_path_parameter("value");
-    session->fetch(content_length,
-        [=](const std::shared_ptr<restbed::Session> s, const restbed::Bytes& /*b* */)
-        {
-            try {
-                if (dht_) {
-                    InfoHash infoHash(hash);
-                    if (!infoHash) {
-                        infoHash = InfoHash::get(hash);
-                    }
-                    s->yield(restbed::OK, "", [=]( const std::shared_ptr< restbed::Session > s) {
-                        dht_->get(infoHash, [s](std::shared_ptr<Value> v) {
-                            // Send values as soon as we get them
-                            Json::StreamWriterBuilder wbuilder;
-                            wbuilder["commentStyle"] = "None";
-                            wbuilder["indentation"] = "";
-                            auto output = Json::writeString(wbuilder, v->toJson()) + "\n";
-                            s->yield(output, [](const std::shared_ptr<restbed::Session> /*session*/){ });
-                            return true;
-                        }, [s](bool /*ok* */) {
-                            // Communication is finished
-                            s->close();
-                        }, {}, value);
-                    });
-                } else {
-                    s->close(restbed::SERVICE_UNAVAILABLE, "{\"err\":\"Incorrect DhtRunner\"}");
-                }
-            } catch (...) {
-                s->close(restbed::INTERNAL_SERVER_ERROR, "{\"err\":\"Internal server error\"}");
-            }
-        }
-    );
-}
+    auto value = params["value"].to_string();
+    dht::InfoHash infoHash(params["hash"].to_string());
+    if (!infoHash)
+        infoHash = dht::InfoHash::get(params["hash"].to_string());
 
-void
-DhtProxyServer::removeClosedListeners(bool testSession)
-{
-    // clean useless listeners
-    std::lock_guard<std::mutex> lock(lockListener_);
-    auto listener = currentListeners_.begin();
-    while (listener != currentListeners_.end()) {
-        auto cancel = dht_ and (not testSession or listener->session->is_closed());
-        if (cancel) {
-            dht_->cancelListen(listener->hash, std::move(listener->token));
-            // Remove listener if unused
-            listener = currentListeners_.erase(listener);
-        } else {
-             ++listener;
-        }
+    if (!dht_){
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_service_unavailable()));
+        response.set_body(RESP_MSG_SERVICE_UNAVAILABLE);
+        return response.done();
+    }
+
+    auto response = std::make_shared<ResponseByPartsBuilder>(
+        this->initHttpResponse(request->create_response<ResponseByParts>()));
+    response->flush();
+    try {
+        dht_->get(infoHash, [this, response](const dht::Sp<dht::Value>& value){
+            auto output = Json::writeString(jsonBuilder_, value->toJson()) + "\n";
+            response->append_chunk(output);
+            response->flush();
+            return true;
+        },
+        [response] (bool /*ok*/){
+            response->done();
+        },
+            {}, value
+        );
+    } catch (const std::exception& e){
+        auto response = this->initHttpResponse(
+            request->create_response(restinio::status_internal_server_error()));
+        response.set_body(RESP_MSG_INTERNAL_SERVER_ERRROR);
+        return response.done();
     }
+    return restinio::request_handling_status_t::accepted;
 }
 
 }
diff --git a/src/dhtrunner.cpp b/src/dhtrunner.cpp
index af782b3c536c4618c2e2289ef0b2d9e8599c8862..cc53237b84a26f6e7bc7c78b35f0e166e02ab4d2 100644
--- a/src/dhtrunner.cpp
+++ b/src/dhtrunner.cpp
@@ -114,6 +114,9 @@ DhtRunner::run(const Config& config, Context&& context)
     if (running)
         return;
 
+    if (context.logger)
+        logger_ = context.logger;
+
     context.sock->setOnReceive([&] (std::unique_ptr<net::ReceivedPacket>&& pkt) {
         {
             std::lock_guard<std::mutex> lck(sock_mtx);
@@ -969,7 +972,7 @@ DhtRunner::enableProxy(bool proxify)
                     }
                     cv.notify_all();
                 }
-            }, config_.proxy_server, config_.push_node_id)
+            }, config_.proxy_server, config_.push_node_id, logger_)
         );
         dht_via_proxy_ = std::unique_ptr<SecureDht>(new SecureDht(std::move(dht_via_proxy), config_.dht_config));
 #ifdef OPENDHT_PUSH_NOTIFICATIONS
diff --git a/src/http.cpp b/src/http.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4b22f1e2452bec69bdd22cc6dd17cf67a7a851fd
--- /dev/null
+++ b/src/http.cpp
@@ -0,0 +1,342 @@
+/*
+ *  Copyright (C) 2016-2019 Savoir-faire Linux Inc.
+ *  Author: Vsevolod Ivanov <vsevolod.ivanov@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "http.h"
+
+namespace http {
+
+// connection
+
+Connection::Connection(const uint16_t id, asio::ip::tcp::socket socket):
+    id_(id), socket_(std::move(socket))
+{}
+
+Connection::~Connection(){
+    close();
+}
+
+uint16_t
+Connection::id(){
+    return id_;
+}
+
+bool
+Connection::is_open(){
+    return socket_.is_open();
+}
+
+void
+Connection::close(){
+    socket_.close();
+}
+
+// connection listener
+
+ConnectionListener::ConnectionListener()
+{}
+
+ConnectionListener::ConnectionListener(std::shared_ptr<dht::DhtRunner> dht,
+    std::shared_ptr<std::map<restinio::connection_id_t, http::ListenerSession>> listeners,
+    std::shared_ptr<std::mutex> lock, std::shared_ptr<dht::Logger> logger):
+        dht_(dht), listeners_(listeners), lock_(lock), logger_(logger)
+{}
+
+ConnectionListener::~ConnectionListener()
+{}
+
+void
+ConnectionListener::state_changed(const restinio::connection_state::notice_t &notice) noexcept
+{
+    std::lock_guard<std::mutex> lock(*lock_);
+    auto id = notice.connection_id();
+    auto cause = to_str(notice.cause());
+
+    if (listeners_->find(id) != listeners_->end()){
+        if (notice.cause() == restinio::connection_state::cause_t::closed){
+            if (logger_)
+                logger_->d("[proxy:server] [connection:%li] cancelling listener", id);
+            dht_->cancelListen(listeners_->at(id).hash,
+                               std::move(listeners_->at(id).token));
+            listeners_->erase(id);
+            if (logger_)
+                logger_->d("[proxy:server] %li listeners are connected", listeners_->size());
+        }
+    }
+}
+
+std::string
+ConnectionListener::to_str(restinio::connection_state::cause_t cause) noexcept
+{
+    std::string result;
+    switch(cause)
+    {
+    case restinio::connection_state::cause_t::accepted:
+        result = "accepted";
+        break;
+    case restinio::connection_state::cause_t::closed:
+        result = "closed";
+        break;
+    case restinio::connection_state::cause_t::upgraded_to_websocket:
+        result = "upgraded";
+        break;
+    default:
+        result = "unknown";
+    }
+    return result;
+}
+
+// client
+
+Client::Client(asio::io_context &ctx, const std::string host, const uint16_t port,
+               std::shared_ptr<dht::Logger> logger):
+        resolver_(ctx), logger_(logger)
+{
+    set_query_address(host, port);
+}
+
+asio::io_context&
+Client::io_context()
+{
+    return resolver_.get_io_context();
+}
+
+void
+Client::set_logger(std::shared_ptr<dht::Logger> logger)
+{
+    logger_ = logger;
+}
+void
+Client::set_query_address(const std::string host, const uint16_t port)
+{
+    host_ = host;
+    port_ = port;
+}
+
+std::shared_ptr<Connection>
+Client::create_connection()
+{
+    auto conn = std::make_shared<Connection>(connId_,
+        std::move(asio::ip::tcp::socket{resolver_.get_io_context()}));
+    if (logger_)
+        logger_->d("[proxy:client] [connection:%i] created", conn->id());
+    connId_++;
+    return conn;
+}
+
+bool
+Client::active_connection(const uint16_t conn_id)
+{
+    auto req = requests_.find(conn_id);
+    if (req == requests_.end())
+        return false;
+    return req->second.connection->is_open();
+}
+
+void
+Client::close_connection(const uint16_t conn_id)
+{
+    auto &req = requests_[conn_id];
+    // close the socket
+    req.connection->close();
+    // remove from active requests
+    requests_.erase(conn_id);
+    if (logger_)
+        logger_->d("[proxy:client] [connection:%i] closed", conn_id);
+}
+
+std::string
+Client::create_request(const restinio::http_request_header_t header,
+                       const restinio::http_header_fields_t header_fields,
+                       const restinio::http_connection_header_t connection,
+                       const std::string body)
+{
+    std::stringstream request;
+
+    // first header
+    request << header.method().c_str() << " " << header.request_target() << " " <<
+               "HTTP/" << header.http_major() << "." << header.http_minor() << "\r\n";
+
+    // other headers
+    for (auto header_field: header_fields)
+        request << header_field.name() << ": " << header_field.value() << "\r\n";
+
+    // last connection header
+    std::string conn_str;
+    switch (connection){
+    case restinio::http_connection_header_t::keep_alive:
+        conn_str = "keep-alive";
+        break;
+    case restinio::http_connection_header_t::close:
+        conn_str = "close";
+        break;
+    case restinio::http_connection_header_t::upgrade:
+        throw std::invalid_argument("upgrade");
+        break;
+    }
+    request << "Connection: " << conn_str << "\r\n";
+
+    // body & content-length
+    if (!body.empty()){
+        request << "Content-Length: " << body.size() << "\r\n\r\n";
+        request << body;
+    }
+
+    // last delim
+    request << "\r\n";
+    return request.str();
+}
+
+uint16_t
+Client::post_request(std::string request,
+                     std::shared_ptr<http_parser> parser,
+                     std::shared_ptr<http_parser_settings> parser_s)
+{
+    auto conn = create_connection();
+
+    // save the request context
+    Request req = {};
+    req.connection = conn;
+    req.content = request;
+    req.parser = parser;
+    req.parser_settings = parser_s;
+    requests_[conn->id()] = req;
+
+    // write the request to buffer
+    std::ostream request_stream(&conn->request_);
+    request_stream << request;
+
+    // resolve the query to the server
+    asio::ip::tcp::resolver::query query(host_, std::to_string(port_));
+    resolver_.async_resolve(query,
+        std::bind(&Client::handle_resolve, this,
+            std::placeholders::_1, std::placeholders::_2, conn));
+
+    return conn->id();
+}
+
+void
+Client::handle_resolve(const asio::error_code &ec,
+                       asio::ip::tcp::resolver::iterator endpoint_it,
+                       std::shared_ptr<Connection> conn)
+{
+    if (ec){
+        if (logger_)
+            logger_->e("[proxy:client] [connection:%i] error resolving", conn->id());
+        conn->close();
+        return;
+    }
+    if (logger_){
+        logger_->d("[proxy:client] [connection:%i] resolved host=%s service=%s", conn->id(),
+            endpoint_it->host_name().c_str(), endpoint_it->service_name().c_str());
+    }
+    asio::ip::tcp::endpoint endpoint = *endpoint_it;
+    conn->socket_.async_connect(endpoint,
+        std::bind(&Client::handle_connect, this,
+                  std::placeholders::_1, ++endpoint_it, conn));
+}
+
+void
+Client::handle_connect(const asio::error_code &ec,
+                       asio::ip::tcp::resolver::iterator endpoint_it,
+                       std::shared_ptr<Connection> conn)
+{
+    if (ec){
+        if (logger_)
+            logger_->e("[proxy:client] [connection:%i] error opening: %s",
+                       conn->id(), ec.message().c_str());
+        close(conn->id());
+        return;
+    }
+    else if (endpoint_it != asio::ip::tcp::resolver::iterator()){
+        if (logger_)
+            logger_->e("[proxy:client] [connection:%i] error connecting, trying next endpoint", conn->id());
+        conn->socket_.close();
+        // connect to next one
+        asio::ip::tcp::endpoint endpoint = *endpoint_it;
+        conn->socket_.async_connect(endpoint,
+            std::bind(&Client::handle_connect, this,
+                      std::placeholders::_1, ++endpoint_it, conn));
+        return;
+    }
+    // send the request
+    asio::async_write(conn->socket_, conn->request_,
+        std::bind(&Client::handle_request, this, std::placeholders::_1, conn));
+}
+
+void
+Client::handle_request(const asio::error_code &ec, std::shared_ptr<Connection> conn)
+{
+    if (!conn->is_open())
+        return;
+
+    if (ec and ec != asio::error::eof){
+        if (logger_)
+            logger_->e("[proxy:client] [connection:%i] error handling request: %s",
+                       conn->id(), ec.message().c_str());
+        close_connection(conn->id());
+        return;
+    }
+    if (logger_)
+        logger_->d("[proxy:client] [connection:%i] request write", conn->id());
+
+    // read response
+    asio::async_read_until(conn->socket_, conn->response_, "\r\n\r\n",
+        std::bind(&Client::handle_response, this, std::placeholders::_1, conn));
+}
+
+void
+Client::handle_response(const asio::error_code &ec, std::shared_ptr<Connection> conn)
+{
+    if (!conn->is_open())
+        return;
+
+    if (ec && ec != asio::error::eof){
+        if (logger_)
+            logger_->e("[proxy:client] [connection:%i] error handling response: %s",
+                       conn->id(), ec.message().c_str());
+        return;
+    }
+    else if ((ec == asio::error::eof) || (ec == asio::error::connection_reset)){
+        close_connection(conn->id());
+        return;
+    }
+    if (logger_)
+        logger_->d("[proxy:client] [connection:%i] response read", conn->id());
+
+    // read the response buffer
+    std::ostringstream str_s;
+    str_s << &conn->response_;
+
+    // parse the request
+    auto &req = requests_[conn->id()];
+    http_parser_execute(req.parser.get(), req.parser_settings.get(),
+                        str_s.str().c_str(), str_s.str().size());
+
+    // detect parsing errors
+    if (HPE_OK != req.parser->http_errno && HPE_PAUSED != req.parser->http_errno){
+        if (logger_){
+            auto err = HTTP_PARSER_ERRNO(req.parser.get());
+            logger_->e("[proxy:client] [connection:%i] error parsing: %s",
+                        conn->id(), http_errno_name(err));
+        }
+    }
+    asio::async_read(conn->socket_, conn->response_, asio::transfer_at_least(1),
+        std::bind(&Client::handle_response, this, std::placeholders::_1, conn));
+}
+
+} // namespace http
diff --git a/tests/dhtproxytester.cpp b/tests/dhtproxytester.cpp
index 77a462a2cd37ee7c9aaa13b20ca1b018790e08f6..e1f2dd7eeac6a3223064d07689e304f750a94417 100644
--- a/tests/dhtproxytester.cpp
+++ b/tests/dhtproxytester.cpp
@@ -2,6 +2,7 @@
  *  Copyright (C) 2019 Savoir-faire Linux Inc.
  *
  *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *          Vsevolod Ivanov <vsevolod.ivanov@savoirfairelinux.com>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -32,26 +33,32 @@ CPPUNIT_TEST_SUITE_REGISTRATION(DhtProxyTester);
 
 void
 DhtProxyTester::setUp() {
-    nodePeer.run(0);
-    nodeProxy = std::make_shared<dht::DhtRunner>();
-    nodeClient = std::make_shared<dht::DhtRunner>();
+    logger = dht::log::getStdLogger();
+
+    nodePeer.run(0, /*identity*/{}, /*threaded*/true);
 
-    nodeProxy->run(0);
+    nodeProxy = std::make_shared<dht::DhtRunner>();
+    nodeProxy->run(0, /*identity*/{}, /*threaded*/true);
     nodeProxy->bootstrap(nodePeer.getBound());
-    server = std::unique_ptr<dht::DhtProxyServer>(new dht::DhtProxyServer(nodeProxy, 8080));
+    serverProxy = std::unique_ptr<dht::DhtProxyServer>(new dht::DhtProxyServer(
+        nodeProxy, 8080, /*pushServer*/"127.0.0.1:8090", logger));
 
-    nodeClient->run(0);
+    nodeClient = std::make_shared<dht::DhtRunner>();
+    nodeClient->run(0, /*identity*/{}, /*threaded*/true);
     nodeClient->bootstrap(nodePeer.getBound());
     nodeClient->setProxyServer("127.0.0.1:8080");
-    nodeClient->enableProxy(true);
+    nodeClient->enableProxy(true); // creates DhtProxyClient
 }
 
 void
 DhtProxyTester::tearDown() {
+    logger->d("[tester:proxy] stopping peer node");
     nodePeer.join();
     nodeClient->join();
-    server->stop();
-    server = nullptr;
+    logger->d("[tester:proxy] stopping proxy server");
+    serverProxy->stop();
+    serverProxy = nullptr;
+    logger->d("[tester:proxy] stopping proxy node");
     nodeProxy->join();
 }
 
@@ -161,7 +168,7 @@ DhtProxyTester::testResubscribeGetValues() {
 
     // Reboot node (to avoid cache)
     nodeClient->join();
-    nodeClient->run(42242, {}, true);
+    nodeClient->run(0, {}, true);
     nodeClient->bootstrap(nodePeer.getBound());
     nodeClient->setProxyServer("127.0.0.1:8080");
     nodeClient->enableProxy(true);
diff --git a/tests/dhtproxytester.h b/tests/dhtproxytester.h
index 067f70687d6e82514057fe9bcfea8230ae618bef..464ca0e9d7dd732278f6c8ccc4881164aae76928 100644
--- a/tests/dhtproxytester.h
+++ b/tests/dhtproxytester.h
@@ -25,6 +25,7 @@
 
 #include <opendht/dhtrunner.h>
 #include <opendht/dht_proxy_server.h>
+#include <opendht/log.h>
 
 namespace test {
 
@@ -61,11 +62,14 @@ class DhtProxyTester : public CppUnit::TestFixture {
    void testResubscribeGetValues();
 
  private:
-    dht::DhtRunner nodePeer {};
+    dht::DhtRunner nodePeer;
 
     std::shared_ptr<dht::DhtRunner> nodeClient;
     std::shared_ptr<dht::DhtRunner> nodeProxy;
-    std::unique_ptr<dht::DhtProxyServer> server;
+    std::unique_ptr<dht::DhtProxyServer> serverProxy;
+
+    dht::DhtRunner::Config config {};
+    std::shared_ptr<dht::Logger> logger {};
 };
 
 }  // namespace test
diff --git a/tools/dhtnode.cpp b/tools/dhtnode.cpp
index fc9e220dd89afd4dad1b2e17370a130151bcc9c0..2c9973aa1f4033c56b9796c1dc0bde42e50ec3f2 100644
--- a/tools/dhtnode.cpp
+++ b/tools/dhtnode.cpp
@@ -540,11 +540,12 @@ main(int argc, char **argv)
             else
                 context.logger = log::getStdLogger();
         }
-
         node->run(params.port, config, std::move(context));
+        if (context.logger)
+            log::enableLogging(*node);
 
         if (not params.bootstrap.first.empty()) {
-            //std::cout << "Bootstrap: " << params.bootstrap.first << ":" << params.bootstrap.second << std::endl;
+            std::cout << "Bootstrap: " << params.bootstrap.first << ":" << params.bootstrap.second << std::endl;
             node->bootstrap(params.bootstrap.first.c_str(), params.bootstrap.second.c_str());
         }
 
@@ -553,7 +554,7 @@ main(int argc, char **argv)
 #endif
         if (params.proxyserver != 0) {
 #ifdef OPENDHT_PROXY_SERVER
-            proxies.emplace(params.proxyserver, std::unique_ptr<DhtProxyServer>(new DhtProxyServer(node, params.proxyserver, params.pushserver)));
+            proxies.emplace(params.proxyserver, std::unique_ptr<DhtProxyServer>(new DhtProxyServer(node, params.proxyserver, params.pushserver, context.logger)));
 #else
             std::cerr << "DHT proxy server requested but OpenDHT built without proxy server support." << std::endl;
             exit(EXIT_FAILURE);
diff --git a/tools/proxy_loadtester.py b/tools/proxy_loadtester.py
new file mode 100644
index 0000000000000000000000000000000000000000..10da6bd584e1cfeb752173166b2ce95be4f0b327
--- /dev/null
+++ b/tools/proxy_loadtester.py
@@ -0,0 +1,82 @@
+# Copyright (C) 2017-2019 Savoir-faire Linux Inc.
+# Author: Vsevolod Ivanov <vsevolod.ivanov@savoirfairelinux.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+# Manually run with Web UI:
+#   locust -f tester.py --host http://127.0.0.1:8080
+#
+# Run in Terminal:
+#   locust -f tester.py --host http://127.0.0.1:8080 \
+#       --clients 100 --hatch-rate 1 --run-time 10s --no-web --only-summary
+
+from locust import HttpLocust, TaskSet
+from random import randint
+import urllib.request
+import base64
+import json
+
+words_url = "http://svnweb.freebsd.org/csrg/share/dict/words?view=co&content-type=text/plain"
+words_resp = urllib.request.urlopen(words_url)
+words = words_resp.read().decode().splitlines()
+
+headers = {'content-type': 'application/json'}
+
+def rand_list_value(mylist):
+    return mylist[randint(0, len(mylist) - 1)]
+
+def put_key(l):
+    key = rand_list_value(words)
+    val = rand_list_value(words)
+    print("Put/get: key={} value={}".format(key, val)) 
+    data = base64.b64encode(val.encode()).decode()
+    print("Base64 encoding: value={} encoded={}".format(val, data))
+    l.client.post("/" + key, data=json.dumps({"data": data}),
+                  headers=headers, catch_response=True)
+
+def get_key(l):
+    key = rand_list_value(words)
+    print("Get: key={}".format(key)) 
+    l.client.get("/" + key)
+
+def get_stats(l):
+    l.client.get("/stats")
+
+def subscribe(l):
+    key = rand_list_value(words)
+    print("Subscribe: key={}".format(key))
+    l.client.get("/" + key + "/subscribe")
+
+def listen(l):
+    key = rand_list_value(words)
+    print("Listen: key={}".format(key))
+    l.client.get("/" + key + "/listen")
+
+class UserBehavior(TaskSet):
+    tasks = {get_key: 5, put_key: 5, get_stats: 1, subscribe: 1, listen: 1}
+
+    def on_start(self):
+        put_key(self)
+        get_key(self)
+        subscribe(self)
+        listen(self)
+
+    def on_stop(self):
+        get_stats(self)
+
+class WebsiteUser(HttpLocust):
+    task_set = UserBehavior
+    min_wait = 5000
+    max_wait = 9000
+    print("Initiate the benchmark at http://127.0.0.1:8089/")