[tarantool-patches] Re: [PATCH v1 1/1] http: fix httpc auto-managed headers

Vladimir Davydov vdavydov.dev at gmail.com
Thu Mar 7 16:51:35 MSK 2019


On Mon, Feb 25, 2019 at 06:21:31PM +0300, Kirill Shcherbatov wrote:
> diff --git a/src/httpc.c b/src/httpc.c
> index 950f8b32f..96740f950 100644
> --- a/src/httpc.c
> +++ b/src/httpc.c
> @@ -170,6 +174,56 @@ httpc_request_delete(struct httpc_request *req)
>  	mempool_free(&req->env->req_pool, req);
>  }
>  
> +/**
> + * Update bitmask of the http request headers that httpc may set
> + * automatically. In case of reserved pattern is found in header,
> + * routine sets corresponding bit in auto_headers_mask.
> + * Returns -1 when header is reserved and it's bit is already set
> + * in auto_headers_mask; 0 otherwise,
> + * @param auto_headers_mask The bitmask of httpc-auto-managed
> + *                          headers to pointer.
> + * @param header The HTTP header string.
> + * @retval 0 When specified header is not auto-managed or when
> + *           corresponding bit was not set in auto_headers_mask.
> + * @retval -1 otherwise.
> +*/
> +static int
> +httpc_set_header_bit(uint8_t *auto_headers_mask, const char *header)
> +{
> +	/*
> +	 * The sequence of managed headers must be sorted to
> +	 * stop scan when strcasecmp < 0. The header is expected
> +	 * to be formated with "%s: %s" pattern, so direct size
> +	 * verification is redundant.
> +	 */
> +	struct {
> +		const char *name;
> +		int len;
> +	} managed_headers[] = {
> +		{"Accept: ", sizeof("Accept: ") - 1},
> +		{"Connection: ", sizeof("Connection: ") - 1},
> +		{"Content-Length: ", sizeof("Content-Length: ") - 1},
> +		{"Keep-Alive: ", sizeof("Keep-Alive: ") - 1},

 - A space after a colon (:) isn't mandatory according to the http
   protocol, e.g. "content-length:10" is a valid header.

 - I don't quite like string literal duplication. Can we do without it?
   Use strlen, may be? The compiler should resolve it at compile time
   AFAIK.

 - Checking "content-length" doesn't make much sense IMO. No-one in
   their right mind will overwrite it after setting a body. We only
   need to handle "accept", "connection", "keep-alive".

> +	};
> +	int managed_headers_cnt = lengthof(managed_headers);
> +	assert(managed_headers_cnt <
> +	       (int)sizeof(*auto_headers_mask) * CHAR_BIT);
> +	for (int i = 0; i < managed_headers_cnt; i++) {
> +		int rc = strncasecmp(header, managed_headers[i].name,
> +				     managed_headers[i].len);
> +		if (rc > 0)
> +			continue;
> +		if (rc < 0)
> +			return 0;
> +		int8_t bit_index = 1 << i;
> +		if ((*auto_headers_mask & bit_index) != 0)
> +			return -1;
> +		*auto_headers_mask |= bit_index;
> +		return 0;
> +	}
> +	return 0;
> +}
> +
>  int
>  httpc_set_header(struct httpc_request *req, const char *fmt, ...)
>  {
> @@ -178,6 +232,10 @@ httpc_set_header(struct httpc_request *req, const char *fmt, ...)
>  	const char *header = tt_vsprintf(fmt, ap);
>  	va_end(ap);
>  
> +	if (httpc_set_header_bit(&req->auto_headers_mask, header) != 0) {
> +		/* Do not add extra header to list. */
> +		return 0;
> +	}
>  	struct curl_slist *l = curl_slist_append(req->headers, header);
>  	if (l == NULL) {
>  		diag_set(OutOfMemory, strlen(header), "curl", "http header");
> diff --git a/src/httpc.h b/src/httpc.h
> index dc709e6fa..dc18ffb69 100644
> --- a/src/httpc.h
> +++ b/src/httpc.h
> @@ -109,16 +109,22 @@ struct httpc_request {
>  	struct region resp_headers;
>  	/** buffer of body */
>  	struct region resp_body;
> +	/**
> +	 * A bitmask of the http request headers from among
> +	 * the headers that httpc can set automatically.
> +	 * Each bit represents some auto-managed HTTP header.
> +	 * This allows to prevent set them twice.
> +	 */
> +	uint8_t auto_headers_mask;
>  };
>  
>  /**
>   * @brief Create a new HTTP request
> - * @param ctx - reference to context
> + * @param env - HTTP client environment.
>   * @return a new HTTP request object
>   */
>  struct httpc_request *
> -httpc_request_new(struct httpc_env *env, const char *method,
> -		  const char *url);
> +httpc_request_new(struct httpc_env *env);
>  
>  /**
>   * @brief Delete HTTP request
> @@ -137,6 +143,17 @@ httpc_request_delete(struct httpc_request *req);
>  int
>  httpc_set_header(struct httpc_request *req, const char *fmt, ...);
>  
> +/**
> + * Set HTTP method and destination url, set service headers.
> + * @param req HTTP request object to update.
> + * @param method HTTP request method (GET, HEAD, POST, PUT...).
> + * @param url The network resource URL address string.
> + * @retval 0 On success.
> + * @retval -1 Otherwise.
> + */
> +int
> +httpc_set_url(struct httpc_request *req, const char *method, const char *url);
> +
>  /**
>   * Sets body of request
>   * @param req request
> diff --git a/src/lua/httpc.c b/src/lua/httpc.c
> index 5572b70e9..d743eeb49 100644
> --- a/src/lua/httpc.c
> +++ b/src/lua/httpc.c
> @@ -145,12 +145,36 @@ luaT_httpc_request(lua_State *L)
>  	if (ctx == NULL)
>  		return luaL_error(L, "can't get httpc environment");
>  
> +	struct httpc_request *req = httpc_request_new(ctx);
> +	if (req == NULL)
> +		return luaT_error(L);
> +
> +	/*
> +	 * All user-defined headers must be set before calling
> +	 * other httpc module methods so that they take priority
> +	 * over the headers that httpc tries to set automatically.
> +	 */
> +	lua_getfield(L, 5, "headers");
> +	if (!lua_isnil(L, -1)) {
> +		lua_pushnil(L);
> +		while (lua_next(L, -2) != 0) {
> +			if (httpc_set_header(req, "%s: %s",
> +					     lua_tostring(L, -2),
> +					     lua_tostring(L, -1)) < 0) {
> +				httpc_request_delete(req);
> +				return luaT_error(L);
> +			}
> +			lua_pop(L, 1);
> +		}
> +	}
> +	lua_pop(L, 1);
> +

I don't like how httpc internal API now looks like: one should set
headers before the method/url and keepalive options, otherwise he/she
risks loosing them. And there isn't a word about this idiosyncrasy
anywhere in the comments.

Also, splitting request construction into allocation (_new) and
method/url initialization (_set_url) looks ugly to me, because no
request makes sense without them.

May be, we could set default headers in httpc_execute instead?



More information about the Tarantool-patches mailing list