@@ -154,6 +154,70 @@ static int init_interp_state(asgi_interp_state_t *state) {
154154 state -> empty_bytes = PyBytes_FromStringAndSize ("" , 0 );
155155 if (!state -> empty_bytes ) return -1 ;
156156
157+ /* Pre-interned header names (bytes) for common HTTP headers */
158+ state -> header_host = PyBytes_FromStringAndSize ("host" , 4 );
159+ if (!state -> header_host ) return -1 ;
160+ state -> header_accept = PyBytes_FromStringAndSize ("accept" , 6 );
161+ if (!state -> header_accept ) return -1 ;
162+ state -> header_content_type = PyBytes_FromStringAndSize ("content-type" , 12 );
163+ if (!state -> header_content_type ) return -1 ;
164+ state -> header_content_length = PyBytes_FromStringAndSize ("content-length" , 14 );
165+ if (!state -> header_content_length ) return -1 ;
166+ state -> header_user_agent = PyBytes_FromStringAndSize ("user-agent" , 10 );
167+ if (!state -> header_user_agent ) return -1 ;
168+ state -> header_cookie = PyBytes_FromStringAndSize ("cookie" , 6 );
169+ if (!state -> header_cookie ) return -1 ;
170+ state -> header_authorization = PyBytes_FromStringAndSize ("authorization" , 13 );
171+ if (!state -> header_authorization ) return -1 ;
172+ state -> header_cache_control = PyBytes_FromStringAndSize ("cache-control" , 13 );
173+ if (!state -> header_cache_control ) return -1 ;
174+ state -> header_connection = PyBytes_FromStringAndSize ("connection" , 10 );
175+ if (!state -> header_connection ) return -1 ;
176+ state -> header_accept_encoding = PyBytes_FromStringAndSize ("accept-encoding" , 15 );
177+ if (!state -> header_accept_encoding ) return -1 ;
178+ state -> header_accept_language = PyBytes_FromStringAndSize ("accept-language" , 15 );
179+ if (!state -> header_accept_language ) return -1 ;
180+ state -> header_referer = PyBytes_FromStringAndSize ("referer" , 7 );
181+ if (!state -> header_referer ) return -1 ;
182+ state -> header_origin = PyBytes_FromStringAndSize ("origin" , 6 );
183+ if (!state -> header_origin ) return -1 ;
184+ state -> header_if_none_match = PyBytes_FromStringAndSize ("if-none-match" , 13 );
185+ if (!state -> header_if_none_match ) return -1 ;
186+ state -> header_if_modified_since = PyBytes_FromStringAndSize ("if-modified-since" , 17 );
187+ if (!state -> header_if_modified_since ) return -1 ;
188+ state -> header_x_forwarded_for = PyBytes_FromStringAndSize ("x-forwarded-for" , 15 );
189+ if (!state -> header_x_forwarded_for ) return -1 ;
190+
191+ /* Cached HTTP status code integers */
192+ state -> status_200 = PyLong_FromLong (200 );
193+ if (!state -> status_200 ) return -1 ;
194+ state -> status_201 = PyLong_FromLong (201 );
195+ if (!state -> status_201 ) return -1 ;
196+ state -> status_204 = PyLong_FromLong (204 );
197+ if (!state -> status_204 ) return -1 ;
198+ state -> status_301 = PyLong_FromLong (301 );
199+ if (!state -> status_301 ) return -1 ;
200+ state -> status_302 = PyLong_FromLong (302 );
201+ if (!state -> status_302 ) return -1 ;
202+ state -> status_304 = PyLong_FromLong (304 );
203+ if (!state -> status_304 ) return -1 ;
204+ state -> status_400 = PyLong_FromLong (400 );
205+ if (!state -> status_400 ) return -1 ;
206+ state -> status_401 = PyLong_FromLong (401 );
207+ if (!state -> status_401 ) return -1 ;
208+ state -> status_403 = PyLong_FromLong (403 );
209+ if (!state -> status_403 ) return -1 ;
210+ state -> status_404 = PyLong_FromLong (404 );
211+ if (!state -> status_404 ) return -1 ;
212+ state -> status_405 = PyLong_FromLong (405 );
213+ if (!state -> status_405 ) return -1 ;
214+ state -> status_500 = PyLong_FromLong (500 );
215+ if (!state -> status_500 ) return -1 ;
216+ state -> status_502 = PyLong_FromLong (502 );
217+ if (!state -> status_502 ) return -1 ;
218+ state -> status_503 = PyLong_FromLong (503 );
219+ if (!state -> status_503 ) return -1 ;
220+
157221 /* Build ASGI subdict: {"version": "3.0", "spec_version": "2.3"} */
158222 state -> asgi_subdict = PyDict_New ();
159223 if (!state -> asgi_subdict ) return -1 ;
@@ -225,6 +289,40 @@ static void cleanup_interp_state(asgi_interp_state_t *state) {
225289 Py_XDECREF (state -> empty_string );
226290 Py_XDECREF (state -> empty_bytes );
227291
292+ /* Clean up pre-interned header names */
293+ Py_XDECREF (state -> header_host );
294+ Py_XDECREF (state -> header_accept );
295+ Py_XDECREF (state -> header_content_type );
296+ Py_XDECREF (state -> header_content_length );
297+ Py_XDECREF (state -> header_user_agent );
298+ Py_XDECREF (state -> header_cookie );
299+ Py_XDECREF (state -> header_authorization );
300+ Py_XDECREF (state -> header_cache_control );
301+ Py_XDECREF (state -> header_connection );
302+ Py_XDECREF (state -> header_accept_encoding );
303+ Py_XDECREF (state -> header_accept_language );
304+ Py_XDECREF (state -> header_referer );
305+ Py_XDECREF (state -> header_origin );
306+ Py_XDECREF (state -> header_if_none_match );
307+ Py_XDECREF (state -> header_if_modified_since );
308+ Py_XDECREF (state -> header_x_forwarded_for );
309+
310+ /* Clean up cached status codes */
311+ Py_XDECREF (state -> status_200 );
312+ Py_XDECREF (state -> status_201 );
313+ Py_XDECREF (state -> status_204 );
314+ Py_XDECREF (state -> status_301 );
315+ Py_XDECREF (state -> status_302 );
316+ Py_XDECREF (state -> status_304 );
317+ Py_XDECREF (state -> status_400 );
318+ Py_XDECREF (state -> status_401 );
319+ Py_XDECREF (state -> status_403 );
320+ Py_XDECREF (state -> status_404 );
321+ Py_XDECREF (state -> status_405 );
322+ Py_XDECREF (state -> status_500 );
323+ Py_XDECREF (state -> status_502 );
324+ Py_XDECREF (state -> status_503 );
325+
228326 state -> initialized = false;
229327}
230328
@@ -562,6 +660,103 @@ static PyObject *asgi_get_scheme(int scheme) {
562660 }
563661}
564662
663+ /**
664+ * @brief Get cached header name or create new bytes object
665+ *
666+ * Uses length-based dispatch for efficient lookup of common HTTP header names.
667+ * Returns a new reference (either Py_INCREF'd cached value or new PyBytes).
668+ */
669+ static PyObject * get_cached_header_name (asgi_interp_state_t * state ,
670+ const unsigned char * name , size_t len ) {
671+ switch (len ) {
672+ case 4 :
673+ if (memcmp (name , "host" , 4 ) == 0 ) {
674+ Py_INCREF (state -> header_host );
675+ return state -> header_host ;
676+ }
677+ break ;
678+ case 6 :
679+ if (memcmp (name , "accept" , 6 ) == 0 ) {
680+ Py_INCREF (state -> header_accept );
681+ return state -> header_accept ;
682+ }
683+ if (memcmp (name , "cookie" , 6 ) == 0 ) {
684+ Py_INCREF (state -> header_cookie );
685+ return state -> header_cookie ;
686+ }
687+ if (memcmp (name , "origin" , 6 ) == 0 ) {
688+ Py_INCREF (state -> header_origin );
689+ return state -> header_origin ;
690+ }
691+ break ;
692+ case 7 :
693+ if (memcmp (name , "referer" , 7 ) == 0 ) {
694+ Py_INCREF (state -> header_referer );
695+ return state -> header_referer ;
696+ }
697+ break ;
698+ case 10 :
699+ if (memcmp (name , "user-agent" , 10 ) == 0 ) {
700+ Py_INCREF (state -> header_user_agent );
701+ return state -> header_user_agent ;
702+ }
703+ if (memcmp (name , "connection" , 10 ) == 0 ) {
704+ Py_INCREF (state -> header_connection );
705+ return state -> header_connection ;
706+ }
707+ break ;
708+ case 12 :
709+ if (memcmp (name , "content-type" , 12 ) == 0 ) {
710+ Py_INCREF (state -> header_content_type );
711+ return state -> header_content_type ;
712+ }
713+ break ;
714+ case 13 :
715+ if (memcmp (name , "authorization" , 13 ) == 0 ) {
716+ Py_INCREF (state -> header_authorization );
717+ return state -> header_authorization ;
718+ }
719+ if (memcmp (name , "cache-control" , 13 ) == 0 ) {
720+ Py_INCREF (state -> header_cache_control );
721+ return state -> header_cache_control ;
722+ }
723+ if (memcmp (name , "if-none-match" , 13 ) == 0 ) {
724+ Py_INCREF (state -> header_if_none_match );
725+ return state -> header_if_none_match ;
726+ }
727+ break ;
728+ case 14 :
729+ if (memcmp (name , "content-length" , 14 ) == 0 ) {
730+ Py_INCREF (state -> header_content_length );
731+ return state -> header_content_length ;
732+ }
733+ break ;
734+ case 15 :
735+ if (memcmp (name , "accept-encoding" , 15 ) == 0 ) {
736+ Py_INCREF (state -> header_accept_encoding );
737+ return state -> header_accept_encoding ;
738+ }
739+ if (memcmp (name , "accept-language" , 15 ) == 0 ) {
740+ Py_INCREF (state -> header_accept_language );
741+ return state -> header_accept_language ;
742+ }
743+ if (memcmp (name , "x-forwarded-for" , 15 ) == 0 ) {
744+ Py_INCREF (state -> header_x_forwarded_for );
745+ return state -> header_x_forwarded_for ;
746+ }
747+ break ;
748+ case 17 :
749+ if (memcmp (name , "if-modified-since" , 17 ) == 0 ) {
750+ Py_INCREF (state -> header_if_modified_since );
751+ return state -> header_if_modified_since ;
752+ }
753+ break ;
754+ }
755+
756+ /* Uncommon header - create new bytes object */
757+ return PyBytes_FromStringAndSize ((char * )name , len );
758+ }
759+
565760/* ============================================================================
566761 * Response Pool Functions
567762 * ============================================================================ */
@@ -796,6 +991,8 @@ static PyObject *asgi_build_scope(const asgi_scope_data_t *data) {
796991 Py_DECREF (root_path );
797992
798993 /* headers: list of [name, value] pairs (both bytes) */
994+ /* Use cached header names for common headers */
995+ asgi_interp_state_t * state = get_asgi_interp_state ();
799996 PyObject * headers = PyList_New (data -> headers_count );
800997 if (headers == NULL ) {
801998 goto error ;
@@ -807,8 +1004,8 @@ static PyObject *asgi_build_scope(const asgi_scope_data_t *data) {
8071004 goto error ;
8081005 }
8091006
810- PyObject * name = PyBytes_FromStringAndSize (
811- ( char * ) data -> headers [i ].name , data -> headers [i ].name_len );
1007+ PyObject * name = get_cached_header_name (
1008+ state , data -> headers [i ].name , data -> headers [i ].name_len );
8121009 PyObject * value = PyBytes_FromStringAndSize (
8131010 (char * )data -> headers [i ].value , data -> headers [i ].value_len );
8141011
@@ -1159,8 +1356,10 @@ static PyObject *asgi_scope_from_map(ErlNifEnv *env, ERL_NIF_TERM scope_map) {
11591356 }
11601357
11611358 /* Create tuple(bytes, bytes) per ASGI spec */
1162- PyObject * py_name = PyBytes_FromStringAndSize (
1163- (char * )name_bin .data , name_bin .size );
1359+ /* Use cached header name for common headers */
1360+ asgi_interp_state_t * state = get_asgi_interp_state ();
1361+ PyObject * py_name = get_cached_header_name (
1362+ state , name_bin .data , name_bin .size );
11641363 PyObject * py_hvalue = PyBytes_FromStringAndSize (
11651364 (char * )value_bin .data , value_bin .size );
11661365
@@ -1222,6 +1421,100 @@ static PyObject *asgi_scope_from_map(ErlNifEnv *env, ERL_NIF_TERM scope_map) {
12221421 return scope ;
12231422}
12241423
1424+ /* ============================================================================
1425+ * Direct Response Extraction
1426+ * ============================================================================ */
1427+
1428+ /**
1429+ * @brief Extract ASGI response tuple directly to Erlang terms
1430+ *
1431+ * Optimized response conversion that directly extracts (status, headers, body)
1432+ * tuple elements without going through generic py_to_term(). Falls back to
1433+ * py_to_term() for non-standard responses.
1434+ *
1435+ * Expected Python format: tuple(int, list[tuple[bytes, bytes]], bytes)
1436+ * Output Erlang format: {Status, [{Header, Value}, ...], Body}
1437+ */
1438+ static ERL_NIF_TERM extract_asgi_response (ErlNifEnv * env , PyObject * result ) {
1439+ /* Validate 3-element tuple, fallback to py_to_term if not */
1440+ if (!PyTuple_Check (result ) || PyTuple_Size (result ) != 3 ) {
1441+ return py_to_term (env , result );
1442+ }
1443+
1444+ /* Get tuple elements (borrowed references) */
1445+ PyObject * py_status = PyTuple_GET_ITEM (result , 0 );
1446+ PyObject * py_headers = PyTuple_GET_ITEM (result , 1 );
1447+ PyObject * py_body = PyTuple_GET_ITEM (result , 2 );
1448+
1449+ /* Validate types */
1450+ if (!PyLong_Check (py_status ) || !PyList_Check (py_headers ) || !PyBytes_Check (py_body )) {
1451+ return py_to_term (env , result );
1452+ }
1453+
1454+ /* Extract status code directly */
1455+ long status = PyLong_AsLong (py_status );
1456+ if (status == -1 && PyErr_Occurred ()) {
1457+ PyErr_Clear ();
1458+ return py_to_term (env , result );
1459+ }
1460+ ERL_NIF_TERM erl_status = enif_make_int (env , (int )status );
1461+
1462+ /* Extract headers list - iterate backwards for efficient cons-cell building */
1463+ Py_ssize_t headers_len = PyList_Size (py_headers );
1464+ ERL_NIF_TERM erl_headers = enif_make_list (env , 0 ); /* Start with empty list */
1465+
1466+ for (Py_ssize_t i = headers_len - 1 ; i >= 0 ; i -- ) {
1467+ PyObject * header_item = PyList_GET_ITEM (py_headers , i );
1468+
1469+ /* Each header should be a 2-element tuple/list of bytes */
1470+ PyObject * py_name = NULL ;
1471+ PyObject * py_value = NULL ;
1472+
1473+ if (PyTuple_Check (header_item ) && PyTuple_Size (header_item ) == 2 ) {
1474+ py_name = PyTuple_GET_ITEM (header_item , 0 );
1475+ py_value = PyTuple_GET_ITEM (header_item , 1 );
1476+ } else if (PyList_Check (header_item ) && PyList_Size (header_item ) == 2 ) {
1477+ py_name = PyList_GET_ITEM (header_item , 0 );
1478+ py_value = PyList_GET_ITEM (header_item , 1 );
1479+ } else {
1480+ /* Invalid header format, fallback */
1481+ return py_to_term (env , result );
1482+ }
1483+
1484+ /* Both name and value must be bytes */
1485+ if (!PyBytes_Check (py_name ) || !PyBytes_Check (py_value )) {
1486+ return py_to_term (env , result );
1487+ }
1488+
1489+ /* Convert header name */
1490+ char * name_data = PyBytes_AS_STRING (py_name );
1491+ Py_ssize_t name_len = PyBytes_GET_SIZE (py_name );
1492+ ERL_NIF_TERM erl_name ;
1493+ unsigned char * name_buf = enif_make_new_binary (env , name_len , & erl_name );
1494+ memcpy (name_buf , name_data , name_len );
1495+
1496+ /* Convert header value */
1497+ char * value_data = PyBytes_AS_STRING (py_value );
1498+ Py_ssize_t value_len = PyBytes_GET_SIZE (py_value );
1499+ ERL_NIF_TERM erl_value ;
1500+ unsigned char * value_buf = enif_make_new_binary (env , value_len , & erl_value );
1501+ memcpy (value_buf , value_data , value_len );
1502+
1503+ /* Create header tuple and prepend to list */
1504+ ERL_NIF_TERM header_tuple = enif_make_tuple2 (env , erl_name , erl_value );
1505+ erl_headers = enif_make_list_cell (env , header_tuple , erl_headers );
1506+ }
1507+
1508+ /* Extract body directly */
1509+ char * body_data = PyBytes_AS_STRING (py_body );
1510+ Py_ssize_t body_len = PyBytes_GET_SIZE (py_body );
1511+ ERL_NIF_TERM erl_body ;
1512+ unsigned char * body_buf = enif_make_new_binary (env , body_len , & erl_body );
1513+ memcpy (body_buf , body_data , body_len );
1514+
1515+ return enif_make_tuple3 (env , erl_status , erl_headers , erl_body );
1516+ }
1517+
12251518/* ============================================================================
12261519 * NIF Functions
12271520 * ============================================================================ */
@@ -1379,8 +1672,8 @@ static ERL_NIF_TERM nif_asgi_run(ErlNifEnv *env, int argc, const ERL_NIF_TERM ar
13791672 goto cleanup ;
13801673 }
13811674
1382- /* Convert result to Erlang term */
1383- ERL_NIF_TERM term_result = py_to_term (env , run_result );
1675+ /* Convert result to Erlang term using optimized extraction */
1676+ ERL_NIF_TERM term_result = extract_asgi_response (env , run_result );
13841677 Py_DECREF (run_result );
13851678
13861679 result = enif_make_tuple2 (env , ATOM_OK , term_result );
0 commit comments