EV-ClickHouse

 view release on metacpan or  search on metacpan

xs/proto_http.c  view on Meta::CPAN

    if (self->session_id) {
        plen += (size_t)snprintf(params + plen, params_cap - plen, "&session_id=");
        plen += url_encode(self->session_id, strlen(self->session_id), params + plen);
    }
    if (url_sql) {
        plen += (size_t)snprintf(params + plen, params_cap - plen, "&query=");
        plen += url_encode(url_sql, url_sql_len, params + plen);
    }
    plen = append_settings_url_params(params, plen,
                                       defaults, overrides,
                                       &query_id, &query_id_len);
    if (query_id) {
        size_t need = plen + 10 + query_id_len * 3 + 1;
        if (need > params_cap) {
            params_cap = need;
            Renew(params, params_cap, char);
        }
        plen += (size_t)snprintf(params + plen, params_cap - plen, "&query_id=");
        plen += url_encode(query_id, query_id_len, params + plen);
    }
    params[plen] = '\0';

    /* user/password are quoted as-is in the X-ClickHouse-* form, or
     * base64-expanded ((n+2)/3)*4 in the Basic auth form. The 4/3 factor
     * is enough either way. */
    req_cap = 512 + body_len + plen
           + (self->host ? strlen(self->host) : 0)
           + (self->user ? strlen(self->user) * 2 : 0)
           + (self->password ? strlen(self->password) * 2 : 0);
    Newx(req, req_cap, char);

    /* request line + headers */
    pos += snprintf(req + pos, req_cap - pos,
                    "POST /%s HTTP/1.1\r\n", params);
    Safefree(params);
    pos += snprintf(req + pos, req_cap - pos,
                    "Host: %s:%u\r\n", self->host, self->port);
    if (self->http_basic_auth && self->user) {
        /* "user:pass" → base64. Single allocation: [cred][b64]. */
        size_t ul = strlen(self->user);
        size_t pl = self->password ? strlen(self->password) : 0;
        size_t cred_len = ul + 1 + pl;
        size_t b64_cap = ((cred_len + 2) / 3) * 4 + 1;
        char *buf;
        Newx(buf, cred_len + b64_cap, char);
        memcpy(buf, self->user, ul);
        buf[ul] = ':';
        if (pl) memcpy(buf + ul + 1, self->password, pl);
        base64_encode((const unsigned char *)buf, cred_len, buf + cred_len);
        pos += snprintf(req + pos, req_cap - pos,
                        "Authorization: Basic %s\r\n", buf + cred_len);
        Safefree(buf);
    } else {
        if (self->user)
            pos += snprintf(req + pos, req_cap - pos,
                            "X-ClickHouse-User: %s\r\n", self->user);
        if (self->password && self->password[0])
            pos += snprintf(req + pos, req_cap - pos,
                            "X-ClickHouse-Key: %s\r\n", self->password);
    }
    pos += snprintf(req + pos, req_cap - pos, "Connection: keep-alive\r\n");
    if (self->compress)
        pos += snprintf(req + pos, req_cap - pos, "Accept-Encoding: gzip\r\n");
    if (content_encoding)
        pos += snprintf(req + pos, req_cap - pos, "%s", content_encoding);
    pos += snprintf(req + pos, req_cap - pos,
                    "Content-Length: %lu\r\n\r\n", (unsigned long)body_len);

    /* body */
    if (body_len > 0) {
        if (pos + body_len > req_cap) {
            req_cap = pos + body_len + 1;
            Renew(req, req_cap, char);
        }
        Copy(body ? body : body_data, req + pos, body_len, char);
        pos += body_len;
    }

    if (body) Safefree(body);

    *req_len = pos;
    return req;
}

/* Build HTTP GET /ping request */
static char* build_http_ping_request(ev_clickhouse_t *self, size_t *req_len) {
    char *req;
    size_t req_cap = 128 + (self->host ? strlen(self->host) : 0);
    size_t pos = 0;

    Newx(req, req_cap, char);
    pos = snprintf(req, req_cap,
                   "GET /ping HTTP/1.1\r\n"
                   "Host: %s:%u\r\n"
                   "Connection: keep-alive\r\n\r\n",
                   self->host, self->port);
    if (pos >= req_cap) pos = req_cap - 1;
    *req_len = pos;
    return req;
}

/* --- HTTP response parsing --- */

/* Length-bounded uint parser. Stops at the first non-digit OR at `len`,
 * whichever comes first. Used to parse X-ClickHouse-Summary values out
 * of a non-NUL-terminated header buffer without letting strtoull scan
 * into bytes past the header on malformed input. */
static uint64_t parse_uint_within(const char *p, size_t len) {
    uint64_t n = 0;
    size_t i;
    for (i = 0; i < len; i++) {
        unsigned char c = (unsigned char)p[i];
        if (c < '0' || c > '9') break;
        n = n * 10 + (c - '0');
    }
    return n;
}

/* Find \r\n\r\n in recv_buf. Returns offset past it, or 0 if not found. */
static size_t find_header_end(const char *buf, size_t len) {
    size_t i;
    if (len < 4) return 0;
    for (i = 0; i <= len - 4; i++) {
        if (buf[i] == '\r' && buf[i+1] == '\n' &&
            buf[i+2] == '\r' && buf[i+3] == '\n') {
            return i + 4;
        }
    }
    return 0;
}

/* Extract ClickHouse error code from HTTP error body ("Code: NNN. ...") */
static int32_t parse_ch_error_code(const char *body, size_t len) {
    if (len > 6 && memcmp(body, "Code: ", 6) == 0)
        return (int32_t)atoi(body + 6);
    return 0;
}

/* Format an HTTP error response into a Newx-allocated "HTTP NNN: ..." message
 * and update self->last_error_code. Body may be gzip-compressed. Caller must
 * Safefree the returned pointer. */
static char* format_http_error(ev_clickhouse_t *self, int status,
                                const char *body, size_t body_len, int is_gzip) {
    char *errmsg;
    char *err_body = (char *)body;
    size_t err_len = body_len;
    if (is_gzip && body_len > 0) {
        size_t dec_len;
        char *dec = gzip_decompress(body, body_len, &dec_len);
        if (dec) { err_body = dec; err_len = dec_len; }
    }
    while (err_len > 0 && (err_body[err_len-1] == '\n' || err_body[err_len-1] == '\r'))
        err_len--;
    self->last_error_code = parse_ch_error_code(err_body, err_len);
    Newx(errmsg, err_len + 64, char);



( run in 1.698 second using v1.01-cache-2.11-cpan-14f38c9f855 )