Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add websocket support #236

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion examples/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@

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
noinst_PROGRAMS = hello_world service minimal_hello_world hello_world_websocket 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

hello_world_SOURCES = hello_world.cpp
service_SOURCES = service.cpp
minimal_hello_world_SOURCES = minimal_hello_world.cpp
hello_world_websocket_SOURCES = hello_world_websocket.cpp
custom_error_SOURCES = custom_error.cpp
allowing_disallowing_methods_SOURCES = allowing_disallowing_methods.cpp
handlers_SOURCES = handlers.cpp
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