Skip to content

Commit c5e2136

Browse files
committed
Websocket support using mod_websocket.c
1 parent de2dda1 commit c5e2136

8 files changed

Lines changed: 425 additions & 39 deletions

File tree

.vscode/c_cpp_properties.json

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{
2+
"configurations": [
3+
{
4+
"name": "Mac",
5+
"includePath": [
6+
"/usr/include",
7+
"/usr/local/include",
8+
"${workspaceRoot}"
9+
],
10+
"defines": [],
11+
"intelliSenseMode": "clang-x64",
12+
"browse": {
13+
"path": [
14+
"/usr/include",
15+
"/usr/local/include",
16+
"${workspaceRoot}"
17+
],
18+
"limitSymbolsToIncludedHeaders": true,
19+
"databaseFilename": ""
20+
},
21+
"macFrameworkPath": [
22+
"/System/Library/Frameworks",
23+
"/Library/Frameworks"
24+
]
25+
},
26+
{
27+
"name": "Linux",
28+
"includePath": [
29+
"/usr/include/x86_64-linux-gnu/c++/5",
30+
"/usr/include/c++/5",
31+
"/usr/local/include",
32+
"/usr/lib/clang/3.8.0/include",
33+
"/usr/include/x86_64-linux-gnu",
34+
"/usr/include",
35+
"${workspaceRoot}",
36+
"${workspaceRoot}/../apache-websocket/",
37+
"/usr/include/apache2",
38+
"/usr/include/apr-1.0"
39+
],
40+
"defines": [],
41+
"intelliSenseMode": "clang-x64",
42+
"browse": {
43+
"path": [
44+
"/usr/include/x86_64-linux-gnu/c++/5",
45+
"/usr/include/c++/5",
46+
"/usr/local/include",
47+
"/usr/lib/clang/3.8.0/include",
48+
"/usr/include/x86_64-linux-gnu",
49+
"/usr/include",
50+
"${workspaceRoot}"
51+
],
52+
"limitSymbolsToIncludedHeaders": true,
53+
"databaseFilename": ""
54+
}
55+
},
56+
{
57+
"name": "Win32",
58+
"includePath": [
59+
"C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include",
60+
"${workspaceRoot}"
61+
],
62+
"defines": [
63+
"_DEBUG",
64+
"UNICODE"
65+
],
66+
"intelliSenseMode": "msvc-x64",
67+
"browse": {
68+
"path": [
69+
"C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include/*",
70+
"${workspaceRoot}"
71+
],
72+
"limitSymbolsToIncludedHeaders": true,
73+
"databaseFilename": ""
74+
}
75+
}
76+
],
77+
"version": 3
78+
}

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"files.associations": {
3+
"thread": "cpp"
4+
}
5+
}

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ OBJ = $(FSRC:=.o)
99

1010
DEP_DIR = .deps
1111

12-
FLAGS = -fPIC -DPIC -Wall -Wno-unknown-pragmas -fstack-protector-strong -flto `pkg-config --cflags apr-1` -I`apxs -q INCLUDEDIR`
12+
FLAGS = -fPIC -DPIC -Wall -Wno-unknown-pragmas -fstack-protector-strong -flto `pkg-config --cflags apr-1` -I`apxs -q INCLUDEDIR` -I ../apache-websocket/
1313
CXXFLAGS = -std=c++14
1414
CFLAGS =
1515
LINKFLAGS = -lprotobuf -lgrpc++

handler.cpp

Lines changed: 137 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,6 @@ size_t read_body(Func func, request_rec* r) {
7171
return total_size;
7272
}
7373

