Skip to content

Commit 175695d

Browse files
Make curl transport optional and bound responses
1 parent 50af4f4 commit 175695d

9 files changed

Lines changed: 478 additions & 18 deletions

File tree

README.md

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ This generator, from a webrpc schema/design file, will code-generate:
1111

1212
2. Implementation output
1313
C JSON encode/decode helpers, generated method request / response handling,
14-
and a self-contained `libcurl`-based HTTP transport/runtime.
14+
and an optional self-contained `libcurl`-based HTTP transport/runtime.
1515

1616
The generated client is intended to speak to any webrpc server language
1717
(Go, nodejs, etc.) as long as the schema features used are supported by this target.
@@ -21,10 +21,11 @@ The generated client is intended to speak to any webrpc server language
2121
Generated `header` output only depends on the C standard library headers included by
2222
the generated file.
2323

24-
Generated `impl` output currently depends on:
24+
Generated `impl` output depends on:
2525

2626
- `cJSON`
27-
- `libcurl`
27+
- `libcurl`, unless the generated implementation is compiled with the
28+
prefix-based no-curl guard
2829

2930
The generated code targets C99.
3031

@@ -53,6 +54,33 @@ Dependency names can vary slightly by platform or package manager. The important
5354
is that the generated implementation can include `<cjson/cJSON.h>` and link against
5455
`libcurl` and `cJSON`.
5556

57+
To build generated implementation output without the built-in libcurl transport, define
58+
`<PREFIX>_NO_CURL_TRANSPORT`, where `<PREFIX>` is the generated prefix uppercased. For
59+
example, code generated with `-prefix=example` can be compiled with:
60+
61+
```bash
62+
cc -std=c99 \
63+
-DEXAMPLE_NO_CURL_TRANSPORT \
64+
$(pkg-config --cflags libcjson) \
65+
-c example.gen.c
66+
67+
cc -std=c99 \
68+
-DEXAMPLE_NO_CURL_TRANSPORT \
69+
app.c example.gen.c \
70+
$(pkg-config --cflags --libs libcjson) \
71+
-o app
72+
```
73+
74+
No-curl mode removes the generated implementation's libcurl include, types, and link
75+
dependency. It does not remove the `cJSON` dependency because generated JSON
76+
encode/decode and response parsing still use `cJSON`.
77+
78+
The lower-level request/response helpers remain available in no-curl mode:
79+
`example_<service>_<method>_prepare_request(...)` builds a prepared request and
80+
`example_<service>_<method>_parse_response(...)` parses an HTTP response supplied by
81+
your own transport. Runtime and client functions still link; send attempts fail with a
82+
`TransportError` indicating that the built-in curl transport is disabled.
83+
5684
Because the generated implementation uses `cJSON`, exact large 64-bit integer handling
5785
follows `cJSON`'s numeric behavior. If your API needs exact integer round-tripping beyond
5886
normal JSON number precision expectations, prefer `bigint` in the schema instead of
@@ -75,7 +103,7 @@ The current generator supports:
75103
- prepare request bytes without sending them
76104
- send a prepared request with the generated transport
77105
- parse a raw HTTP response into generated response types
78-
- generated `libcurl` client configuration for bearer auth, custom headers, and timeouts
106+
- generated client configuration for bearer auth, custom headers, timeouts, and bounded response buffering
79107

80108
## Limitations
81109

@@ -87,6 +115,11 @@ The current generator does not support:
87115
- a shared external transport abstraction; the generated runtime is currently self-contained
88116
- automatic redirect following
89117

118+
Generated client options include `max_response_bytes`, which bounds the response body
119+
buffer used by the built-in curl transport. `*_client_options_init(...)` defaults this
120+
to `1024 * 1024` bytes. Leaving it zero is treated the same as the default, not as an
121+
unlimited response size.
122+
90123
Implementation generation also assumes a companion generated header include via
91124
`-header=<file>`.
92125

_examples/smoke/example.gen.c

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,22 @@
66

77
#include "example.gen.h"
88

9+
#ifndef SMOKE_NO_CURL_TRANSPORT
910
#include <curl/curl.h>
11+
#endif
1012
#include <cjson/cJSON.h>
1113
#include <limits.h>
1214
#include <math.h>
1315
#include <stdio.h>
1416

17+
#ifndef SMOKE_NO_CURL_TRANSPORT
1518
typedef struct {
1619
char *data;
1720
size_t len;
1821
size_t cap;
22+
size_t max_response_bytes;
1923
} smoke_buffer;
24+
#endif
2025

2126
#if defined(__GNUC__) || defined(__clang__)
2227
#define SMOKE_JSON_UNUSED __attribute__((unused))
@@ -133,8 +138,16 @@ static void smoke_set_error(
133138
error->cause = cause ? smoke_strdup(cause) : NULL;
134139
}
135140

