Skip to content

Commit b3d7021

Browse files
author
Your Name
committed
Add websocket support
1 parent c5cf5ea commit b3d7021

File tree

9 files changed

+824
-14
lines changed

9 files changed

+824
-14
lines changed

examples/Makefile.am

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818

1919
LDADD = $(top_builddir)/src/libhttpserver.la
2020
AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver/
21+
AM_LDFLAGS = -lpthread
2122
METASOURCES = AUTO
22-
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
23+
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
2324

2425
hello_world_SOURCES = hello_world.cpp
2526
service_SOURCES = service.cpp
2627
minimal_hello_world_SOURCES = minimal_hello_world.cpp
28+
hello_world_websocket_SOURCES = hello_world_websocket.cpp
2729
custom_error_SOURCES = custom_error.cpp
2830
allowing_disallowing_methods_SOURCES = allowing_disallowing_methods.cpp
2931
handlers_SOURCES = handlers.cpp

examples/hello_world_websocket.cpp

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
This file is part of libhttpserver
3+
Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino
4+
5+
This library is free software; you can redistribute it and/or
6+
modify it under the terms of the GNU Lesser General Public
7+
License as published by the Free Software Foundation; either
8+
version 2.1 of the License, or (at your option) any later version.
9+
10+
This library is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public
16+
License along with this library; if not, write to the Free Software
17+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
18+
USA
19+
*/
20+
21+
#include <iostream>
22+
23+
#include <httpserver.hpp>
24+
25+
#define CHAT_PAGE \
26+
"<html>\n" \
27+
"<head>\n" \
28+
"<title>WebSocket chat</title>\n" \
29+
"<script>\n" \
30+
"document.addEventListener('DOMContentLoaded', function() {\n" \
31+
" const ws = new WebSocket('ws://' + window.location.host + '/ws');\n" \
32+
" const btn = document.getElementById('send');\n" \
33+
" const msg = document.getElementById('msg');\n" \
34+
" const log = document.getElementById('log');\n" \
35+
" ws.onopen = function() {\n" \
36+
" log.value += 'Connected\\n';\n" \
37+
" };\n" \
38+
" ws.onclose = function() {\n" \
39+
" log.value += 'Disconnected\\n';\n" \
40+
" };\n" \
41+
" ws.onmessage = function(ev) {\n" \
42+
" log.value += ev.data + '\\n';\n" \
43+
" };\n" \
44+
" btn.onclick = function() {\n" \
45+
" log.value += '<You>: ' + msg.value + '\\n';\n" \
46+
" ws.send(msg.value);\n" \
47+
" };\n" \
48+
" msg.onkeyup = function(ev) {\n" \
49+
" if (ev.keyCode === 13) {\n" \
50+
" ev.preventDefault();\n" \
51+
" ev.stopPropagation();\n" \
52+
" btn.click();\n" \
53+
" msg.value = '';\n" \
54+
" }\n" \
55+
" };\n" \
56+
"});\n" \
57+
"</script>\n" \
58+
"</head>\n" \
59+
"<body>\n" \
60+
"<input type='text' id='msg' autofocus/>\n" \
61+
"<input type='button' id='send' value='Send' /><br /><br />\n" \
62+
"<textarea id='log' rows='20' cols='28'></textarea>\n" \
63+
"</body>\n" \
64+
"</html>"
65+
66+
class hello_world_resource : public httpserver::http_resource, public httpserver::websocket_handler {
67+
public:
68+
const std::shared_ptr<httpserver::http_response> render(const httpserver::http_request&);
69+
virtual std::thread handle_websocket(httpserver::websocket* ws) override;
70+
};
71+
72+
// Using the render method you are able to catch each type of request you receive
73+
const std::shared_ptr<httpserver::http_response> hello_world_resource::render(const httpserver::http_request& req) {
74+
// It is possible to send a response initializing an http_string_response that reads the content to send in response from a string.
75+
return std::shared_ptr<httpserver::http_response>(new httpserver::string_response(CHAT_PAGE, 200, "text/html"));
76+
}
77+
78+
std::thread hello_world_resource::handle_websocket(httpserver::websocket* ws) {
79+
return std::thread([ws]{
80+
while (!ws->disconnect()) {
81+
ws->send("hello world");
82+
usleep(1000 * 1000);
83+
std::string message;
84+
if (ws->receive(message, 100)) {
85+
ws->send("server received: " + message);
86+
}
87+
}
88+
});
89+
}
90+
91+
int main() {
92+
// 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.
93+
httpserver::webserver ws = httpserver::create_webserver(8080).start_method(httpserver::http::http_utils::INTERNAL_SELECT).max_threads(1);
94+
95+
hello_world_resource hwr;
96+
// This way we are registering the hello_world_resource to answer for the endpoint
97+
// "/hello". The requested method is called (if the request is a GET we call the render_GET
98+
// method. In case that the specific render method is not implemented, the generic "render"
99+
// method is called.
100+
ws.register_resource("/hello", &hwr, true);
101+
ws.register_resource("/ws", &hwr, true);
102+
103+
// This way we are putting the created webserver in listen. We pass true in order to have
104+
// a blocking call; if we want the call to be non-blocking we can just pass false to the method.
105+
ws.start(true);
106+
return 0;
107+
}

