Mercurial > sci
annotate libsci/apic.c @ 64:562372396019
misc: improve manual pages and documentation
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 18 Aug 2022 20:17:18 +0200 |
parents | 308aa1086702 |
children | 26bbca3765dd |
rev | line source |
---|---|
33
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
1 /* |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
2 * apic.c -- synchronous HTTP request |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
3 * |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
4 * Copyright (c) 2021-2022 David Demelier <markand@malikania.fr> |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
5 * |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
6 * Permission to use, copy, modify, and/or distribute this software for any |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
7 * purpose with or without fee is hereby granted, provided that the above |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
8 * copyright notice and this permission notice appear in all copies. |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
9 * |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
17 */ |
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
18 |
19 | 19 #include <assert.h> |
20 #include <errno.h> | |
21 #include <stdarg.h> | |
22 #include <stdio.h> | |
23 #include <string.h> | |
24 | |
25 #include <curl/curl.h> | |
26 | |
27 #include "apic.h" | |
28 #include "util.h" | |
29 | |
56 | 30 struct context { |
31 /* Output */ | |
32 FILE *fp; | |
33 char *out; | |
34 size_t outsz; | |
35 | |
36 /* HTTP requester. */ | |
37 char url[1024]; | |
38 char keyhdr[128]; | |
19 | 39 CURL *curl; |
40 CURLcode code; | |
41 struct curl_slist *headers; | |
56 | 42 |
43 /* JSON stuff. */ | |
44 json_t *doc; | |
45 json_error_t error; | |
19 | 46 }; |
47 | |
20 | 48 struct apiconf apiconf = { |
49 .baseurl = "http://127.0.0.1" | |
50 }; | |
51 | |
19 | 52 static size_t |
53 writer(char *in, size_t n, size_t w, FILE *fp) | |
54 { | |
55 if (fwrite(in, n, w, fp) != w) | |
56 return 0; | |
57 | |
58 return w; | |
59 } | |
60 | |
56 | 61 static inline void |
62 init_url(struct context *ctx, const char *fmt, va_list ap) | |
19 | 63 { |
20 | 64 char page[128]; |
19 | 65 |
20 | 66 vsnprintf(page, sizeof (page), fmt, ap); |
56 | 67 snprintf(ctx->url, sizeof (ctx->url), "%s/%s", apiconf.baseurl, page); |
19 | 68 } |
69 | |
56 | 70 static inline void |
71 init_output(struct context *ctx) | |
19 | 72 { |
56 | 73 ctx->fp = util_open_memstream(&ctx->out, &ctx->outsz); |
19 | 74 } |
75 | |
50
b474f0985e39
scictl: add API key support
David Demelier <markand@malikania.fr>
parents:
33
diff
changeset
|
76 static void |
56 | 77 init_curl(struct context *ctx, const char *body) |
19 | 78 { |
50
b474f0985e39
scictl: add API key support
David Demelier <markand@malikania.fr>
parents:
33
diff
changeset
|
79 /* Create API key string. */ |
56 | 80 snprintf(ctx->keyhdr, sizeof (ctx->keyhdr), "X-Api-Key: %s", apiconf.key); |
81 | |
82 ctx->headers = curl_slist_append(ctx->headers, "Content-Type: application/json"); | |
83 ctx->headers = curl_slist_append(ctx->headers, ctx->keyhdr); | |
19 | 84 |
56 | 85 ctx->curl = curl_easy_init(); |
86 curl_easy_setopt(ctx->curl, CURLOPT_HTTPHEADER, ctx->headers); | |
87 curl_easy_setopt(ctx->curl, CURLOPT_URL, ctx->url); | |
88 curl_easy_setopt(ctx->curl, CURLOPT_FOLLOWLOCATION, 1L); | |
89 curl_easy_setopt(ctx->curl, CURLOPT_TIMEOUT, 3L); | |
90 curl_easy_setopt(ctx->curl, CURLOPT_WRITEFUNCTION, writer); | |
91 curl_easy_setopt(ctx->curl, CURLOPT_WRITEDATA, ctx->fp); | |
92 curl_easy_setopt(ctx->curl, CURLOPT_NOSIGNAL, 1L); | |
19 | 93 |
94 /* Assume POST request if there is a body. */ | |
95 if (body) { | |
56 | 96 curl_easy_setopt(ctx->curl, CURLOPT_POSTFIELDS, body); |
97 curl_easy_setopt(ctx->curl, CURLOPT_POSTFIELDSIZE, strlen(body)); | |
19 | 98 } |
56 | 99 } |
19 | 100 |
56 | 101 static void |
102 invoke(struct apic *req, struct context *ctx) | |
103 { | |
104 ctx->code = curl_easy_perform(ctx->curl); | |
105 | |
106 /* Close file descriptor to get its output. */ | |
107 fclose(ctx->fp); | |
108 ctx->fp = NULL; | |
109 | |
110 if (ctx->code != CURLE_OK) | |
111 snprintf(req->error, sizeof (req->error), "%s", curl_easy_strerror(ctx->code)); | |
112 else { | |
113 curl_easy_getinfo(ctx->curl, CURLINFO_RESPONSE_CODE, &req->status); | |
114 | |
115 if (req->status != 200) | |
116 snprintf(req->error, sizeof (req->error), "HTTP returned %ld", req->status); | |
117 else if (ctx->out[0] && !(ctx->doc = json_loads(ctx->out, 0, &ctx->error))) | |
118 snprintf(req->error, sizeof (req->error), "JSON parse error: %s", ctx->error.text); | |
119 } | |
120 } | |
121 | |
122 static void | |
123 finish(struct context *ctx) | |
124 { | |
125 if (ctx->fp) | |
126 fclose(ctx->fp); | |
127 | |
128 free(ctx->out); | |
129 | |
130 if (ctx->curl) | |
131 curl_easy_cleanup(ctx->curl); | |
132 if (ctx->headers) | |
133 curl_slist_free_all(ctx->headers); | |
19 | 134 } |
135 | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
136 static json_t * |
20 | 137 perform(struct apic *req, const char *body, const char *fmt, va_list ap) |
19 | 138 { |
56 | 139 struct context ctx = {0}; |
19 | 140 |
56 | 141 init_url(&ctx, fmt, ap); |
142 init_output(&ctx); | |
143 init_curl(&ctx, body); | |
19 | 144 |
56 | 145 /* Perform that request now to obtain data. */ |
146 invoke(req, &ctx); | |
147 finish(&ctx); | |
19 | 148 |
56 | 149 return ctx.doc; |
19 | 150 } |
151 | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
152 static json_t * |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
153 get(struct apic *req, const char *fmt, ...) |
22
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
154 { |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
155 va_list ap; |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
156 json_t *ret; |
22
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
157 |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
158 va_start(ap, fmt); |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
159 ret = perform(req, NULL, fmt, ap); |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
160 va_end(ap); |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
161 |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
162 return ret; |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
163 } |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
164 |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
165 static int |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
166 create(struct apic *req, json_t *doc, const char *fmt, ...) |
22
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
167 { |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
168 va_list ap; |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
169 json_t *ret; |
22
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
170 char *body; |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
171 |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
172 memset(req, 0, sizeof (*req)); |
33
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
173 body = util_json_dump(doc); |
22
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
174 |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
175 va_start(ap, fmt); |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
176 ret = perform(req, body, fmt, ap); |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
177 va_end(ap); |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
178 |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
179 /* TODO: update id. */ |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
180 (void)ret; |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
181 |
22
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
182 free(body); |
33
1d0ddf9e6efd
misc: general documentation
David Demelier <markand@malikania.fr>
parents:
27
diff
changeset
|
183 json_decref(ret); |
22
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
184 |
50
b474f0985e39
scictl: add API key support
David Demelier <markand@malikania.fr>
parents:
33
diff
changeset
|
185 return req->error[0] ? -1 : 0; |
22
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
186 } |
dd078aea5d02
misc: use project/worker name as primary key
David Demelier <markand@malikania.fr>
parents:
20
diff
changeset
|
187 |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
188 json_t * |
20 | 189 apic_get(struct apic *req, const char *fmt, ...) |
19 | 190 { |
191 assert(req); | |
192 assert(fmt); | |
193 | |
194 va_list ap; | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
195 json_t *ret; |
19 | 196 |
197 va_start(ap, fmt); | |
198 ret = perform(req, NULL, fmt, ap); | |
199 va_end(ap); | |
200 | |
201 return ret; | |
202 } | |
203 | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
204 json_t * |
20 | 205 apic_post(struct apic *req, const json_t *doc, const char *fmt, ...) |
19 | 206 { |
207 assert(req); | |
208 assert(fmt); | |
209 | |
210 va_list ap; | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
211 json_t *ret; |
19 | 212 char *body; |
213 | |
214 if (!(body = json_dumps(doc, JSON_COMPACT))) | |
215 util_die("%s", strerror(ENOMEM)); | |
216 | |
217 va_start(ap, fmt); | |
218 ret = perform(req, body, fmt, ap); | |
219 va_end(ap); | |
220 | |
221 free(body); | |
222 | |
223 return ret; | |
224 } | |
20 | 225 |
226 int | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
227 apic_job_add(struct apic *req, json_t *job) |
20 | 228 { |
229 assert(req); | |
230 assert(job); | |
231 | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
232 return create(req, job, "api/v1/jobs"); |
20 | 233 } |
234 | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
235 json_t * |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
236 apic_job_todo(struct apic *req, const char *worker_name) |
20 | 237 { |
238 assert(req); | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
239 assert(worker_name); |
20 | 240 |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
241 return get(req, "api/v1/todo/%s", worker_name); |
20 | 242 } |
243 | |
244 int | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
245 apic_jobresult_add(struct apic *req, json_t *res) |
20 | 246 { |
247 assert(req); | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
248 assert(res); |
20 | 249 |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
250 return create(req, res, "api/v1/jobresults"); |
20 | 251 } |
252 | |
253 int | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
254 apic_project_save(struct apic *req, json_t *p) |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
255 { |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
256 assert(req); |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
257 assert(p); |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
258 |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
259 return create(req, p, "api/v1/projects"); |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
260 } |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
261 |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
262 json_t * |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
263 apic_project_list(struct apic *req) |
20 | 264 { |
265 assert(req); | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
266 |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
267 return get(req, "api/v1/projects"); |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
268 } |
20 | 269 |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
270 json_t * |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
271 apic_project_find(struct apic *req, const char *name) |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
272 { |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
273 assert(req); |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
274 assert(name); |
20 | 275 |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
276 return get(req, "api/v1/projects/%s", name); |
20 | 277 } |
278 | |
279 int | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
280 apic_worker_save(struct apic *req, json_t *wk) |
20 | 281 { |
282 assert(req); | |
283 assert(wk); | |
284 | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
285 return create(req, wk, "api/v1/workers"); |
20 | 286 } |
287 | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
288 json_t * |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
289 apic_worker_list(struct apic *req) |
20 | 290 { |
291 assert(req); | |
292 | |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
293 return get(req, "api/v1/workers"); |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
294 } |
20 | 295 |
27
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
296 json_t * |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
297 apic_worker_find(struct apic *req, const char *name) |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
298 { |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
299 assert(req); |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
300 assert(name); |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
301 |
dae2de19ca5d
misc: switch to JSON everywhere
David Demelier <markand@malikania.fr>
parents:
24
diff
changeset
|
302 return get(req, "api/v1/workers/%s", name); |
20 | 303 } |