Skip to content

Commit

Permalink
Add websocket support
Browse files Browse the repository at this point in the history
  • Loading branch information
Your Name committed Jun 21, 2021
1 parent c5cf5ea commit c032b0b
Show file tree
Hide file tree
Showing 9 changed files with 822 additions and 13 deletions.
1 change: 1 addition & 0 deletions examples/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

LDADD = $(top_builddir)/src/libhttpserver.la
AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver/
AM_LDFLAGS = -lpthread
METASOURCES = AUTO
noinst_PROGRAMS = hello_world service minimal_hello_world custom_error allowing_disallowing_methods handlers hello_with_get_arg setting_headers custom_access_log basic_authentication digest_authentication minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads benchmark_nodelay deferred_with_accumulator

Expand Down
107 changes: 107 additions & 0 deletions examples/hello_world_websocket.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
This file is part of libhttpserver
Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
*/

#include <iostream>

#include <httpserver.hpp>

#define CHAT_PAGE \
"<html>\n" \
"<head>\n" \
"<title>WebSocket chat</title>\n" \
"<script>\n" \
"document.addEventListener('DOMContentLoaded', function() {\n" \
" const ws = new WebSocket('ws://' + window.location.host + '/ws');\n" \
" const btn = document.getElementById('send');\n" \
" const msg = document.getElementById('msg');\n" \
" const log = document.getElementById('log');\n" \
" ws.onopen = function() {\n" \
" log.value += 'Connected\\n';\n" \
" };\n" \
" ws.onclose = function() {\n" \
" log.value += 'Disconnected\\n';\n" \
" };\n" \
" ws.onmessage = function(ev) {\n" \
" log.value += ev.data + '\\n';\n" \
" };\n" \
" btn.onclick = function() {\n" \
" log.value += '<You>: ' + msg.value + '\\n';\n" \
" ws.send(msg.value);\n" \
" };\n" \
" msg.onkeyup = function(ev) {\n" \
" if (ev.keyCode === 13) {\n" \
" ev.preventDefault();\n" \
" ev.stopPropagation();\n" \
" btn.click();\n" \
" msg.value = '';\n" \
" }\n" \
" };\n" \
"});\n" \
"</script>\n" \
"</head>\n" \
"<body>\n" \
"<input type='text' id='msg' autofocus/>\n" \
"<input type='button' id='send' value='Send' /><br /><br />\n" \
"<textarea id='log' rows='20' cols='28'></textarea>\n" \
"</body>\n" \
"</html>"

class hello_world_resource : public httpserver::http_resource, public httpserver::websocket_handler {
public:
const std::shared_ptr<httpserver::http_response> render(const httpserver::http_request&);
virtual std::thread handle_websocket(httpserver::websocket* ws) override;
};

// Using the render method you are able to catch each type of request you receive
const std::shared_ptr<httpserver::http_response> hello_world_resource::render(const httpserver::http_request& req) {
// It is possible to send a response initializing an http_string_response that reads the content to send in response from a string.
return std::shared_ptr<httpserver::http_response>(new httpserver::string_response(CHAT_PAGE, 200, "text/html"));
}

std::thread hello_world_resource::handle_websocket(httpserver::websocket* ws) {
return std::thread([ws]{
while (!ws->disconnect()) {
ws->send("hello world");
usleep(1000 * 1000);
std::string message;
if (ws->receive(message, 100)) {
ws->send("server received: " + message);
}
}
});
}

int main() {
// It is possible to create a webserver passing a great number of parameters. In this case we are just passing the port and the number of thread running.
httpserver::webserver ws = httpserver::create_webserver(8080).start_method(httpserver::http::http_utils::INTERNAL_SELECT).max_threads(1);

hello_world_resource hwr;
// This way we are registering the hello_world_resource to answer for the endpoint
// "/hello". The requested method is called (if the request is a GET we call the render_GET
// method. In case that the specific render method is not implemented, the generic "render"
// method is called.
ws.register_resource("/hello", &hwr, true);
ws.register_resource("/ws", &hwr, true);

// This way we are putting the created webserver in listen. We pass true in order to have
// a blocking call; if we want the call to be non-blocking we can just pass false to the method.
ws.start(true);
return 0;
}
4 changes: 2 additions & 2 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
AM_CPPFLAGS = -I../ -I$(srcdir)/httpserver/
METASOURCES = AUTO
lib_LTLIBRARIES = libhttpserver.la
libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp http_request.cpp http_response.cpp string_response.cpp basic_auth_fail_response.cpp digest_auth_fail_response.cpp deferred_response.cpp file_response.cpp http_resource.cpp details/http_endpoint.cpp
libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp http_request.cpp http_response.cpp string_response.cpp websocket.cpp basic_auth_fail_response.cpp digest_auth_fail_response.cpp deferred_response.cpp file_response.cpp http_resource.cpp details/http_endpoint.cpp
noinst_HEADERS = httpserver/string_utilities.hpp httpserver/details/modded_request.hpp gettext.h
nobase_include_HEADERS = httpserver.hpp httpserver/create_webserver.hpp httpserver/webserver.hpp httpserver/http_utils.hpp httpserver/details/http_endpoint.hpp httpserver/http_request.hpp httpserver/http_response.hpp httpserver/http_resource.hpp httpserver/string_response.hpp httpserver/basic_auth_fail_response.hpp httpserver/digest_auth_fail_response.hpp httpserver/deferred_response.hpp httpserver/file_response.hpp

