OSSL_HTTP_REQ_CTX(3SSL)

πŸ’– OSSL_HTTP_REQ_CTX(3SSL) β€” OpenSSL πŸ’–

okay so bestie, you found the OpenSSL manual page for OSSL_HTTP_REQ_CTX(3SSL). it's giving low-level HTTP client energy, and honestly? iconic. this is part one, so we're keeping it cute and precise. πŸ’…


✨ NAME ✨

the whole cast, together at last β€” HTTP client low-level functions:

  • OSSL_HTTP_REQ_CTX
  • OSSL_HTTP_REQ_CTX_new
  • OSSL_HTTP_REQ_CTX_free
  • OSSL_HTTP_REQ_CTX_set_request_line
  • OSSL_HTTP_REQ_CTX_add1_header
  • OSSL_HTTP_REQ_CTX_set_expected
  • OSSL_HTTP_REQ_CTX_set1_req
  • OSSL_HTTP_REQ_CTX_nbio
  • OSSL_HTTP_REQ_CTX_nbio_d2i
  • OSSL_HTTP_REQ_CTX_exchange
  • OSSL_HTTP_REQ_CTX_get0_mem_bio
  • OSSL_HTTP_REQ_CTX_get_resp_len
  • OSSL_HTTP_REQ_CTX_set_max_response_length
  • OSSL_HTTP_REQ_CTX_set_max_response_hdr_lines
  • OSSL_HTTP_is_alive

and that's the squad: HTTP client low-level functions. periodt. 🫢


🌈 SYNOPSIS 🌈

here's the exact C interface, verbatim, because precision is the vibe, no cap:

        #include <openssl/http.h>

        typedef struct ossl_http_req_ctx_st OSSL_HTTP_REQ_CTX;

        OSSL_HTTP_REQ_CTX *OSSL_HTTP_REQ_CTX_new(BIO *wbio, BIO *rbio, int buf_size);
        void OSSL_HTTP_REQ_CTX_free(OSSL_HTTP_REQ_CTX *rctx);

        int OSSL_HTTP_REQ_CTX_set_request_line(OSSL_HTTP_REQ_CTX *rctx, int method_POST,
                                               const char *server, const char *port,
                                               const char *path);
        int OSSL_HTTP_REQ_CTX_add1_header(OSSL_HTTP_REQ_CTX *rctx,
                                          const char *name, const char *value);

        int OSSL_HTTP_REQ_CTX_set_expected(OSSL_HTTP_REQ_CTX *rctx,
                                           const char *expected_content_type, int expect_asn1,
                                           int timeout, int keep_alive);
        int OSSL_HTTP_REQ_CTX_set1_req(OSSL_HTTP_REQ_CTX *rctx, const char *content_type,
                                       const ASN1_ITEM *it, const ASN1_VALUE *req);
        int OSSL_HTTP_REQ_CTX_nbio(OSSL_HTTP_REQ_CTX *rctx);
        int OSSL_HTTP_REQ_CTX_nbio_d2i(OSSL_HTTP_REQ_CTX *rctx,
                                       ASN1_VALUE **pval, const ASN1_ITEM *it);
        BIO *OSSL_HTTP_REQ_CTX_exchange(OSSL_HTTP_REQ_CTX *rctx);

        BIO *OSSL_HTTP_REQ_CTX_get0_mem_bio(const OSSL_HTTP_REQ_CTX *rctx);
        size_t OSSL_HTTP_REQ_CTX_get_resp_len(const OSSL_HTTP_REQ_CTX *rctx);
        void OSSL_HTTP_REQ_CTX_set_max_response_length(OSSL_HTTP_REQ_CTX *rctx,
                                                       unsigned long len);
        void OSSL_HTTP_REQ_CTX_set_max_response_hdr_lines(OSSL_HTTP_REQ_CTX *rctx,
                                                          size_t count);

        int OSSL_HTTP_is_alive(const OSSL_HTTP_REQ_CTX *rctx);

✨ DESCRIPTION ✨

so OSSL_HTTP_REQ_CTX is, like, a context structure for one HTTP request + response pair πŸ’Œ β€” it's where you collect all the data you need to actually pull that request off.

ngl bestie, this whole file documents low-level HTTP functions that people rarely touch directly πŸ™ˆ. If you can, you want the high-level HTTP client functions like OSSL_HTTP_get(3) and OSSL_HTTP_transfer(3) instead. those are the main characters fr.