136-
static int smoke_buffer_grow(smoke_buffer *buf, size_t need) {
141+
#ifndef SMOKE_NO_CURL_TRANSPORT
142+
static size_t smoke_default_max_response_bytes(void) {
143+
return (size_t)1024 * 1024;
144+
}
145+
146+
static int smoke_buffer_grow(smoke_buffer *buf, size_t need, size_t max_response_bytes) {
147+
size_t max_cap;
137148
if (!buf) return 0;
149+
max_cap = max_response_bytes == SIZE_MAX ? SIZE_MAX : max_response_bytes + 1;
150+
if (need > max_cap) return 0;
138151
if (buf->cap >= need) return 1;
139152
size_t new_cap = buf->cap ? buf->cap : 1024;
140153
while (new_cap < need) {
@@ -144,6 +157,9 @@ static int smoke_buffer_grow(smoke_buffer *buf, size_t need) {
144157
}
145158
new_cap *= 2;
146159
}
160+
if (new_cap > max_cap) {
161+
new_cap = max_cap;
162+
}
147163
char *next = (char *)realloc(buf->data, new_cap);
148164
if (!next) return 0;
149165
buf->data = next;
@@ -154,12 +170,18 @@ static int smoke_buffer_grow(smoke_buffer *buf, size_t need) {
154170
static size_t smoke_write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
155171
smoke_buffer *buf = (smoke_buffer *)userdata;
156172
size_t n;
173+
size_t need;
174+
size_t max_response_bytes;
157175

158176
if (!buf) return 0;
159177
if (size != 0 && nmemb > SIZE_MAX / size) return 0;
160178
n = size * nmemb;
179+
if (n > SIZE_MAX - 1) return 0;
161180
if (buf->len > SIZE_MAX - n - 1) return 0;
162-
if (!smoke_buffer_grow(buf, buf->len + n + 1)) return 0;
181+
need = buf->len + n + 1;
182+
max_response_bytes = buf->max_response_bytes > 0 ? buf->max_response_bytes : smoke_default_max_response_bytes();
183+
if (need - 1 > max_response_bytes) return 0;
184+
if (!smoke_buffer_grow(buf, need, max_response_bytes)) return 0;
163185
memcpy(buf->data + buf->len, ptr, n);
164186
buf->len += n;
165187
buf->data[buf->len] = '\0';
@@ -378,6 +400,7 @@ static int smoke_http_send_request(
378400
const char *bearer_token,
379401
const struct curl_slist *default_headers,
380402
long timeout_ms,
403+
size_t max_response_bytes,
381404
smoke_http_response *response,
382405
smoke_error *error
383406
) {
@@ -396,6 +419,7 @@ static int smoke_http_send_request(
396419
}
397420
smoke_http_response_init(&result);
398421
memset(&buf, 0, sizeof(buf));
422+
buf.max_response_bytes = max_response_bytes > 0 ? max_response_bytes : smoke_default_max_response_bytes();
399423

400424
curl = curl_easy_init();
401425
if (!curl) {
@@ -458,6 +482,15 @@ static int smoke_http_send_request(
458482
}
459483
return rc;
460484
}
485+
#else
486+
int smoke_runtime_init(smoke_error *error) {
487+
(void)error;
488+
return 0;
489+
}
490+
491+
void smoke_runtime_cleanup(void) {
492+
}
493+
#endif
461494

462495
static void smoke_parse_rpc_error(const char *body, long http_status, smoke_error *error) {
463496
cJSON *error_name = NULL;
@@ -931,11 +964,13 @@ int smoke_smoke_echo_parse_response(
931964
return rc;
932965
}
933966

967+
#ifndef SMOKE_NO_CURL_TRANSPORT
934968
struct smoke_smoke_client {
935969
char *base_url;
936970
char *bearer_token;
937971
struct curl_slist *default_headers;
938972
long timeout_ms;
973+
size_t max_response_bytes;
939974
};
940975

941976
static void smoke_smoke_client_free_config_parts(char **bearer_token, struct curl_slist **default_headers) {
@@ -953,23 +988,29 @@ static void smoke_smoke_client_reset_config(smoke_smoke_client *client) {
953988
if (!client) return;
954989
smoke_smoke_client_free_config_parts(&client->bearer_token, &client->default_headers);
955990
client->timeout_ms = 10000L;
991+
client->max_response_bytes = (size_t)1024 * 1024;
956992
}
957993

958994
static int smoke_smoke_client_build_config(
959995
const smoke_client_options *options,
960996
char **bearer_token,
961997
struct curl_slist **default_headers,
962-
long *timeout_ms
998+
long *timeout_ms,
999+
size_t *max_response_bytes
9631000
) {
964-
if (!bearer_token || !default_headers || !timeout_ms) return 0;
1001+
if (!bearer_token || !default_headers || !timeout_ms || !max_response_bytes) return 0;
9651002
*bearer_token = NULL;
9661003
*default_headers = NULL;
9671004
*timeout_ms = 10000L;
1005+
*max_response_bytes = (size_t)1024 * 1024;
9681006
if (!options) return 1;
9691007

9701008
if (options->timeout_ms > 0) {
9711009
*timeout_ms = options->timeout_ms;
9721010
}
1011+
if (options->max_response_bytes > 0) {
1012+
*max_response_bytes = options->max_response_bytes;
1013+
}
9731014

9741015
if (options->bearer_token) {
9751016
*bearer_token = smoke_strdup(options->bearer_token);
@@ -991,23 +1032,26 @@ static int smoke_smoke_client_build_config(
9911032
fail:
9921033
smoke_smoke_client_free_config_parts(bearer_token, default_headers);
9931034
*timeout_ms = 10000L;
1035+
*max_response_bytes = (size_t)1024 * 1024;
9941036
return 0;
9951037
}
9961038

9971039
int smoke_smoke_client_configure(smoke_smoke_client *client, const smoke_client_options *options) {
9981040
char *next_bearer_token = NULL;
9991041
struct curl_slist *next_default_headers = NULL;
10001042
long next_timeout_ms = 10000L;
1043+
size_t next_max_response_bytes = (size_t)1024 * 1024;
10011044

10021045
if (!client) return 0;
1003-
if (!smoke_smoke_client_build_config(options, &next_bearer_token, &next_default_headers, &next_timeout_ms)) {
1046+
if (!smoke_smoke_client_build_config(options, &next_bearer_token, &next_default_headers, &next_timeout_ms, &next_max_response_bytes)) {
10041047
return 0;
10051048
}
10061049

10071050
smoke_smoke_client_reset_config(client);
10081051
client->bearer_token = next_bearer_token;
10091052
client->default_headers = next_default_headers;
10101053
client->timeout_ms = next_timeout_ms;
1054+
client->max_response_bytes = next_max_response_bytes;
10111055
return 1;
10121056
}
10131057

@@ -1056,10 +1100,47 @@ int smoke_smoke_client_send_prepared_request(
10561100
client->bearer_token,
10571101
client->default_headers,
10581102
client->timeout_ms,
1103+
client->max_response_bytes,
10591104
response,
10601105
error
10611106
);
10621107
}
1108+
#else
1109+
struct smoke_smoke_client {
1110+
int placeholder;
1111+
};
1112+
1113+
smoke_smoke_client *smoke_smoke_client_create(const char *base_url, const smoke_client_options *options) {
1114+
smoke_smoke_client *client;
1115+
(void)base_url;
1116+
(void)options;
1117+
client = (smoke_smoke_client *)calloc(1, sizeof(*client));
1118+
return client;
1119+
}
1120+
1121+
void smoke_smoke_client_destroy(smoke_smoke_client *client) {
1122+
free(client);
1123+
}
1124+
1125+
int smoke_smoke_client_configure(smoke_smoke_client *client, const smoke_client_options *options) {
1126+
(void)options;
1127+
return client ? 1 : 0;
1128+
}
1129+
1130+
int smoke_smoke_client_send_prepared_request(
1131+
smoke_smoke_client *client,
1132+
const smoke_prepared_request *request,
1133+
smoke_http_response *response,
1134+
smoke_error *error
1135+
) {
1136+
if (!client || !request || !response) {
1137+
smoke_set_error(error, 0, 0, "ClientError", "client, request, and response must be non-NULL", NULL);
1138+
return -1;
1139+
}
1140+
smoke_set_error(error, 0, 0, "TransportError", "built-in curl transport is disabled", NULL);
1141+
return -1;
1142+
}
1143+
#endif
10631144
int smoke_smoke_echo(
10641145
smoke_smoke_client *client,
10651146
const smoke_smoke_echo_request *request,

_examples/smoke/example.gen.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ typedef struct {
5454
const char * const *headers;
5555
size_t headers_count;
5656
long timeout_ms;
57+
size_t max_response_bytes;
5758
} smoke_client_options;
5859

5960
static inline char *smoke_strdup(const char *value) {
@@ -204,6 +205,7 @@ static inline void smoke_client_options_init(smoke_client_options *options) {
204205
if (!options) return;
205206
memset(options, 0, sizeof(*options));
206207
options->timeout_ms = 10000L;
208+
options->max_response_bytes = (size_t)1024 * 1024;
207209
}
208210

209211

@@ -340,9 +342,9 @@ static inline void smoke_smoke_echo_response_free(smoke_smoke_echo_response *val
340342
}
341343
memset(value, 0, sizeof(*value));
342344
}
343-
/* Must be called before using the generated libcurl client runtime. */
345+
/* Must be called before using the generated client runtime. */
344346
int smoke_runtime_init(smoke_error *error);
345-
/* Call after the last use of the generated libcurl client runtime. */
347+
/* Call after the last use of the generated client runtime. */
346348
void smoke_runtime_cleanup(void);
347349
typedef struct smoke_smoke_client smoke_smoke_client;
348350

client.go.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
{{- $prefix := .Prefix -}}
33
{{- $services := .Services -}}
44

5-
/* Must be called before using the generated libcurl client runtime. */
5+
/* Must be called before using the generated client runtime. */
66
int {{ printf "%s_runtime_init" $prefix }}({{ printf "%s_error" $prefix }} *error);
7-
/* Call after the last use of the generated libcurl client runtime. */
7+
/* Call after the last use of the generated client runtime. */
88
void {{ printf "%s_runtime_cleanup" $prefix }}(void);
99

1010
{{- range $_, $service := $services }}

0 commit comments

Comments
 (0)