File syn_http.c¶
File List > net > syn_http.c
Go to the documentation of this file
#if __has_include("syn_config.h")
#include "syn_config.h"
#endif
#if !defined(SYN_USE_HTTP) || SYN_USE_HTTP
#include "../port/syn_port_system.h"
#include "../util/syn_assert.h"
#include "../util/syn_fmt.h"
#include "syn_http.h"
#include <stdlib.h>
#include <string.h>
#define HTTP_RECV_TIMEOUT_MS 10000
/* ── Helpers ────────────────────────────────────────────────────────────── */
static bool prefix_icase(const char *str, const char *prefix)
{
return syn_str_prefix_icase(str, prefix);
}
static uint32_t parse_uint(const char *s)
{
return syn_fmt_parse_uint(s);
}
static bool sock_write_str(SYN_Socket sock, const char *str)
{
size_t len = strlen(str);
int sent = syn_port_sock_send_all(sock, str, len);
return (sent == (int)len);
}
static bool send_request(SYN_Socket sock, const char *method, const char *host, const char *path,
const SYN_HttpHeader *headers, uint8_t header_count,
const char *content_type, size_t content_length)
{
if (!sock_write_str(sock, method))
return false;
if (!sock_write_str(sock, " "))
return false;
if (!sock_write_str(sock, path))
return false;
if (!sock_write_str(sock, " HTTP/1.1\r\n"))
return false;
if (!sock_write_str(sock, "Host: "))
return false;
if (!sock_write_str(sock, host))
return false;
if (!sock_write_str(sock, "\r\n"))
return false;
if (content_type != NULL) {
if (!sock_write_str(sock, "Content-Type: "))
return false;
if (!sock_write_str(sock, content_type))
return false;
if (!sock_write_str(sock, "\r\n"))
return false;
}
if (content_length > 0) {
char len_str[16];
int pos = 0;
uint32_t cl = (uint32_t)content_length;
char tmp[16];
int tpos = 0;
while (cl > 0) {
tmp[tpos++] = (char)('0' + (cl % 10));
cl /= 10;
}
for (int i = tpos - 1; i >= 0; i--) {
len_str[pos++] = tmp[i];
}
len_str[pos] = '\0';
if (!sock_write_str(sock, "Content-Length: "))
return false;
if (!sock_write_str(sock, len_str))
return false;
if (!sock_write_str(sock, "\r\n"))
return false;
}
if (!sock_write_str(sock, "Connection: close\r\n"))
return false;
for (uint8_t i = 0; i < header_count; i++) {
if (headers[i].name == NULL || headers[i].value == NULL)
continue;
if (!sock_write_str(sock, headers[i].name))
return false;
if (!sock_write_str(sock, ": "))
return false;
if (!sock_write_str(sock, headers[i].value))
return false;
if (!sock_write_str(sock, "\r\n"))
return false;
}
if (!sock_write_str(sock, "\r\n"))
return false;
return true;
}
static void parse_redirect_url(const char *url, const char *orig_host, uint16_t orig_port,
char *host_out, size_t host_sz, char *path_out, size_t path_sz,
uint16_t *port_out)
{
if (strncmp(url, "http://", 7) == 0) {
url += 7;
const char *slash = strchr(url, '/');
const char *colon = strchr(url, ':');
if (colon && (!slash || colon < slash)) {
size_t host_len = (size_t)(colon - url);
if (host_len >= host_sz)
host_len = host_sz - 1;
memcpy(host_out, url, host_len);
host_out[host_len] = '\0';
*port_out = (uint16_t)atoi(colon + 1);
} else {
size_t host_len = slash ? (size_t)(slash - url) : strlen(url);
if (host_len >= host_sz)
host_len = host_sz - 1;
memcpy(host_out, url, host_len);
host_out[host_len] = '\0';
*port_out = 80;
}
if (slash) {
strncpy(path_out, slash, path_sz - 1);
} else {
strncpy(path_out, "/", path_sz - 1);
}
path_out[path_sz - 1] = '\0';
} else if (url[0] == '/') {
strncpy(host_out, orig_host, host_sz - 1);
host_out[host_sz - 1] = '\0';
*port_out = orig_port;
strncpy(path_out, url, path_sz - 1);
path_out[path_sz - 1] = '\0';
} else {
strncpy(host_out, orig_host, host_sz - 1);
host_out[host_sz - 1] = '\0';
*port_out = orig_port;
path_out[0] = '/';
strncpy(path_out + 1, url, path_sz - 2);
path_out[path_sz - 1] = '\0';
}
}
/* ── API ────────────────────────────────────────────────────────────────── */
SYN_Status syn_http_client_init(SYN_HttpClient *client, const char *method, const char *host,
uint16_t port, const char *path, const char *content_type,
const uint8_t *body, size_t body_len, const SYN_HttpHeader *headers,
uint8_t header_count, SYN_HttpBodyCallback body_cb, void *cb_ctx,
uint8_t *work_buf, size_t work_buf_size)
{
SYN_ASSERT(client != NULL);
SYN_ASSERT(method != NULL);
SYN_ASSERT(host != NULL);
SYN_ASSERT(path != NULL);
SYN_ASSERT(work_buf != NULL);
SYN_ASSERT(work_buf_size >= 128);
memset(client, 0, sizeof(*client));
client->state = SYN_HTTP_STATE_IDLE;
client->sock = SYN_SOCKET_INVALID;
client->method = method;
client->host = host;
client->port = port;
client->path = path;
client->content_type = content_type;
client->body = body;
client->body_len = body_len;
client->headers = headers;
client->header_count = header_count;
client->body_cb = body_cb;
client->cb_ctx = cb_ctx;
client->work_buf = work_buf;
client->work_buf_size = work_buf_size;
client->status = SYN_OK;
strncpy(client->cur_host, host, sizeof(client->cur_host) - 1);
client->cur_host[sizeof(client->cur_host) - 1] = '\0';
strncpy(client->cur_path, path, sizeof(client->cur_path) - 1);
client->cur_path[sizeof(client->cur_path) - 1] = '\0';
client->cur_port = port;
return SYN_OK;
}
SYN_PT_Status syn_http_client_task(SYN_PT *pt, SYN_Task *task)
{
SYN_HttpClient *c = (SYN_HttpClient *)task->user_data;
SYN_ASSERT(c != NULL);
PT_BEGIN(pt);
c->hops = 0;
for (;;) {
c->state = SYN_HTTP_STATE_CONNECTING;
c->sock = syn_port_sock_connect_host(c->cur_host, c->cur_port);
if (c->sock == SYN_SOCKET_INVALID) {
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_ERROR;
PT_EXIT(pt);
}
c->state = SYN_HTTP_STATE_SENDING_REQUEST;
if (!send_request(c->sock, c->method, c->cur_host, c->cur_path, c->headers, c->header_count,
c->content_type, c->body_len)) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_ERROR;
PT_EXIT(pt);
}
if (c->body != NULL && c->body_len > 0) {
int sent = syn_port_sock_send_all(c->sock, c->body, c->body_len);
if (sent != (int)c->body_len) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_ERROR;
PT_EXIT(pt);
}
}
c->state = SYN_HTTP_STATE_READING_HEADERS;
c->buf_used = 0;
memset(&c->resp, 0, sizeof(c->resp));
c->header_timeout_ms = syn_port_get_tick_ms();
for (;;) {
int n = syn_port_sock_recv(c->sock, c->work_buf + c->buf_used,
c->work_buf_size - 1 - c->buf_used, 0);
if (n > 0) {
c->buf_used += (size_t)n;
c->work_buf[c->buf_used] = '\0';
c->header_timeout_ms = syn_port_get_tick_ms();
} else if (n == 0) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_ERROR;
PT_EXIT(pt);
} else {
if ((syn_port_get_tick_ms() - c->header_timeout_ms) >= HTTP_RECV_TIMEOUT_MS) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_TIMEOUT;
PT_EXIT(pt);
}
PT_DEFER(pt, task);
continue;
}
const char *end = strstr((const char *)c->work_buf, "\r\n\r\n");
if (end != NULL) {
c->body_start = (size_t)(end - (const char *)c->work_buf) + 4;
break;
}
if (c->buf_used >= c->work_buf_size - 1) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_ERROR;
PT_EXIT(pt);
}
}
{
const char *line = (const char *)c->work_buf;
const char *sp = strchr(line, ' ');
if (sp == NULL) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_ERROR;
PT_EXIT(pt);
}
c->resp.status_code = (int)parse_uint(sp + 1);
const char *hdr_end = (const char *)c->work_buf + c->body_start - 2;
const char *cur = strchr(line, '\n');
if (cur)
cur++;
while (cur && cur < hdr_end) {
if (prefix_icase(cur, "content-length:")) {
const char *val = cur + 15;
while (*val == ' ')
val++;
c->resp.content_length = parse_uint(val);
} else if (prefix_icase(cur, "transfer-encoding:")) {
const char *val = cur + 18;
while (*val == ' ')
val++;
if (prefix_icase(val, "chunked")) {
c->resp.chunked = true;
}
} else if (prefix_icase(cur, "connection:")) {
const char *val = cur + 11;
while (*val == ' ')
val++;
if (prefix_icase(val, "close")) {
c->resp.connection_close = true;
}
} else if (prefix_icase(cur, "location:")) {
const char *val = cur + 9;
while (*val == ' ')
val++;
size_t len = 0;
while (val[len] != '\r' && val[len] != '\n' &&
len < sizeof(c->resp.location) - 1) {
c->resp.location[len] = val[len];
len++;
}
c->resp.location[len] = '\0';
}
cur = strchr(cur, '\n');
if (cur)
cur++;
}
}
if (c->resp.status_code >= 300 && c->resp.status_code < 400 &&
c->resp.location[0] != '\0') {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
if (++c->hops >= 5) {
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_ERROR;
PT_EXIT(pt);
}
char next_host[64];
char next_path[128];
uint16_t next_port;
parse_redirect_url(c->resp.location, c->cur_host, c->cur_port, next_host,
sizeof(next_host), next_path, sizeof(next_path), &next_port);
strncpy(c->cur_host, next_host, sizeof(c->cur_host) - 1);
c->cur_host[sizeof(c->cur_host) - 1] = '\0';
strncpy(c->cur_path, next_path, sizeof(c->cur_path) - 1);
c->cur_path[sizeof(c->cur_path) - 1] = '\0';
c->cur_port = next_port;
continue;
}
if (c->body_cb == NULL) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_DONE;
c->status = SYN_OK;
PT_EXIT(pt);
}
c->state = SYN_HTTP_STATE_READING_BODY;
c->buf_pos = c->body_start;
c->body_timeout_ms = syn_port_get_tick_ms();
if (c->resp.chunked) {
c->chunk_state = 0;
c->chunk_line_pos = 0;
for (;;) {
if (c->chunk_state == 0) {
if (c->buf_pos < c->buf_used) {
uint8_t ch = c->work_buf[c->buf_pos++];
if (c->chunk_line_pos < sizeof(c->chunk_line) - 1) {
c->chunk_line[c->chunk_line_pos++] = (char)ch;
}
if (ch == '\n') {
c->chunk_line[c->chunk_line_pos] = '\0';
c->chunk_remaining = 0;
const char *p = c->chunk_line;
while (*p) {
char hc = *p++;
if (hc >= '0' && hc <= '9')
c->chunk_remaining =
(c->chunk_remaining << 4) | (uint32_t)(hc - '0');
else if (hc >= 'a' && hc <= 'f')
c->chunk_remaining =
(c->chunk_remaining << 4) | (uint32_t)(hc - 'a' + 10);
else if (hc >= 'A' && hc <= 'F')
c->chunk_remaining =
(c->chunk_remaining << 4) | (uint32_t)(hc - 'A' + 10);
else if (hc == ';' || hc == '\r' || hc == '\n')
break;
else {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_ERROR;
PT_EXIT(pt);
}
}
if (c->chunk_remaining == 0) {
c->chunk_state = 2;
} else {
c->chunk_state = 1;
}
c->chunk_line_pos = 0;
}
} else {
int n = syn_port_sock_recv(c->sock, c->work_buf, c->work_buf_size, 0);
if (n > 0) {
c->buf_used = (size_t)n;
c->buf_pos = 0;
c->body_timeout_ms = syn_port_get_tick_ms();
} else if (n == 0) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_ERROR;
PT_EXIT(pt);
} else {
if ((syn_port_get_tick_ms() - c->body_timeout_ms) >=
HTTP_RECV_TIMEOUT_MS) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_TIMEOUT;
PT_EXIT(pt);
}
PT_YIELD(pt);
}
}
} else if (c->chunk_state == 1) {
if (c->chunk_remaining > 0) {
size_t avail = c->buf_used - c->buf_pos;
if (avail > 0) {
size_t to_deliver =
(avail > c->chunk_remaining) ? c->chunk_remaining : avail;
if (!c->body_cb(c->work_buf + c->buf_pos, to_deliver, c->cb_ctx)) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_ERROR;
PT_EXIT(pt);
}
c->buf_pos += to_deliver;
c->chunk_remaining -= (uint32_t)to_deliver;
} else {
int n = syn_port_sock_recv(c->sock, c->work_buf, c->work_buf_size, 0);
if (n > 0) {
c->buf_used = (size_t)n;
c->buf_pos = 0;
c->body_timeout_ms = syn_port_get_tick_ms();
} else if (n == 0) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_ERROR;
PT_EXIT(pt);
} else {
if ((syn_port_get_tick_ms() - c->body_timeout_ms) >=
HTTP_RECV_TIMEOUT_MS) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_TIMEOUT;
PT_EXIT(pt);
}
PT_YIELD(pt);
}
}
} else {
if (c->buf_pos < c->buf_used) {
uint8_t ch = c->work_buf[c->buf_pos++];
if (ch == '\n') {
c->chunk_state = 0;
}
} else {
int n = syn_port_sock_recv(c->sock, c->work_buf, c->work_buf_size, 0);
if (n > 0) {
c->buf_used = (size_t)n;
c->buf_pos = 0;
c->body_timeout_ms = syn_port_get_tick_ms();
} else if (n == 0) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_ERROR;
PT_EXIT(pt);
} else {
if ((syn_port_get_tick_ms() - c->body_timeout_ms) >=
HTTP_RECV_TIMEOUT_MS) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_TIMEOUT;
PT_EXIT(pt);
}
PT_DEFER(pt, task);
}
}
}
} else if (c->chunk_state == 2) {
if (c->buf_pos < c->buf_used) {
uint8_t ch = c->work_buf[c->buf_pos++];
if (ch == '\n') {
break;
}
} else {
int n = syn_port_sock_recv(c->sock, c->work_buf, c->work_buf_size, 0);
if (n > 0) {
c->buf_used = (size_t)n;
c->buf_pos = 0;
c->body_timeout_ms = syn_port_get_tick_ms();
} else if (n == 0) {
break;
} else {
if ((syn_port_get_tick_ms() - c->body_timeout_ms) >=
HTTP_RECV_TIMEOUT_MS) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_TIMEOUT;
PT_EXIT(pt);
}
PT_DEFER(pt, task);
}
}
}
}
} else {
c->body_remaining = c->resp.content_length;
c->known_length = (c->resp.content_length > 0);
size_t avail = c->buf_used - c->buf_pos;
if (avail > 0) {
size_t to_deliver = avail;
if (c->known_length && to_deliver > c->body_remaining) {
to_deliver = c->body_remaining;
}
if (!c->body_cb(c->work_buf + c->buf_pos, to_deliver, c->cb_ctx)) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_ERROR;
PT_EXIT(pt);
}
if (c->known_length) {
c->body_remaining -= (uint32_t)to_deliver;
}
c->buf_pos += to_deliver;
}
while (!c->known_length || c->body_remaining > 0) {
size_t to_read = c->work_buf_size;
if (c->known_length && to_read > c->body_remaining) {
to_read = c->body_remaining;
}
int n = syn_port_sock_recv(c->sock, c->work_buf, to_read, 0);
if (n > 0) {
if (!c->body_cb(c->work_buf, (size_t)n, c->cb_ctx)) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_ERROR;
PT_EXIT(pt);
}
if (c->known_length) {
c->body_remaining -= (uint32_t)n;
}
c->body_timeout_ms = syn_port_get_tick_ms();
} else if (n == 0) {
if (c->known_length && c->body_remaining > 0) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_ERROR;
PT_EXIT(pt);
}
break;
} else {
if ((syn_port_get_tick_ms() - c->body_timeout_ms) >= HTTP_RECV_TIMEOUT_MS) {
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_ERROR;
c->status = SYN_TIMEOUT;
PT_EXIT(pt);
}
PT_DEFER(pt, task);
}
}
}
syn_port_sock_close(c->sock);
c->sock = SYN_SOCKET_INVALID;
c->state = SYN_HTTP_STATE_DONE;
c->status = SYN_OK;
break;
}
PT_END(pt);
}
#endif /* SYN_USE_HTTP */