NetBurner 3.5.8
PDF Version
json_writer.h
1/*NB_REVISION*/
2
3/*NB_COPYRIGHT*/
4
5// json_writer.h — accumulate a JSON (or any text) response and flush it to a socket in ONE
6// write, so it leaves as full-size TCP segments instead of many tiny per-field/per-byte ones.
7//
8// Why: handlers that build JSON with repeated fdprintf(sock, ...) produce hundreds/thousands of
9// 1-2 byte TCP segments. Under concurrent NTP load a single lost tiny segment triggers a ~1 s TCP
10// retransmit timeout that freezes the shared ethernet TX path (NTP included). Fewer, larger
11// segments make that far less likely. (The pjs-based handlers use SendJsonBody() in
12// webfunctions.cpp for the same reason; this is the equivalent for fdprintf-style handlers.)
13//
14// Usage:
15// JsonWriter jw(sock); // grabs a heap buffer (grows as needed)
16// jw.Printf("{\"a\":%d,", x);
17// ...
18// jw.Printf("}");
19// // flushed automatically when jw goes out of scope (or call jw.Flush()).
20//
21// On allocation failure it degrades gracefully to per-call socket writes (never drops output).
22
23#ifndef NTP1061_JSON_WRITER_H
24#define NTP1061_JSON_WRITER_H
25
26#include <stdarg.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <iosys.h> // writeall
30
31class JsonWriter
32{
33 int m_sock;
34 char *m_buf;
35 int m_cap;
36 int m_len;
37
38public:
39 explicit JsonWriter(int sock, int initCap = 2048)
40 : m_sock(sock), m_buf((char *)malloc(initCap)), m_cap(m_buf ? initCap : 0), m_len(0) {}
41
42 ~JsonWriter() { Flush(); if (m_buf) free(m_buf); }
43
44 void Printf(const char *fmt, ...)
45 {
46 va_list ap;
47
48 // No buffer (allocation failed): write this piece straight to the socket.
49 if (!m_buf)
50 {
51 char tmp[256];
52 va_start(ap, fmt);
53 int n = vsnprintf(tmp, sizeof(tmp), fmt, ap);
54 va_end(ap);
55 if (n > 0) writeall(m_sock, tmp, (n < (int)sizeof(tmp)) ? n : (int)sizeof(tmp) - 1);
56 return;
57 }
58
59 for (;;)
60 {
61 va_start(ap, fmt);
62 int need = vsnprintf(m_buf + m_len, m_cap - m_len, fmt, ap);
63 va_end(ap);
64
65 if (need < 0) return; // encoding error
66 if (m_len + need < m_cap) { m_len += need; return; } // fit
67
68 // Did not fit: grow and retry.
69 int nc = m_cap * 2;
70 while (m_len + need + 1 > nc) nc *= 2;
71 char *nb = (char *)realloc(m_buf, nc);
72 if (!nb)
73 {
74 // Out of memory: flush what we have, then write this piece directly.
75 Flush();
76 char tmp[256];
77 va_start(ap, fmt);
78 int n = vsnprintf(tmp, sizeof(tmp), fmt, ap);
79 va_end(ap);
80 if (n > 0) writeall(m_sock, tmp, (n < (int)sizeof(tmp)) ? n : (int)sizeof(tmp) - 1);
81 return;
82 }
83 m_buf = nb;
84 m_cap = nc;
85 }
86 }
87
88 void Flush() { if (m_buf && m_len > 0) { writeall(m_sock, m_buf, m_len); m_len = 0; } }
89};
90
91#endif // NTP1061_JSON_WRITER_H
int writeall(int fd, const char *buf, int nbytes=0)
Write the specified number of bytes to a file descriptor. Will block until all bytes are sent,...