OSSL_HTTP_REQ_CTX_new() ✨ allocates a fresh HTTP request context structure, and it gets populated with:

  • the BIO to write/send the request to β€” that's wbio
  • the BIO to read/receive the response from β€” that's rbio, which may be equal to wbio
  • the maximum expected response header line length β€” that's buf_size

If buf_size is <= 0, that's your cue that the OSSL_HTTP_DEFAULT_MAX_LINE_LEN of 4KiB should be used. also, buf_size doubles as the number of content bytes read at a time πŸ’…. The allocated context also has an internal memory BIO chilling inside it, and that's what collects the HTTP request header lines.

OSSL_HTTP_REQ_CTX_free() 🫢 frees up the HTTP request context rctx. Important deets: the rbio is not free'd, but wbio will be free'd if free_wbio is set. and if the argument is NULL? nothing happens, no drama, we do nothing πŸ’€.

OSSL_HTTP_REQ_CTX_set_request_line() πŸ’– adds the 1st HTTP request line to rctx. Here's the breakdown:

  • The HTTP method is decided by method_POST β€” set it to 1 to mean "POST", or 0 to mean "GET".
  • server and port may be set to give the server and the optional port that an HTTP proxy shall forward the request to β€” otherwise they must be left NULL.
  • path gives the HTTP request path; if you leave it NULL, "/" is used.
  • for backward compatibility, path may begin with "http://", which conveys an absoluteURI. In that case it indicates HTTP proxy use and also provides the server (and optionally the port) that the proxy shall forward the request to πŸ›°οΈ. In this case the server and port arguments must be NULL.
🚨 and the server, port, and path arguments must not contain CR or LF characters. no exceptions bestie.

OSSL_HTTP_REQ_CTX_add1_header() 🌸 adds header name with value value to the context rctx. You can call it more than once to stack up multiple header lines πŸ’«. The name and value arguments must not contain CR or LF characters. so like, to add a "Host" header for "example.com" you'd call:

OSSL_HTTP_REQ_CTX_add1_header(ctx, "Host", "example.com");

OSSL_HTTP_REQ_CTX_set_expected() 🌈 optionally sets some expectations in rctx about what the HTTP client should get back in the response. because of how an HTTP request is structured, if the keep_alive argument is nonzero then this function must be used before calling OSSL_HTTP_REQ_CTX_set1_req() β€” order matters, iykyk.

  • If the expected_content_type argument is not NULL, the client will check β€” in a case-insensitive way β€” that the specified "Content-Type" string value is included in the HTTP header of the response, and it returns an error if it's not πŸ”. In the "Content-Type" header line, the specified string should be present either as a whole, or β€” if the specified string does not include a ";" character β€” it's enough that the specified string appears as a prefix in the header line, followed by a ";" character and any further text. so if expected_content_type says "text/html", that gets matched by "Text/HTML", "text/html; charset=UTF-8", etc. slay πŸ’…
  • If the expect_asn1 parameter is nonzero, a structure in ASN.1 encoding will be expected as the response content, and input streaming is disabled. Translation: an ASN.1 sequence header is required, its length field gets checked, and you should use OSSL_HTTP_REQ_CTX_get0_mem_bio() to get the buffered response. Otherwise (by default) any input format is allowed, with body length checks being performed on error messages only. In that case you should use the BIO given as the rbio argument to OSSL_HTTP_REQ_CTX_new() directly to read the response contents β€” and that one may support streaming 🌊.
  • If the timeout parameter is > 0, that's the maximum number of seconds the subsequent HTTP transfer (sending the request + receiving a response) is allowed to take ⏰. timeout == 0 enables waiting indefinitely, i.e. no timeout can occur β€” and this is the default. timeout < 0 takes over any value set via the overall_timeout argument of OSSL_HTTP_open(3), with the default being 0, which means no timeout.
  • If the keep_alive parameter is 0 β€” which, yes, is the default β€” the connection is not kept open after receiving a response. This is the default behavior for HTTP 1.0. If the value is 1 or 2 then a persistent connection is requested πŸ«‚. If the value is 2 then a persistent connection is required, i.e. an error occurs in case the server does not grant it. no takebacks.

