Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 318 lines (281 sloc) 11.074 kb
eeebb66 @jayridge initial import of pubsub
jayridge authored
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
ac22e1f @jehiah connection stats that include the size of outgoing buffers for connec…
jehiah authored
4 #include <time.h>
eae7c73 @jayridge initial import of sorted file db
jayridge authored
5 #include "queue.h"
6 #include "simplehttp.h"
ac22e1f @jehiah connection stats that include the size of outgoing buffers for connec…
jehiah authored
7 #include "http-internal.h"
eeebb66 @jayridge initial import of pubsub
jayridge authored
8
09802dd @nathanfolkman Adding support for multipart.
nathanfolkman authored
9 #define BOUNDARY "xXPubSubXx"
7cbcd85 @jehiah kick slow clients and write a message (after dropping their pending d…
jehiah authored
10 #define MAX_PENDING_DATA 1024*1024*50
9eca153 @nathanfolkman Added BUFSIZE.
nathanfolkman authored
11
6db1157 @jehiah pubsub server speaking websocket's
jehiah authored
12 int ps_debug = 0;
13
7cbcd85 @jehiah kick slow clients and write a message (after dropping their pending d…
jehiah authored
14 enum kick_client_enum {
15 CLIENT_OK = 0,
16 KICK_CLIENT = 1,
17 };
eeebb66 @jayridge initial import of pubsub
jayridge authored
18 typedef struct cli {
0e581ad @jayridge added multipart=0
jayridge authored
19 int multipart;
6db1157 @jehiah pubsub server speaking websocket's
jehiah authored
20 int websocket;
7cbcd85 @jehiah kick slow clients and write a message (after dropping their pending d…
jehiah authored
21 enum kick_client_enum kick_client;
ac22e1f @jehiah connection stats that include the size of outgoing buffers for connec…
jehiah authored
22 uint64_t connection_id;
23 time_t connect_time;
eeebb66 @jayridge initial import of pubsub
jayridge authored
24 struct evbuffer *buf;
25 struct evhttp_request *req;
26 TAILQ_ENTRY(cli) entries;
27 } cli;
28 TAILQ_HEAD(, cli) clients;
29
ac22e1f @jehiah connection stats that include the size of outgoing buffers for connec…
jehiah authored
30 uint64_t totalConns = 0;
31 uint64_t currentConns = 0;
1a34ab1 @jehiah updating pubsub reader code; moving client code to pubsubclient folder
jehiah authored
32 uint64_t kickedClients = 0;
ac22e1f @jehiah connection stats that include the size of outgoing buffers for connec…
jehiah authored
33 uint64_t msgRecv = 0;
34 uint64_t msgSent = 0;
1604d50 @nathanfolkman Added stats.
nathanfolkman authored
35
0e581ad @jayridge added multipart=0
jayridge authored
36
7cbcd85 @jehiah kick slow clients and write a message (after dropping their pending d…
jehiah authored
37
38 int
39 is_slow(struct cli *client) {
40 if (client->kick_client == KICK_CLIENT) { return 1; }
41 struct evhttp_connection *evcon;
42 unsigned long output_buffer_length;
43
44 evcon = (struct evhttp_connection *)client->req->evcon;
45 output_buffer_length = (unsigned long)EVBUFFER_LENGTH(evcon->output_buffer);
46 if (output_buffer_length > MAX_PENDING_DATA) {
1a34ab1 @jehiah updating pubsub reader code; moving client code to pubsubclient folder
jehiah authored
47 kickedClients+=1;
d5089c8 @jehiah %lu -> %llu formating
jehiah authored
48 fprintf(stdout, "%llu >> kicking client with %llu pending data\n", client->connection_id, output_buffer_length);
7cbcd85 @jehiah kick slow clients and write a message (after dropping their pending d…
jehiah authored
49 client->kick_client = KICK_CLIENT;
50 // clear the clients output buffer
51 evbuffer_drain(evcon->output_buffer, EVBUFFER_LENGTH(evcon->output_buffer));
d5089c8 @jehiah %lu -> %llu formating
jehiah authored
52 evbuffer_add_printf(evcon->output_buffer, "ERROR_TOO_SLOW. kicked for having %llu pending bytes\n", output_buffer_length);
7cbcd85 @jehiah kick slow clients and write a message (after dropping their pending d…
jehiah authored
53 return 1;
54 }
55 return 0;
56 }
57
58 int
59 can_kick(struct cli *client) {
60 if (client->kick_client == CLIENT_OK){return 0;}
61 // if the buffer length is back to zero, we can kick now
62 // our error notice has been pushed to the client
63 struct evhttp_connection *evcon;
64 evcon = (struct evhttp_connection *)client->req->evcon;
65 if (EVBUFFER_LENGTH(evcon->output_buffer) == 0){
66 return 1;
67 }
68 return 0;
69 }
70
0e581ad @jayridge added multipart=0
jayridge authored
71 void
72 argtoi(struct evkeyvalq *args, char *key, int *val, int def)
73 {
74 char *tmp;
75
76 *val = def;
77 tmp = (char *)evhttp_find_header(args, (const char *)key);
78 if (tmp) {
79 *val = atoi(tmp);
80 }
81 }
82
1604d50 @nathanfolkman Added stats.
nathanfolkman authored
83 void
1b59fb8 @nathanfolkman Added new /clients callback.
nathanfolkman authored
84 clients_cb(struct evhttp_request *req, struct evbuffer *evb, void *ctx)
85 {
86 struct cli *client;
ac22e1f @jehiah connection stats that include the size of outgoing buffers for connec…
jehiah authored
87 struct tm *time_struct;
88 char buf[248];
89 unsigned long output_buffer_length;
90 struct evhttp_connection *evcon;
1b59fb8 @nathanfolkman Added new /clients callback.
nathanfolkman authored
91
ac22e1f @jehiah connection stats that include the size of outgoing buffers for connec…
jehiah authored
92 if (TAILQ_EMPTY(&clients)) {
93 evbuffer_add_printf(evb, "no /sub connections\n");
94 }
1b59fb8 @nathanfolkman Added new /clients callback.
nathanfolkman authored
95 TAILQ_FOREACH(client, &clients, entries) {
ac22e1f @jehiah connection stats that include the size of outgoing buffers for connec…
jehiah authored
96 evcon = (struct evhttp_connection *)client->req->evcon;
97
98 time_struct = gmtime(&client->connect_time);
99 strftime(buf, 248, "%Y-%m-%d %H:%M:%S", time_struct);
100 output_buffer_length = (unsigned long)EVBUFFER_LENGTH(evcon->output_buffer);
d5089c8 @jehiah %lu -> %llu formating
jehiah authored
101 evbuffer_add_printf(evb, "%s:%d connected at %s. output buffer size:%llu state:%d\n",
ac22e1f @jehiah connection stats that include the size of outgoing buffers for connec…
jehiah authored
102 client->req->remote_host,
103 client->req->remote_port,
104 buf,
105 output_buffer_length,
106 (int)evcon->state);
1b59fb8 @nathanfolkman Added new /clients callback.
nathanfolkman authored
107 }
108
109 evhttp_send_reply(req, HTTP_OK, "OK", evb);
110 }
111
112 void
38c69fd @nathanfolkman Moved stats to HTTP response headers.
nathanfolkman authored
113 stats_cb(struct evhttp_request *req, struct evbuffer *evb, void *ctx)
1604d50 @nathanfolkman Added stats.
nathanfolkman authored
114 {
5b0770f @nathanfolkman Fixed args.
nathanfolkman authored
115 struct evkeyvalq args;
38c69fd @nathanfolkman Moved stats to HTTP response headers.
nathanfolkman authored
116 char buf[33];
076e8df @mreiferson updated pubsub with json stats, parse query args before accessing, ac…
mreiferson authored
117 const char *reset;
118 const char *format;
38c69fd @nathanfolkman Moved stats to HTTP response headers.
nathanfolkman authored
119
1a34ab1 @jehiah updating pubsub reader code; moving client code to pubsubclient folder
jehiah authored
120 sprintf(buf, "%llu", totalConns);
38c69fd @nathanfolkman Moved stats to HTTP response headers.
nathanfolkman authored
121 evhttp_add_header(req->output_headers, "X-PUBSUB-TOTAL-CONNECTIONS", buf);
1a34ab1 @jehiah updating pubsub reader code; moving client code to pubsubclient folder
jehiah authored
122 sprintf(buf, "%llu", currentConns);
8ee367b @nathanfolkman Fixed stat name.
nathanfolkman authored
123 evhttp_add_header(req->output_headers, "X-PUBSUB-ACTIVE-CONNECTIONS", buf);
1a34ab1 @jehiah updating pubsub reader code; moving client code to pubsubclient folder
jehiah authored
124 sprintf(buf, "%llu", msgRecv);
38c69fd @nathanfolkman Moved stats to HTTP response headers.
nathanfolkman authored
125 evhttp_add_header(req->output_headers, "X-PUBSUB-MESSAGES-RECEIVED", buf);
1a34ab1 @jehiah updating pubsub reader code; moving client code to pubsubclient folder
jehiah authored
126 sprintf(buf, "%llu", msgSent);
38c69fd @nathanfolkman Moved stats to HTTP response headers.
nathanfolkman authored
127 evhttp_add_header(req->output_headers, "X-PUBSUB-MESSAGES-SENT", buf);
1a34ab1 @jehiah updating pubsub reader code; moving client code to pubsubclient folder
jehiah authored
128 sprintf(buf, "%llu", kickedClients);
129 evhttp_add_header(req->output_headers, "X-PUBSUB-KICKED-CLIENTS", buf);
38c69fd @nathanfolkman Moved stats to HTTP response headers.
nathanfolkman authored
130
076e8df @mreiferson updated pubsub with json stats, parse query args before accessing, ac…
mreiferson authored
131 evhttp_parse_query(req->uri, &args);
132 format = (char *)evhttp_find_header(&args, "format");
133
134 if ((format != NULL) && (strcmp(format, "json") == 0)) {
135 evbuffer_add_printf(evb, "{");
136 evbuffer_add_printf(evb, "\"current_connections\": %llu,", currentConns);
137 evbuffer_add_printf(evb, "\"total_connections\": %llu,", totalConns);
138 evbuffer_add_printf(evb, "\"messages_received\": %llu,", msgRecv);
139 evbuffer_add_printf(evb, "\"messages_sent\": %llu,", msgSent);
140 evbuffer_add_printf(evb, "\"kicked_clients\": %llu,", kickedClients);
141 evbuffer_add_printf(evb, "}\n");
142 } else {
143 evbuffer_add_printf(evb, "Active connections: %llu\n", currentConns);
144 evbuffer_add_printf(evb, "Total connections: %llu\n", totalConns);
145 evbuffer_add_printf(evb, "Messages received: %llu\n", msgRecv);
146 evbuffer_add_printf(evb, "Messages sent: %llu\n", msgSent);
147 evbuffer_add_printf(evb, "Kicked clients: %llu\n", kickedClients);
148 }
149
38c69fd @nathanfolkman Moved stats to HTTP response headers.
nathanfolkman authored
150 reset = (char *)evhttp_find_header(&args, "reset");
b1d1329 @nathanfolkman Trying to fix new stats command.
nathanfolkman authored
151 if (reset) {
1604d50 @nathanfolkman Added stats.
nathanfolkman authored
152 msgRecv = 0;
153 msgSent = 0;
154 }
076e8df @mreiferson updated pubsub with json stats, parse query args before accessing, ac…
mreiferson authored
155
38c69fd @nathanfolkman Moved stats to HTTP response headers.
nathanfolkman authored
156 evhttp_send_reply(req, HTTP_OK, "OK", evb);
157 evhttp_clear_headers(&args);
1604d50 @nathanfolkman Added stats.
nathanfolkman authored
158 }
eeebb66 @jayridge initial import of pubsub
jayridge authored
159
160 void on_close(struct evhttp_connection *evcon, void *ctx)
161 {
162 struct cli *client = (struct cli *)ctx;
163
164 if (client) {
d5089c8 @jehiah %lu -> %llu formating
jehiah authored
165 fprintf(stdout, "%llu >> close from %s:%d\n", client->connection_id, evcon->address, evcon->port);
1604d50 @nathanfolkman Added stats.
nathanfolkman authored
166 currentConns--;
eeebb66 @jayridge initial import of pubsub
jayridge authored
167 TAILQ_REMOVE(&clients, client, entries);
168 evbuffer_free(client->buf);
169 free(client);
ac22e1f @jehiah connection stats that include the size of outgoing buffers for connec…
jehiah authored
170 } else {
171 fprintf(stdout, "[unknown] >> close from %s:%d\n", evcon->address, evcon->port);
eeebb66 @jayridge initial import of pubsub
jayridge authored
172 }
173 }
174
175 void pub_cb(struct evhttp_request *req, struct evbuffer *evb, void *ctx)
176 {
177 int i = 0;
178 struct cli *client;
1604d50 @nathanfolkman Added stats.
nathanfolkman authored
179
180 msgRecv++;
5b0770f @nathanfolkman Fixed args.
nathanfolkman authored
181 totalConns++;
6db1157 @jehiah pubsub server speaking websocket's
jehiah authored
182
eeebb66 @jayridge initial import of pubsub
jayridge authored
183 TAILQ_FOREACH(client, &clients, entries) {
1604d50 @nathanfolkman Added stats.
nathanfolkman authored
184 msgSent++;
eeebb66 @jayridge initial import of pubsub
jayridge authored
185 evbuffer_drain(client->buf, EVBUFFER_LENGTH(client->buf));
7cbcd85 @jehiah kick slow clients and write a message (after dropping their pending d…
jehiah authored
186 if (is_slow(client)) {
187 if (can_kick(client)) {
188 evhttp_connection_free(client->req->evcon);
189 continue;
190 }
191 continue;
192 }
6db1157 @jehiah pubsub server speaking websocket's
jehiah authored
193 if (client->websocket) {
194 // set to non-chunked so that send_reply_chunked doesn't add \r\n before/after this block
195 client->req->chunked = 0;
196 // write the frame. a websocket frame is \x00 + msg + \xFF
eae7c73 @jayridge initial import of sorted file db
jayridge authored
197 evbuffer_add(client->buf, "\0", 1);
198 evbuffer_add(client->buf, EVBUFFER_DATA(req->input_buffer), EVBUFFER_LENGTH(req->input_buffer));
199 evbuffer_add(client->buf, "\xFF", 1);
6db1157 @jehiah pubsub server speaking websocket's
jehiah authored
200 }
201 else if (client->multipart) {
202 /* chunked */
0e581ad @jayridge added multipart=0
jayridge authored
203 evbuffer_add_printf(client->buf,
204 "content-type: %s\r\ncontent-length: %d\r\n\r\n",
205 "*/*",
206 (int)EVBUFFER_LENGTH(req->input_buffer));
eae7c73 @jayridge initial import of sorted file db
jayridge authored
207 evbuffer_add(client->buf, EVBUFFER_DATA(req->input_buffer), EVBUFFER_LENGTH(req->input_buffer));
0e581ad @jayridge added multipart=0
jayridge authored
208 evbuffer_add_printf(client->buf, "\r\n--%s\r\n", BOUNDARY);
209 } else {
6db1157 @jehiah pubsub server speaking websocket's
jehiah authored
210 /* new line terminated */
eae7c73 @jayridge initial import of sorted file db
jayridge authored
211 evbuffer_add(client->buf, EVBUFFER_DATA(req->input_buffer), EVBUFFER_LENGTH(req->input_buffer));
0e581ad @jayridge added multipart=0
jayridge authored
212 evbuffer_add_printf(client->buf, "\n");
213 }
eeebb66 @jayridge initial import of pubsub
jayridge authored
214 evhttp_send_reply_chunk(client->req, client->buf);
215 i++;
216 }
5b0770f @nathanfolkman Fixed args.
nathanfolkman authored
217
eeebb66 @jayridge initial import of pubsub
jayridge authored
218 evbuffer_add_printf(evb, "Published to %d clients.\n", i);
219 evhttp_send_reply(req, HTTP_OK, "OK", evb);
220 }
221
222 void sub_cb(struct evhttp_request *req, struct evbuffer *evb, void *ctx)
223 {
224 struct cli *client;
0e581ad @jayridge added multipart=0
jayridge authored
225 struct evkeyvalq args;
226 char *uri;
6db1157 @jehiah pubsub server speaking websocket's
jehiah authored
227 char *ws_origin;
228 char *ws_upgrade;
229 char *host;
230 char buf[248];
ac22e1f @jehiah connection stats that include the size of outgoing buffers for connec…
jehiah authored
231 struct tm *time_struct;
eeebb66 @jayridge initial import of pubsub
jayridge authored
232
1604d50 @nathanfolkman Added stats.
nathanfolkman authored
233 currentConns++;
234 totalConns++;
0e581ad @jayridge added multipart=0
jayridge authored
235 uri = evhttp_decode_uri(req->uri);
236 evhttp_parse_query(uri, &args);
237 free(uri);
eeebb66 @jayridge initial import of pubsub
jayridge authored
238 client = calloc(1, sizeof(*client));
0e581ad @jayridge added multipart=0
jayridge authored
239 argtoi(&args, "multipart", &client->multipart, 1);
eeebb66 @jayridge initial import of pubsub
jayridge authored
240 client->req = req;
ac22e1f @jehiah connection stats that include the size of outgoing buffers for connec…
jehiah authored
241 client->connection_id = totalConns;
242 client->connect_time = time(NULL);
243 time_struct = gmtime(&client->connect_time);
eeebb66 @jayridge initial import of pubsub
jayridge authored
244 client->buf = evbuffer_new();
7cbcd85 @jehiah kick slow clients and write a message (after dropping their pending d…
jehiah authored
245 client->kick_client = CLIENT_OK;
6db1157 @jehiah pubsub server speaking websocket's
jehiah authored
246
ac22e1f @jehiah connection stats that include the size of outgoing buffers for connec…
jehiah authored
247 strftime(buf, 248, "%Y-%m-%d %H:%M:%S", time_struct);
248
249 // print out info about this connection
d5089c8 @jehiah %lu -> %llu formating
jehiah authored
250 fprintf(stdout, "%llu >> /sub connection from %s:%d %s\n", client->connection_id, req->remote_host, req->remote_port, buf);
ac22e1f @jehiah connection stats that include the size of outgoing buffers for connec…
jehiah authored
251
6db1157 @jehiah pubsub server speaking websocket's
jehiah authored
252 // Connection: Upgrade
253 // Upgrade: WebSocket
254 ws_upgrade = (char *) evhttp_find_header(req->input_headers, "Upgrade");
255 ws_origin = (char *) evhttp_find_header(req->input_headers, "Origin");
256 host = (char *) evhttp_find_header(req->input_headers, "Host");
257
258 if (ps_debug && ws_upgrade) {
d5089c8 @jehiah %lu -> %llu formating
jehiah authored
259 fprintf(stderr, "%llu >> upgrade header is %s\n", client->connection_id, ws_upgrade);
260 fprintf(stderr, "%llu >> multipart is %d\n", client->connection_id, client->multipart);
6db1157 @jehiah pubsub server speaking websocket's
jehiah authored
261 }
262
263 if (ws_upgrade && strstr(ws_upgrade, "WebSocket") != NULL) {
264 if (ps_debug) {
d5089c8 @jehiah %lu -> %llu formating
jehiah authored
265 fprintf(stderr, "%llu >> upgrading connection to a websocket\n", client->connection_id);
6db1157 @jehiah pubsub server speaking websocket's
jehiah authored
266 }
267 client->websocket = 1;
268 client->req->major = 1;
269 client->req->minor = 1;
270 evhttp_add_header(client->req->output_headers, "Upgrade", "WebSocket");
271 evhttp_add_header(client->req->output_headers, "Connection", "Upgrade");
272 evhttp_add_header(client->req->output_headers, "Server", "simplehttp/pubsub");
273 if (ws_origin) {
274 evhttp_add_header(client->req->output_headers, "WebSocket-Origin", ws_origin);
275 }
276 if (host) {
277 sprintf(buf, "ws://%s%s", host, req->uri);
278 if (ps_debug) {
d5089c8 @jehiah %lu -> %llu formating
jehiah authored
279 fprintf(stderr, "%llu >> setting WebSocket-Location to %s\n", client->connection_id, buf);
6db1157 @jehiah pubsub server speaking websocket's
jehiah authored
280 }
281 evhttp_add_header(client->req->output_headers, "WebSocket-Location", buf);
282 }
283 // evbuffer_add_printf(client->buf, "\r\n");
284 }
285 else if (client->multipart) {
0e581ad @jayridge added multipart=0
jayridge authored
286 evhttp_add_header(client->req->output_headers, "content-type",
287 "multipart/x-mixed-replace; boundary=" BOUNDARY);
288 evbuffer_add_printf(client->buf, "--%s\r\n", BOUNDARY);
289 } else {
290 evhttp_add_header(client->req->output_headers, "content-type",
291 "application/json");
292 evbuffer_add_printf(client->buf, "\r\n");
293 }
6db1157 @jehiah pubsub server speaking websocket's
jehiah authored
294 if (client->websocket) {
295 evhttp_send_reply_start(client->req, 101, "Web Socket Protocol Handshake");
296 } else {
297 evhttp_send_reply_start(client->req, HTTP_OK, "OK");
298 }
09802dd @nathanfolkman Adding support for multipart.
nathanfolkman authored
299 evhttp_send_reply_chunk(client->req, client->buf);
eeebb66 @jayridge initial import of pubsub
jayridge authored
300 TAILQ_INSERT_TAIL(&clients, client, entries);
301 evhttp_connection_set_closecb(req->evcon, on_close, (void *)client);
0e581ad @jayridge added multipart=0
jayridge authored
302 evhttp_clear_headers(&args);
eeebb66 @jayridge initial import of pubsub
jayridge authored
303 }
304
305 int
306 main(int argc, char **argv)
307 {
308 TAILQ_INIT(&clients);
309 simplehttp_init();
310 simplehttp_set_cb("/pub*", pub_cb, NULL);
311 simplehttp_set_cb("/sub*", sub_cb, NULL);
076e8df @mreiferson updated pubsub with json stats, parse query args before accessing, ac…
mreiferson authored
312 simplehttp_set_cb("/stats*", stats_cb, NULL);
ac22e1f @jehiah connection stats that include the size of outgoing buffers for connec…
jehiah authored
313 simplehttp_set_cb("/clients", clients_cb, NULL);
eeebb66 @jayridge initial import of pubsub
jayridge authored
314 simplehttp_main(argc, argv);
315
316 return 0;
317 }
Something went wrong with that request. Please try again.