Skip to content

Commit 03cf7c5

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

10 files changed

+881
-24
lines changed

examples/Makefile.am

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
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
2223
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
2324

examples/hello_world.cpp

+59-11
Original file line numberDiff line numberDiff line change
@@ -22,35 +22,83 @@
2222

2323
#include <httpserver.hpp>
2424

25-
class hello_world_resource : public httpserver::http_resource {
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 {
2667
public:
2768
const std::shared_ptr<httpserver::http_response> render(const httpserver::http_request&);
28-
void set_some_data(const std::string &s) {data = s;}
29-
std::string data;
69+
virtual std::thread handle_websocket(httpserver::websocket* ws) override;
3070
};
3171

3272
// Using the render method you are able to catch each type of request you receive
3373
const std::shared_ptr<httpserver::http_response> hello_world_resource::render(const httpserver::http_request& req) {
34-
// It is possible to store data inside the resource object that can be altered through the requests
35-
std::cout << "Data was: " << data << std::endl;
36-
std::string datapar = req.get_arg("data");
37-
set_some_data(datapar == "" ? "no data passed!!!" : datapar);
38-
std::cout << "Now data is:" << data << std::endl;
39-
4074
// It is possible to send a response initializing an http_string_response that reads the content to send in response from a string.
41-
return std::shared_ptr<httpserver::http_response>(new httpserver::string_response("Hello World!!!", 200));
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+
});
4289
}
4390

4491
int main() {
4592
// 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.
46-
httpserver::webserver ws = httpserver::create_webserver(8080).start_method(httpserver::http::http_utils::INTERNAL_SELECT).max_threads(5);
93+
httpserver::webserver ws = httpserver::create_webserver(8080).start_method(httpserver::http::http_utils::INTERNAL_SELECT).max_threads(1);
4794

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

55103
// This way we are putting the created webserver in listen. We pass true in order to have
56104
// a blocking call; if we want the call to be non-blocking we can just pass false to the method.

examples/hello_world_websocket.cpp

+107
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

+2-2
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

+2
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

+19-1
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

+77
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_

0 commit comments

Comments
 (0)