Expand All @@ -32,7 +32,7 @@ AM_LDFLAGS += -O0 --coverage -lgcov --no-inline
endif

if !COND_CROSS_COMPILE
libhttpserver_la_LIBADD = -lmicrohttpd
libhttpserver_la_LIBADD = -lmicrohttpd -lmicrohttpd_ws
endif

libhttpserver_la_CFLAGS = $(AM_CFLAGS)
Expand Down
2 changes: 2 additions & 0 deletions src/httpserver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#include "httpserver/file_response.hpp"
#include "httpserver/http_request.hpp"
#include "httpserver/http_resource.hpp"
#include "httpserver/websocket.hpp"
#include "httpserver/websocket_handler.hpp"
#include "httpserver/http_response.hpp"
#include "httpserver/http_utils.hpp"
#include "httpserver/string_response.hpp"
Expand Down
20 changes: 19 additions & 1 deletion src/httpserver/webserver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@

namespace httpserver { class http_resource; }
namespace httpserver { class http_response; }
namespace httpserver { class websocket; }
namespace httpserver { class websocket_handler; }
namespace httpserver { namespace details { struct modded_request; } }

struct MHD_Connection;
Expand Down Expand Up @@ -182,6 +184,10 @@ class webserver {
const std::shared_ptr<http_response> internal_error_page(details::modded_request* mr, bool force_our = false) const;
const std::shared_ptr<http_response> not_found_page(details::modded_request* mr) const;

MHD_Result create_websocket_connection(
websocket_handler* ws_handler,
MHD_Connection *connection);

static void request_completed(void *cls,
struct MHD_Connection *connection, void **con_cls,
enum MHD_RequestTerminationCode toe);
Expand All @@ -194,7 +200,19 @@ class webserver {
const char *filename, const char *content_type, const char *transfer_encoding,
const char *data, uint64_t off, size_t size);

static void upgrade_handler(void *cls, struct MHD_Connection* connection, void **con_cls, int upgrade_socket);
static int connecteduser_parse_received_websocket_stream (websocket* cu,
char* buf,
size_t buf_len);

static void* connecteduser_receive_messages (void* cls);

static void upgrade_handler(void *cls,
struct MHD_Connection *connection,
void *con_cls,
const char *extra_in,
size_t extra_in_size,
MHD_socket fd,
struct MHD_UpgradeResponseHandle *urh);

MHD_Result requests_answer_first_step(MHD_Connection* connection, struct details::modded_request* mr);

Expand Down
77 changes: 77 additions & 0 deletions src/httpserver/websocket.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
This file is part of libhttpserver
Copyright (C) 2011-2019 Sebastiano Merlino
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
*/

#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION)
#error "Only <httpserver.hpp> or <httpserverpp> can be included directly."
#endif

#ifndef SRC_HTTPSERVER_WEBSOCKET_HPP_
#define SRC_HTTPSERVER_WEBSOCKET_HPP_

#include <string>
#include <microhttpd.h>
#include <list>
#include <thread>
#include <mutex>
#include <condition_variable>

struct MHD_UpgradeResponseHandle;
struct MHD_WebSocketStream;

namespace httpserver {

class websocket {
public:
void send(const std::string& message);
std::string receive();
bool receive(std::string& message, uint64_t timeout_milliseconds);
bool disconnect() const;
private:
/**
* Sends all data of the given buffer via the TCP/IP socket
*
* @param fd The TCP/IP socket which is used for sending
* @param buf The buffer with the data to send
* @param len The length in bytes of the data in the buffer
*/
void send_raw(const char* buf, size_t len);
void insert_into_receive_queue(const std::string& message);

/* the TCP/IP socket for reading/writing */
MHD_socket fd = 0;
/* the UpgradeResponseHandle of libmicrohttpd (needed for closing the socket) */
MHD_UpgradeResponseHandle* urh = nullptr;
/* the websocket encode/decode stream */
MHD_WebSocketStream* ws = nullptr;
/* the possibly read data at the start (only used once) */
char *extra_in = nullptr;
size_t extra_in_size = 0;
/* specifies whether the websocket shall be closed (1) or not (0) */
bool disconnect_ = false;
class websocket_handler* ws_handler = nullptr;
std::mutex receive_mutex_;
std::condition_variable receive_cv_;
std::list<std::string> received_messages_;
friend class webserver;
};

} // namespace httpserver

#endif // SRC_HTTPSERVER_STRING_UTILITIES_HPP_
53 changes: 53 additions & 0 deletions src/httpserver/websocket_handler.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
This file is part of libhttpserver
Copyright (C) 2011-2019 Sebastiano Merlino
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION)
#error "Only <httpserver.hpp> or <httpserverpp> can be included directly."
#endif

#ifndef SRC_HTTPSERVER_WEBSOCKET_HANDLER_HPP_
#define SRC_HTTPSERVER_WEBSOCKET_HANDLER_HPP_

#include <thread>

namespace httpserver { class websocket; }

namespace httpserver {

/**
* Class representing a callable websocket resource.
**/
class websocket_handler {
public:
/**
* Class destructor
**/
virtual ~websocket_handler() = default;

/**
* Method used to handle a websocket connection
* @param ws Websocket
* @return A thread object handling the websocket
**/
virtual std::thread handle_websocket(websocket* ws) = 0;
};

} // namespace httpserver
#endif // SRC_HTTPSERVER_WEBSOCKET_HANDLER_HPP_
Loading

0 comments on commit c032b0b

Please sign in to comment.