74-
#define PTABLE(s, table) print_table(s, #table, table)
75-
#define PSTR(str) ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, "%s = %s", #str, str);
76-
7774
void print_table(server_rec* s, const char* name, apr_table_t* table) {
7875
auto *fields = apr_table_elts(table);
7976
auto *e = (apr_table_entry_t *)fields->elts;
@@ -94,10 +91,8 @@ class grpc_connection_provider {
9491
std::map<std::string, con_entry> channels;
9592
public:
9693
grpc_connection_provider() {
97-
printf("ConProvider create\n");
9894
}
9995
~grpc_connection_provider() {
100-
printf("ConProvider destroy\n");
10196
}
10297

10398
std::unique_ptr<::thalhammer::http::Handler::Stub> getStub(const char* host, int64_t timeout) {
@@ -137,7 +132,12 @@ class grpc_connection_provider {
137132

138133
static grpc_connection_provider con_provider;
139134

140-
int handle_request(request_rec* r, const grpcbackend_config_t* config) {
135+
http_handler::http_handler(request_rec* r) {
136+
this->r = r;
137+
this->config = static_cast<grpcbackend_config_t*>(ap_get_module_config(r->per_dir_config, &grpcbackend_module));
138+
}
139+
140+
int http_handler::handle_request() {
141141
if(config->host == nullptr)
142142
return HTTP_INTERNAL_SERVER_ERROR;
143143
auto stub = con_provider.getStub(config->host, config->connect_timeout_ms);
@@ -183,26 +183,6 @@ int handle_request(request_rec* r, const grpcbackend_config_t* config) {
183183
}
184184
}
185185

186-
/*
187-
// Debug helpers
188-
PSTR(r->uri);
189-
PSTR(r->unparsed_uri);
190-
PSTR(r->parsed_uri.scheme);
191-
PSTR(r->parsed_uri.hostinfo);
192-
PSTR(r->parsed_uri.user);
193-
PSTR(r->parsed_uri.password);
194-
PSTR(r->parsed_uri.hostname);
195-
PSTR(r->parsed_uri.port_str);
196-
PSTR(r->parsed_uri.path);
197-
PSTR(r->parsed_uri.query);
198-
PSTR(r->parsed_uri.fragment);
199-
PSTR(r->filename);
200-
PSTR(r->canonical_filename);
201-
PSTR(r->path_info);
202-
PSTR(r->args);
203-
PSTR(r->hostname);
204-
*/
205-
206186
bool failed = false;
207187
read_body([&stream, &failed](const char* data, size_t len){
208188
::thalhammer::http::HandleRequest req;
@@ -254,3 +234,134 @@ int handle_request(request_rec* r, const grpcbackend_config_t* config) {
254234

255235
return DONE;
256236
}
237+
238+
void websocket_handler::send(int type, const uint8_t* buffer, size_t buffer_size)
239+
{
240+
_server->send(_server, type, buffer, buffer_size);
241+
}
242+
243+
websocket_handler::websocket_handler(const WebSocketServer* server)
244+
: _server(server)
245+
{
246+
auto* r = server->request(server);
247+
auto* config = static_cast<grpcbackend_config_t*>(ap_get_module_config(r->per_dir_config, &grpcbackend_module));
248+
if(!config->host)
249+
throw std::runtime_error("Missing grpc host");
250+
_stub = con_provider.getStub(config->host, config->connect_timeout_ms);
251+
252+
if(!_stub) {
253+
throw std::runtime_error("GRPC Backend timeout");
254+
}
255+
256+
_stream = _stub->HandleWebSocket(&_call_context);
257+
258+
{
259+
::thalhammer::http::HandleWebSocketRequest req;
260+
auto* client = req.mutable_request()->mutable_client();
261+
auto* con = r->connection;
262+
client->set_local_port(con->local_addr->port);
263+
client->set_local_ip(apr_addr_to_string(con->local_addr));
264+
client->set_remote_port(con->client_addr->port);
265+
client->set_remote_ip(apr_addr_to_string(con->client_addr));
266+
client->set_encrypted(!strcmp(ap_http_scheme(r), "https"));
267+
auto* request = req.mutable_request();
268+
request->set_method(r->method);
269+
request->set_protocol(r->protocol);
270+
request->set_resource(r->unparsed_uri);
271+
272+
auto *fields = apr_table_elts(r->headers_in);
273+
auto *e = (apr_table_entry_t *)fields->elts;
274+
for(int i = 0; i < fields->nelts; i++) {
275+
auto* header = request->add_headers();
276+
header->set_key(e[i].key);
277+
header->set_value(e[i].val);
278+
}
279+
280+
if(!_stream->Write(req)) {
281+
con_provider.reset_cache(config->host);
282+
throw std::runtime_error("Failed to write initial grpc request");
283+
}
284+
}
285+
286+
{
287+
::thalhammer::http::HandleWebSocketResponse resp;
288+
if(!_stream->Read(&resp)) {
289+
con_provider.reset_cache(config->host);
290+
throw std::runtime_error("Failed to read initial grpc response");
291+
} else {
292+
for(auto& header : resp.response().headers()) {
293+
apr_table_setn(r->headers_out, apr_pstrdup(r->pool, header.key().c_str()), apr_pstrdup(r->pool, header.value().c_str()));
294+
std::string key = header.key();
295+
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
296+
}
297+
}
298+
}
299+
300+
_recv_thread = std::thread([this,r](){
301+
try {
302+
::thalhammer::http::HandleWebSocketResponse resp;
303+
while(!_recv_shutdown && _stream->Read(&resp)) {
304+
auto& msg = resp.message();
305+
int mtype = MESSAGE_TYPE_INVALID;
306+
switch(msg.type()) {
307+
case ::thalhammer::http::WebSocketMessage::TEXT:
308+
mtype = MESSAGE_TYPE_TEXT; break;
309+
case ::thalhammer::http::WebSocketMessage::BINARY:
310+
mtype = MESSAGE_TYPE_BINARY; break;
311+
case ::thalhammer::http::WebSocketMessage::CLOSE:
312+
_server->close(_server);
313+
_recv_shutdown = true;
314+
break;
315+
default:
316+
break;
317+
}
318+
if(mtype != MESSAGE_TYPE_INVALID) {
319+
auto& content = msg.content();
320+
this->send(mtype, (const uint8_t*)content.data(), content.size());
321+
}
322+
}
323+
} catch(...) {
324+
ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "Exception in read thread");
325+
}
326+
});
327+
}
328+
329+
websocket_handler::~websocket_handler()
330+
{
331+
}
332+
333+
void websocket_handler::on_message(int type, const uint8_t* buffer, size_t buffer_size)
334+
{
335+
::thalhammer::http::HandleWebSocketRequest req;
336+
auto* msg = req.mutable_message();
337+
switch(type) {
338+
case MESSAGE_TYPE_TEXT:
339+
msg->set_type(::thalhammer::http::WebSocketMessage::TEXT); break;
340+
case MESSAGE_TYPE_BINARY:
341+
msg->set_type(::thalhammer::http::WebSocketMessage::BINARY); break;
342+
}
343+
msg->set_content((const char*)buffer, buffer_size);
344+
345+
if(!_stream->Write(req))
346+
{
347+
ap_log_error(APLOG_MARK, APLOG_ERR, 0, _server->request(_server)->server, "Failed to send websocket message to backend");
348+
}
349+
}
350+
351+
void websocket_handler::on_disconnect()
352+
{
353+
::thalhammer::http::HandleWebSocketRequest req;
354+
auto* msg = req.mutable_message();
355+
msg->set_type(::thalhammer::http::WebSocketMessage::CLOSE);
356+
msg->set_content("");
357+
_stream->WriteLast(req, ::grpc::WriteOptions());
358+
359+
_recv_shutdown = true;
360+
::grpc::Status s = _stream->Finish();
361+
if(!s.ok()) {
362+
ap_log_error(APLOG_MARK, APLOG_ERR, 0, _server->request(_server)->server, "Failed to execute rpc: %s", s.error_message().c_str());
363+
}
364+
if(_recv_thread.joinable())
365+
_recv_thread.join();
366+
}
367+

handler.h

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,59 @@
11
#pragma once
2+
#include "handler.grpc.pb.h"
23
extern "C" {
34
#include "httpd.h"
45
#include "http_log.h"
56
#include "http_protocol.h"
67
#include "apr_strings.h"
78
}
89
#include "config.h"
9-
extern int handle_request(request_rec* r, const grpcbackend_config_t* config);
10+
#include "websocket_plugin.h"
11+
#include <thread>
12+
#include <atomic>
13+
template<typename T>
14+
class pool_class {
15+
static apr_status_t cleanup(void* ptr) {
16+
if(ptr != nullptr) {
17+
T* instance = (T*)ptr;
18+
instance->~T();
19+
}
20+
return APR_SUCCESS;
21+
}
22+
apr_pool_t* _pool;
23+
public:
24+
template<typename... Args>
25+
static T* create(apr_pool_t* pool, Args&&... args) {
26+
auto* mem = apr_palloc(pool, sizeof(T));
27+
new(mem) T(std::forward<Args>(args)...);
28+
apr_pool_cleanup_register(pool, mem, cleanup, apr_pool_cleanup_null) ;
29+
return (T*)mem;
30+
}
31+
32+
virtual ~pool_class() {
33+
}
34+
};
35+
36+
class http_handler: public pool_class<http_handler> {
37+
request_rec* r;
38+
const grpcbackend_config_t* config;
39+
public:
40+
http_handler(request_rec* r);
41+
const grpcbackend_config_t* get_config() const { return config; }
42+
int handle_request();
43+
};
44+
45+
class websocket_handler: public pool_class<websocket_handler> {
46+
std::thread _recv_thread;
47+
std::atomic<bool> _recv_shutdown;
48+
const WebSocketServer* _server;
49+
std::unique_ptr<::thalhammer::http::Handler::Stub> _stub;
50+
::grpc::ClientContext _call_context;
51+
std::unique_ptr<::grpc::ClientReaderWriterInterface<::thalhammer::http::HandleWebSocketRequest, ::thalhammer::http::HandleWebSocketResponse>> _stream;
52+
protected:
53+
void send(int type, const uint8_t* buffer, size_t buffer_size);
54+
public:
55+
websocket_handler(const WebSocketServer* server);
56+
virtual ~websocket_handler();
57+
void on_message(int type, const uint8_t* buffer, size_t buffer_size);
58+
void on_disconnect();
59+
};

handler.proto

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,39 @@ message HandleResponse {
3939
Response response = 1;
4040
}
4141

42+
message WebSocketMessage {
43+
enum Type {
44+
TEXT = 0;
45+
BINARY = 1;
46+
CLOSE = 2;
47+
}
48+
Type type = 1;
49+
bytes content = 2;
50+
}
51+
52+
message WebSocketRequest {
53+
string method = 1;
54+
string resource = 2;
55+
string protocol = 3;
56+
repeated Header headers = 4;
57+
ClientInfo client = 5;
58+
}
59+
60+
message WebSocketResponse {
61+
repeated Header headers = 3;
62+
}
63+
64+
message HandleWebSocketRequest {
65+
WebSocketRequest request = 1;
66+
WebSocketMessage message = 2;
67+
}
68+
69+
message HandleWebSocketResponse {
70+
WebSocketResponse response = 1;
71+
WebSocketMessage message = 2;
72+
}
73+
4274
service Handler {
4375
rpc Handle(stream HandleRequest) returns(stream HandleResponse) {}
76+
rpc HandleWebSocket(stream HandleWebSocketRequest) returns(stream HandleWebSocketResponse) {}
4477
}

0 commit comments

Comments
 (0)