1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
// Copyright (C) 2021 Russell King.
// Licensed under GPL version 2. See COPYING.
#include <gio/gio.h>
#include "event-httpd.h"
#include "resource.h"
struct resource_object {
GList *client_list;
GString *string;
};
static struct resource_object position_object;
static struct resource_object signal_object;
static void resource_obj_send(struct client *c, struct resource_object *obj)
{
GString *string = obj->string;
if (!string)
return;
respond_chunk(c, string);
}
// Update this client with the new resource object data
static void resource_obj_update(gpointer data, gpointer user_data)
{
struct resource_object *obj = user_data;
struct client *c = data;
resource_obj_send(c, obj);
}
static void resource_obj_set_string(struct resource_object *obj, GString *str)
{
GString *old;
old = obj->string;
obj->string = str;
g_string_free(old, TRUE);
g_list_foreach(obj->client_list, resource_obj_update, obj);
}
static void resource_obj_add_client(struct resource_object *obj,
struct client *c)
{
obj->client_list = g_list_append(obj->client_list, c);
}
static void resource_obj_del_client(struct resource_object *obj,
struct client *c)
{
obj->client_list = g_list_remove(obj->client_list, c);
}
static int object_v1_get(struct client *c, struct resource *r)
{
struct resource_object *obj = r->data;
respond_header(c, 200, "OK",
"Cache-Control: no-cache\r\n"
"Connection: keep-alive\r\n"
"Content-Type: text/event-stream; charset=UTF-8\r\n"
"Transfer-Encoding: chunked\r\n");
resource_obj_send(c, obj);
// Add this client to the object list so it receives updates
resource_obj_add_client(obj, c);
return 1;
}
static void object_v1_close(struct client *c, struct resource *r)
{
struct resource_object *obj = r->data;
// Remove this client from the object list
resource_obj_del_client(obj, c);
}
static void object_v1_update_open(struct client *c, struct resource *r)
{
// Keep track of the latest updater
r->updater = c;
}
// Update all attached clients with the new resource object data
static int object_v1_update(struct client *c, struct resource *r, const char *m)
{
struct resource_object *obj = r->data;
GString *string;
char *n;
// Only allow the latest updater to provide updates, in case we
// end up with multiple connections. This also allows us to quickly
// release the resources of a stale connection.
if (r->updater != c)
return -1;
// Remove any trailing whitespace.
n = g_strchomp(g_strdup(m));
// Format the text/event-stream response
// We prefix the string with the "data:" tag in preparation for
// sending to via the text/event-stream connection.
// The double newline terminates this event
// See https://www.html5rocks.com/en/tutorials/eventsource/basics/
string = g_string_new(NULL);
g_string_printf(string, "data:%s\n\n", n);
g_free(n);
resource_obj_set_string(obj, string);
return 0;
}
static void object_v1_update_close(struct client *c, struct resource *r)
{
// If the updater goes away, clear the data so we don't serve
// stale data.
if (r->updater == c)
r->updater = NULL;
}
static const struct resource_ops resource_v1_ops = {
.get = object_v1_get,
.close = object_v1_close,
.update_open = object_v1_update_open,
.update = object_v1_update,
.update_close = object_v1_update_close,
};
static struct resource resource_position1 = {
.ops = &resource_v1_ops,
.data = &position_object,
};
static struct resource resource_signal1 = {
.ops = &resource_v1_ops,
.data = &signal_object,
};
void resource_init(GHashTable *hash)
{
g_hash_table_insert(hash, "/api/1/position", &resource_position1);
g_hash_table_insert(hash, "/api/1/signal", &resource_signal1);
}
|