src/Makefile.am

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
AM_CPPFLAGS = -I../ -I$(srcdir)/httpserver/
2020
METASOURCES = AUTO
2121
lib_LTLIBRARIES = libhttpserver.la
22-
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
22+
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
2323
noinst_HEADERS = httpserver/string_utilities.hpp httpserver/details/modded_request.hpp gettext.h
2424
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
2525

@@ -32,7 +32,7 @@ AM_LDFLAGS += -O0 --coverage -lgcov --no-inline
3232
endif
3333

3434
if !COND_CROSS_COMPILE
35-
libhttpserver_la_LIBADD = -lmicrohttpd
35+
libhttpserver_la_LIBADD = -lmicrohttpd -lmicrohttpd_ws
3636
endif
3737

3838
libhttpserver_la_CFLAGS = $(AM_CFLAGS)

src/httpserver.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
#include "httpserver/file_response.hpp"
3030
#include "httpserver/http_request.hpp"
3131
#include "httpserver/http_resource.hpp"
32+
#include "httpserver/websocket.hpp"
33+
#include "httpserver/websocket_handler.hpp"
3234
#include "httpserver/http_response.hpp"
3335
#include "httpserver/http_utils.hpp"
3436
#include "httpserver/string_response.hpp"

src/httpserver/webserver.hpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151

5252
namespace httpserver { class http_resource; }
5353
namespace httpserver { class http_response; }
54+
namespace httpserver { class websocket; }
55+
namespace httpserver { class websocket_handler; }
5456
namespace httpserver { namespace details { struct modded_request; } }
5557

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

187+
MHD_Result create_websocket_connection(
188+
websocket_handler* ws_handler,
189+
MHD_Connection *connection);
190+
185191
static void request_completed(void *cls,
186192
struct MHD_Connection *connection, void **con_cls,
187193
enum MHD_RequestTerminationCode toe);
@@ -194,7 +200,19 @@ class webserver {
194200
const char *filename, const char *content_type, const char *transfer_encoding,
195201
const char *data, uint64_t off, size_t size);
196202

197-
static void upgrade_handler(void *cls, struct MHD_Connection* connection, void **con_cls, int upgrade_socket);
203+
static int connecteduser_parse_received_websocket_stream (websocket* cu,
204+
char* buf,
205+
size_t buf_len);
206+
207+
static void* connecteduser_receive_messages (void* cls);
208+
209+
static void upgrade_handler(void *cls,
210+
struct MHD_Connection *connection,
211+
void *con_cls,
212+
const char *extra_in,
213+
size_t extra_in_size,
214+
MHD_socket fd,
215+
struct MHD_UpgradeResponseHandle *urh);
198216

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