OSSL_HTTP_REQ_CTX_set1_req() πŸŽ€ finalizes the HTTP request context. You need it if the method_POST parameter in the OSSL_HTTP_REQ_CTX_set_request_line() call was 1 and an ASN.1-encoded request should be sent. It also must be used when requesting "keep-alive" β€” even if a GET request is going to be sent, in which case req must be NULL. Unless req is NULL, the function adds the DER encoding of req using the ASN.1 template it to do the encoding (and note: that does not support streaming 😭). The HTTP header "Content-Length" gets filled out with the length of the request. content_type must be NULL if req is NULL. If content_type isn't NULL, the HTTP header "Content-Type" is also added with the given string value. The content_type argument must not contain CR or LF characters. and all these header lines go into the internal memory BIO for the request header πŸ“₯.

OSSL_HTTP_REQ_CTX_nbio() πŸ”₯ tries to send the request prepared in rctx and to gather the response via HTTP, using the wbio and rbio that were given when calling OSSL_HTTP_REQ_CTX_new(). The function may need to be called again if its result is -1, which indicates BIO_should_retry(3) πŸ”. In that case it's advisable to sleep a little in between, using BIO_wait(3) on the read BIO, to prevent a busy loop 😴. See OSSL_HTTP_REQ_CTX_set_expected() for how the response content type, the response body, the HTTP transfer timeout, and "keep-alive" are treated.

Any error message body is consumed if a "Content-Type" header is not included or its value starts with "text/". This is used for tracing the body contents if HTTP tracing is enabled πŸ‘€. And if the "Content-Length" header is present in the response and its value exceeds the maximum allowed response content length β€” or the response is an error message with its body length exceeding this value β€” or the content is an ASN.1-encoded structure with a length exceeding this value β€” or both length indications are present but disagree β€” then an error occurs. pick your poison, all of those end badly πŸ’€.

OSSL_HTTP_REQ_CTX_nbio_d2i() ✨ is like OSSL_HTTP_REQ_CTX_nbio() but on success it additionally parses the response, which must be a DER-encoded ASN.1 structure, using the ASN.1 template it, and places the result in *pval 🎁.

OSSL_HTTP_REQ_CTX_exchange() πŸ’– calls OSSL_HTTP_REQ_CTX_nbio() as often as needed in order to exchange a request and response, or until a timeout is reached ⏳. On success it returns a pointer to the BIO that can be used to read the result. If an ASN.1-encoded response was expected, this is the BIO returned by OSSL_HTTP_REQ_CTX_get0_mem_bio() when called after the exchange β€” and heads up, this memory BIO does not support streaming. Otherwise the returned BIO is the rbio given to OSSL_HTTP_REQ_CTX_new(), which may support streaming 🌊. When this BIO is returned, it has been read past the end of the response header, so the actual response body can be read from it.

🚨 The returned BIO pointer MUST NOT be freed by the caller. periodt.

OSSL_HTTP_REQ_CTX_get0_mem_bio() 🌸 returns the internal memory BIO. Before the HTTP request is sent, this could be used to adapt its header lines. Use with caution! ⚠️ After receiving a response via HTTP, the BIO represents the current state of reading the response header. If the response was expected to be ASN.1 encoded, its contents can be read via this BIO, which does not support streaming. The returned BIO pointer must not be freed by the caller 🫢.

OSSL_HTTP_REQ_CTX_get_resp_len() πŸ“ returns the size of the response contents in rctx if provided by the server as "Content-Length" header field, else 0.

OSSL_HTTP_REQ_CTX_set_max_response_length() πŸ”’ sets the maximum allowed response content length for rctx to len. If not set, or if len is 0, then the OSSL_HTTP_DEFAULT_MAX_RESP_LEN is used, which currently is 100 KiB.

OSSL_HTTP_REQ_CTX_set_max_response_hdr_lines() 🧾 changes the limit for the number of HTTP header lines allowed to be received in a response. The default limit is OSSL_HTTP_DEFAULT_MAX_RESP_HDR_LINES, currently 256. If the limit is not 0 and the number of lines exceeds the limit, then the HTTP_R_RESPONSE_TOO_MANY_HDRLINES error is indicated 🚩. Setting the limit to 0 disables the check entirely, yolo mode.

OSSL_HTTP_is_alive() πŸ’“ can be used to query if the HTTP connection given by rctx is still alive, i.e. has not been closed. It returns 0 if rctx is NULL.

If the client application requested or required a persistent connection and the server granted it, it can keep rctx as long as it wants to send further requests β€” and as long as OSSL_HTTP_is_alive() returns nonzero β€” else it should call OSSL_HTTP_REQ_CTX_free(rctx) or OSSL_HTTP_close(3) 🧹. And in case the client application keeps rctx but the connection then dies for any reason at the server side, it will notice this by obtaining an I/O error when trying to send the next request via rctx πŸ’€. rip.


