cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
sse.c
Go to the documentation of this file.
1#define _POSIX_C_SOURCE 200809L
5#include <errno.h>
6#include <pthread.h>
7#include <stdio.h>
8#include <string.h>
9#include <sys/socket.h>
10
11struct cwist_sse_stream {
12 int fd;
13 int closed;
14 pthread_mutex_t mutex;
15};
16
21static cwist_error_t sse_error(int value) {
22 return (cwist_error_t){.errtype = CWIST_ERR_INT16, .error.err_i16 = value};
23}
24
34static bool sse_ok(cwist_error_t err) {
35 bool ok = cwist_error_is_ok(&err);
37 return ok;
38}
39
46static bool sse_append(cwist_sstring *out, const char *data, size_t len) {
47 return sse_ok(cwist_sstring_append_len(out, data, len));
48}
49
61static int append_field(cwist_sstring *out, const char *name, const char *value) {
62 const char *line = value ? value : "";
63 do {
64 const char *end = strchr(line, '\n');
65 size_t len = end ? (size_t)(end - line) : strlen(line);
66 if (len && line[len - 1] == '\r') len--;
67 if (!sse_append(out, name, strlen(name)) || !sse_append(out, ":", 1) ||
68 (len && !sse_append(out, line, len)) || !sse_append(out, "\n", 1))
69 return -1;
70 line = end ? end + 1 : NULL;
71 } while (line);
72 return 0;
73}
74
89static cwist_sstring *format_event(const char *event, const char *id, int retry_ms,
90 const char *data, int is_comment) {
92 if (!frame) return NULL;
93 int failed = is_comment ? append_field(frame, "", data) : 0;
94 if (!is_comment && id) failed |= append_field(frame, "id", id);
95 if (!is_comment && event) failed |= append_field(frame, "event", event);
96 if (!is_comment && retry_ms >= 0) {
97 char retry[32];
98 snprintf(retry, sizeof(retry), "%d", retry_ms);
99 failed |= append_field(frame, "retry", retry);
100 }
101 if (!is_comment) failed |= append_field(frame, "data", data);
102 if (failed || !sse_append(frame, "\n", 1)) {
104 return NULL;
105 }
106 return frame;
107}
108
118 if (!res ||
119 !sse_ok(cwist_http_header_add(&res->headers, "Content-Type",
120 "text/event-stream; charset=utf-8")) ||
121 !sse_ok(cwist_http_header_add(&res->headers, "Cache-Control", "no-cache")) ||
122 !sse_ok(cwist_http_header_add(&res->headers, "X-Accel-Buffering", "no")))
123 return sse_error(-1);
124 res->keep_alive = true;
125 return sse_error(0);
126}
127
138cwist_error_t cwist_sse_response_event(cwist_http_response *res, const char *event, const char *id,
139 int retry_ms, const char *data) {
140 if (!res || retry_ms < -1) return sse_error(-1);
141 cwist_sstring *frame = format_event(event, id, retry_ms, data, 0);
142 if (!frame) return sse_error(-1);
145 return result;
146}
147
155 if (!res) return sse_error(-1);
156 cwist_sstring *frame = format_event(NULL, NULL, -1, comment, 1);
157 if (!frame) return sse_error(-1);
160 return result;
161}
162
169 if (!event) return sse_error(-1);
170 return cwist_sse_response_event(res, event->event, event->id, event->retry_ms, event->data);
171}
172
183static int sse_send_all(int fd, const char *data, size_t len) {
184 while (len) {
185 ssize_t n = send(fd, data, len, MSG_NOSIGNAL);
186 if (n < 0 && errno == EINTR) continue;
187 if (n <= 0) return -1;
188 data += n;
189 len -= (size_t)n;
190 }
191 return 0;
192}
193
206 static const char headers[] =
207 "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream; charset=utf-8\r\nCache-Control: no-cache\r\nX-Accel-Buffering: no\r\nConnection: keep-alive\r\n\r\n";
208 if (!req || req->client_fd < 0 || req->upgraded ||
209 sse_send_all(req->client_fd, headers, sizeof(headers) - 1))
210 return NULL;
211 cwist_sse_stream_t *stream = cwist_alloc(sizeof(*stream));
212 if (!stream) return NULL;
213 stream->fd = req->client_fd;
214 stream->closed = 0;
215 pthread_mutex_init(&stream->mutex, NULL);
216 req->upgraded = true;
217 return stream;
218}
219
233cwist_error_t cwist_sse_stream_send(cwist_sse_stream_t *stream, const char *event, const char *id,
234 int retry_ms, const char *data) {
235 if (!stream || retry_ms < -1) return sse_error(-1);
236 cwist_sstring *frame = format_event(event, id, retry_ms, data, 0);
237 if (!frame) return sse_error(-1);
238 pthread_mutex_lock(&stream->mutex);
239 int rc = stream->closed ? -1 : sse_send_all(stream->fd, frame->data, frame->size);
240 if (rc) stream->closed = 1;
241 pthread_mutex_unlock(&stream->mutex);
243 return sse_error(rc);
244}
245
257 if (!stream) return sse_error(-1);
258 cwist_sstring *frame = format_event(NULL, NULL, -1, comment, 1);
259 if (!frame) return sse_error(-1);
260 pthread_mutex_lock(&stream->mutex);
261 int rc = stream->closed ? -1 : sse_send_all(stream->fd, frame->data, frame->size);
262 if (rc) stream->closed = 1;
263 pthread_mutex_unlock(&stream->mutex);
265 return sse_error(rc);
266}
267
274 if (!event) return sse_error(-1);
275 return cwist_sse_stream_send(stream, event->event, event->id, event->retry_ms, event->data);
276}
277
286 if (!stream) return;
287 pthread_mutex_destroy(&stream->mutex);
288 cwist_free(stream);
289}
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
Safe string type with helper methods.
cwist_error_t cwist_sstring_append_len(cwist_sstring *str, const char *data, size_t len)
Append raw bytes with length.
Definition sstring.c:212
cwist_sstring * cwist_sstring_create(void)
Create a new sstring instance.
Definition sstring.c:802
void cwist_sstring_destroy(cwist_sstring *str)
Destroy an sstring instance.
Definition sstring.c:823
@ CWIST_ERR_INT16
used when checking common errcodes in Unix/Linux
Definition cwist_err.h:16
cwist_error_t cwist_http_header_add(cwist_http_header_node **head, const char *key, const char *value)
Add a header to the list.
Definition http.c:1861
Server-Sent Events stream API.
struct cwist_sse_stream cwist_sse_stream_t
Definition sse.h:11
size_t cap
Definition otel.c:56
cwist_error_t cwist_sse_response_init(cwist_http_response *res)
Prepare a response buffer for a Server-Sent Events reply.
Definition sse.c:117
cwist_error_t cwist_sse_stream_comment(cwist_sse_stream_t *stream, const char *comment)
Send an SSE comment line on a live stream.
Definition sse.c:256
cwist_error_t cwist_sse_response_comment(cwist_http_response *res, const char *comment)
Append an SSE comment line to a response buffer.
Definition sse.c:154
cwist_sse_stream_t * cwist_sse_stream_open(cwist_http_request *req)
Upgrade a request connection to a live SSE stream.
Definition sse.c:205
cwist_error_t cwist_sse_response_event(cwist_http_response *res, const char *event, const char *id, int retry_ms, const char *data)
Append a formatted SSE event to a response buffer.
Definition sse.c:138
cwist_error_t cwist_sse_stream_send(cwist_sse_stream_t *stream, const char *event, const char *id, int retry_ms, const char *data)
Send a formatted SSE event on a live stream.
Definition sse.c:233
void cwist_sse_stream_close(cwist_sse_stream_t *stream)
Close a live SSE stream and release its resources.
Definition sse.c:285
cwist_error_t cwist_sse_response_write(cwist_http_response *res, const cwist_sse_event_t *event)
Append an SSE event described by a cwist_sse_event_t to a response.
Definition sse.c:168
cwist_error_t cwist_sse_stream_write(cwist_sse_stream_t *stream, const cwist_sse_event_t *event)
Send an SSE event described by a cwist_sse_event_t on a stream.
Definition sse.c:273
Integrated error structure used throughout the system.
Definition cwist_err.h:72
cwist_errtype_t errtype
Definition cwist_err.h:73
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
Definition http.h:82
cwist_sstring * body
Definition http.h:87
cwist_http_header_node * headers
Definition http.h:86
const char * id
Definition sse.h:16
int retry_ms
Definition sse.h:17
const char * data
Definition sse.h:18
const char * event
Definition sse.h:15
char * data
Access directly only when raw handling is necessary.
Definition sstring.h:15
void cwist_error_dispose(cwist_error_t *err)
Release resources owned by an error value. Currently only CWIST_ERR_JSON owns memory (the cJSON paylo...
Definition error.c:43