src/httpserver/websocket.hpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
This file is part of libhttpserver
3+
Copyright (C) 2011-2019 Sebastiano Merlino
4+
5+
This library is free software; you can redistribute it and/or
6+
modify it under the terms of the GNU Lesser General Public
7+
License as published by the Free Software Foundation; either
8+
version 2.1 of the License, or (at your option) any later version.
9+
10+
This library is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public
16+
License along with this library; if not, write to the Free Software
17+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
18+
USA
19+
*/
20+
21+
#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION)
22+
#error "Only <httpserver.hpp> or <httpserverpp> can be included directly."
23+
#endif
24+
25+
#ifndef SRC_HTTPSERVER_WEBSOCKET_HPP_
26+
#define SRC_HTTPSERVER_WEBSOCKET_HPP_
27+
28+
#include <string>
29+
#include <microhttpd.h>
30+
#include <list>
31+
#include <thread>
32+
#include <mutex>
33+
#include <condition_variable>
34+
35+
struct MHD_UpgradeResponseHandle;
36+
struct MHD_WebSocketStream;
37+
38+
namespace httpserver {
39+
40+
class websocket {
41+
public:
42+
void send(const std::string& message);
43+
std::string receive();
44+
bool receive(std::string& message, uint64_t timeout_milliseconds);
45+
bool disconnect() const;
46+
private:
47+
/**
48+
* Sends all data of the given buffer via the TCP/IP socket
49+
*
50+
* @param fd The TCP/IP socket which is used for sending
51+
* @param buf The buffer with the data to send
52+
* @param len The length in bytes of the data in the buffer
53+
*/
54+
void send_raw(const char* buf, size_t len);
55+
void insert_into_receive_queue(const std::string& message);
56+
57+
/* the TCP/IP socket for reading/writing */
58+
MHD_socket fd = 0;
59+
/* the UpgradeResponseHandle of libmicrohttpd (needed for closing the socket) */
60+
MHD_UpgradeResponseHandle* urh = nullptr;
61+
/* the websocket encode/decode stream */
62+
MHD_WebSocketStream* ws = nullptr;
63+
/* the possibly read data at the start (only used once) */
64+
char *extra_in = nullptr;
65+
size_t extra_in_size = 0;
66+
/* specifies whether the websocket shall be closed (1) or not (0) */
67+
bool disconnect_ = false;
68+
class websocket_handler* ws_handler = nullptr;
69+
std::mutex receive_mutex_;
70+
std::condition_variable receive_cv_;
71+
std::list<std::string> received_messages_;
72+
friend class webserver;
73+
};
74+
75+
} // namespace httpserver
76+
77+
#endif // SRC_HTTPSERVER_STRING_UTILITIES_HPP_

src/httpserver/websocket_handler.hpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
This file is part of libhttpserver
3+
Copyright (C) 2011-2019 Sebastiano Merlino
4+
5+
This library is free software; you can redistribute it and/or
6+
modify it under the terms of the GNU Lesser General Public
7+
License as published by the Free Software Foundation; either
8+
version 2.1 of the License, or (at your option) any later version.
9+
10+
This library is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public
16+
License along with this library; if not, write to the Free Software
17+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
19+
*/
20+
21+
#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION)
22+
#error "Only <httpserver.hpp> or <httpserverpp> can be included directly."
23+
#endif
24+
25+
#ifndef SRC_HTTPSERVER_WEBSOCKET_HANDLER_HPP_
26+
#define SRC_HTTPSERVER_WEBSOCKET_HANDLER_HPP_
27+
28+
#include <thread>
29+
30+
namespace httpserver { class websocket; }
31+
32+
namespace httpserver {
33+
34+
/**
35+
* Class representing a callable websocket resource.
36+
**/
37+
class websocket_handler {
38+
public:
39+
/**
40+
* Class destructor
41+
**/
42+
virtual ~websocket_handler() = default;
43+
44+
/**
45+
* Method used to handle a websocket connection
46+
* @param ws Websocket
47+
* @return A thread object handling the websocket
48+
**/
49+
virtual std::thread handle_websocket(websocket* ws) = 0;
50+
};
51+
52+
} // namespace httpserver
53+
#endif // SRC_HTTPSERVER_WEBSOCKET_HANDLER_HPP_

0 commit comments

Comments
 (0)