⚠️ WARNINGS ⚠️

The server's response may be unexpected if the hostname that was used to create the wbio, any "Host" header, and the host specified in the request URL do not match 😬.

Many of these functions must be called in a certain order. no freestyling.

  1. First, the HTTP request context must be allocated: OSSL_HTTP_REQ_CTX_new() 🌱
  2. Then, the HTTP request must be prepared with request data:
    1. Calling OSSL_HTTP_REQ_CTX_set_request_line() ✍️
    2. Adding extra header lines with OSSL_HTTP_REQ_CTX_add1_header(). This is optional and may be done multiple times with different names πŸ’«
    3. Finalize the request using OSSL_HTTP_REQ_CTX_set1_req(). This may be omitted if the GET method is used and "keep-alive" is not requested βœ…

When the request context is fully prepared, the HTTP exchange may be performed with OSSL_HTTP_REQ_CTX_nbio() or OSSL_HTTP_REQ_CTX_exchange() πŸš€.


πŸ“ NOTES πŸ“

When built with tracing enabled, OSSL_HTTP_REQ_CTX_nbio() and all functions using it β€” such as OSSL_HTTP_REQ_CTX_exchange() and OSSL_HTTP_transfer(3) β€” may be traced using OSSL_TRACE_CATEGORY_HTTP πŸ”.

See also OSSL_trace_enabled(3) and openssl-env(7). ✨

πŸ’– RETURN VALUES πŸ’–

okay bestie, here's the receipts section, like what do you actually get back from each of these babies:

  • OSSL_HTTP_REQ_CTX_new() β€” hands you a pointer to an OSSL_HTTP_REQ_CTX, or NULL if something went wrong. no context for you, sorry bestie 😭
  • OSSL_HTTP_REQ_CTX_free(), OSSL_HTTP_REQ_CTX_set_max_response_length(), and OSSL_HTTP_REQ_CTX_set_max_response_hdr_lines() β€” these just do their thing and give you nothing back. no return value, no notes, they're the strong silent type πŸ’…
  • OSSL_HTTP_REQ_CTX_set_request_line(), OSSL_HTTP_REQ_CTX_add1_header(), OSSL_HTTP_REQ_CTX_set1_req(), and OSSL_HTTP_REQ_CTX_set_expected() β€” these serve 1 for success and 0 for failure. simple as that, periodt ✨
  • OSSL_HTTP_REQ_CTX_nbio() and OSSL_HTTP_REQ_CTX_nbio_d2i() β€” they give you 1 for success, 0 on error or redirection, and -1 if a retry is needed. three whole moods, main character energy fr fr
  • OSSL_HTTP_REQ_CTX_exchange() and OSSL_HTTP_REQ_CTX_get0_mem_bio() β€” on success they return a pointer to a BIO like described up above, or NULL on failure.
🚨 IMPORTANT bestie!!! The returned BIO must not be freed by the caller. do NOT touch it, do NOT free it, hands off, it's not yours πŸ™ˆ
  • OSSL_HTTP_REQ_CTX_get_resp_len() β€” returns the size of the response contents, or 0 if that's just not available, or if an error happened. zero of mystery, lowkey iconic 🌸
  • OSSL_HTTP_is_alive() β€” returns 1 if its argument is non-NULL and the client requested a persistent connection and the server did not disagree on keeping the connection open, else 0. it's giving "are we still talking?" vibe check πŸ¦‹

πŸ‘€ SEE ALSO πŸ‘€

go read these next, they're all connected, iykyk:


🌈 HISTORY 🌈

the lore drop:

  • OSSL_HTTP_REQ_CTX_set_max_response_hdr_lines() was added in OpenSSL 3.3. baby of the family πŸŽ€
  • Every other function described here was added in OpenSSL 3.0. the OGs, we stan πŸ‘‘

Copyright 2015-2026 The OpenSSL Project Authors. All Rights Reserved.

Licensed under the Apache License 2.0 (the "License"). You may not use this file except in compliance with the License. You can obtain a copy in the file LICENSE in the source distribution or at <https://www.openssl.org/source/license.html>.


and that's the whole page bestie, we ate and left no crumbs 🫢✨ this was from OpenSSL docs, version 4.0.3, dated 2026-09-29 β€” so if you're reading this in some other timeline, double check your version, okay?? mwah πŸ’‹

πŸ“¦ where this page comes from