From 8c5540c7187f28b1cf838e0567f34ca5bb6547ce Mon Sep 17 00:00:00 2001 From: NathanFreeman <1056159381@qq.com> Date: Thu, 15 Feb 2024 16:01:13 +0800 Subject: [PATCH 1/8] optimize http server --- ext-src/php_swoole_http.h | 164 +++++++++++++++++++++++++- ext-src/php_swoole_private.h | 16 ++- ext-src/swoole_http2_server.cc | 8 +- ext-src/swoole_http_response.cc | 198 ++++++++++++-------------------- include/swoole_config.h | 4 +- 5 files changed, 250 insertions(+), 140 deletions(-) diff --git a/ext-src/php_swoole_http.h b/ext-src/php_swoole_http.h index 71a7613fcf0..18e001409dc 100644 --- a/ext-src/php_swoole_http.h +++ b/ext-src/php_swoole_http.h @@ -114,6 +114,165 @@ struct Response { zval _ztrailer; }; +struct Header { + const char *key = nullptr; + const char *value = nullptr; + Header *next = nullptr; + size_t length = 0; +}; + +struct Status { + const char *protocol = nullptr; + const char *status = nullptr; + const char *reason = nullptr; + Header *next = nullptr; + size_t length = 0; +}; + +struct ByteBuffer { + Status *start = nullptr; + Header *current = nullptr; + size_t total = 0; + + inline void add_status(int status, const char *reason) { + char buf[16]; + int length = swoole_itoa(buf, status); + buf[length] = '\0'; + add_status(buf, reason); + } + + inline void add_status(const char *status, const char *reason) { + start = (Status *) emalloc(sizeof(Status)); + start->protocol = "HTTP/1.1 "; + start->status = status; + start->reason = reason; + start->next = nullptr; + + // calculate http status line length + if (!reason) { + start->length = strlen(start->protocol) + strlen(status) + SW_CRLF_LEN; + } else { + start->length = strlen(start->protocol) + strlen(status) + strlen(status) + SW_CRLF_LEN + 1; + } + } + + inline void add_header(const char *key, size_t key_length, const char *value, size_t value_length) { + Header *header = (Header *) emalloc(sizeof(Header)); + header->key = key; + header->value = value; + header->next = nullptr; + // calculate http response header length, 2 => strlen(": ") + header->length = key_length + value_length + SW_CRLF_LEN + 2; + + if (current) { + current->next = header; + } else { + start->next = header; + } + current = header; + } + + inline size_t get_protocol_length(size_t length = 0) { + // calculate http protocol length + total = length + start->length + SW_CRLF_LEN; + Header *header = start->next; + + while (header) { + total += header->length; + header = header->next; + } + + return total; + } + + inline void write_protocol(String *http_buffer, const char *data, size_t length) { + http_buffer->append(start->protocol, strlen(start->protocol)); + http_buffer->append(start->status, strlen(start->status)); + if (start->reason) { + http_buffer->append(" ", 1); + http_buffer->append(start->reason, strlen(start->reason)); + } + http_buffer->append(SW_CRLF, SW_CRLF_LEN); + + size_t key_length = 0; + size_t value_length = 0; + Header *header = start->next; + while (header) { + key_length = strlen(header->key); + value_length = strlen(header->value); + + if (SW_STRCASEEQ(header->key, key_length, "Content-Type")) { + if (SW_STRCASEEQ(header->value, value_length, SW_HTTP_TEXT_PLAIN)) { + http_buffer->append(ZEND_STRL("Content-Type: " SW_HTTP_TEXT_PLAIN "\r\n")); + header = header->next; + continue; + } + + if (SW_STRCASEEQ(header->value, value_length, SW_HTTP_DEFAULT_CONTENT_TYPE)) { + http_buffer->append(ZEND_STRL("Content-Type: " SW_HTTP_DEFAULT_CONTENT_TYPE "\r\n")); + header = header->next; + continue; + } + + if (SW_STRCASEEQ(header->value, value_length, SW_HTTP_APPLICATION_JSON)) { + http_buffer->append(ZEND_STRL("Content-Type: " SW_HTTP_APPLICATION_JSON "\r\n")); + header = header->next; + continue; + } + } + + if (SW_STRCASEEQ(header->key, key_length, "Server") && + SW_STRCASEEQ(header->value, value_length, SW_HTTP_SERVER_SOFTWARE)) { + http_buffer->append(ZEND_STRL("Server: " SW_HTTP_SERVER_SOFTWARE "\r\n")); + header = header->next; + continue; + } + + if (SW_STRCASEEQ(header->key, key_length, "Transfer-Encoding") && + SW_STRCASEEQ(header->value, value_length, "chunked")) { + http_buffer->append(ZEND_STRL("Transfer-Encoding: chunked\r\n")); + header = header->next; + continue; + } + + if (SW_STRCASEEQ(header->key, key_length, "Connection")) { + if (SW_STRCASEEQ(header->value, value_length, "keep-alive")) { + http_buffer->append(ZEND_STRL("Connection: keep-alive\r\n")); + } else { + http_buffer->append(ZEND_STRL("Connection: close\r\n")); + } + header = header->next; + continue; + } + + http_buffer->append(header->key, key_length); + http_buffer->append(ZEND_STRL(": ")); + http_buffer->append(header->value, value_length); + http_buffer->append(SW_CRLF, SW_CRLF_LEN); + header = header->next; + } + + http_buffer->append(SW_CRLF, SW_CRLF_LEN); + + if (data) { + http_buffer->append(data, length); + } + + assert(http_buffer->length == total); + } + + ~ByteBuffer() { + Header *header = nullptr; + while (start->next) { + header = start->next; + start->next = header->next; + efree(header); + } + + efree(start); + } +}; + struct Context { SessionId fd; uchar completed : 1; @@ -192,8 +351,8 @@ struct Context { void end(zval *zdata, zval *return_value); bool send_file(const char *file, uint32_t l_file, off_t offset, size_t length); void send_trailer(zval *return_value); - String *get_write_buffer(); - void build_header(String *http_buffer, const char *body, size_t length); + String *get_write_buffer(size_t capacity = SW_BUFFER_SIZE_STD); + void build_header(const char *body, size_t length); ssize_t build_trailer(String *http_buffer); size_t get_content_length() { @@ -212,7 +371,6 @@ struct Context { bool is_available(); void free(); }; - } // namespace http namespace http2 { diff --git a/ext-src/php_swoole_private.h b/ext-src/php_swoole_private.h index c762021e964..c6f4d65115b 100644 --- a/ext-src/php_swoole_private.h +++ b/ext-src/php_swoole_private.h @@ -705,11 +705,12 @@ static sw_inline void sw_zend_update_property_null_ex(zend_class_entry *scope, z zend_update_property_ex(scope, SW_Z8_OBJ_P(object), s, &tmp); } -static sw_inline zval *sw_zend_read_property_ex(zend_class_entry *ce, zval *obj, zend_string *s, int silent) { - zval rv, *property = zend_read_property_ex(ce, SW_Z8_OBJ_P(obj), s, silent, &rv); +static sw_inline zval *sw_zend_read_property_ex(zend_class_entry *ce, zval *zobject, zend_string *name, int silent) { + zval *zv = zend_hash_find(&ce->properties_info, name); + zend_property_info *property_info = (zend_property_info *) Z_PTR_P(zv); + zval *property = OBJ_PROP(SW_Z8_OBJ_P(zobject), property_info->offset); if (UNEXPECTED(property == &EG(uninitialized_zval))) { - sw_zend_update_property_null_ex(ce, obj, s); - return zend_read_property_ex(ce, SW_Z8_OBJ_P(obj), s, silent, &rv); + ZVAL_NULL(property); } return property; } @@ -942,11 +943,8 @@ static sw_inline int php_swoole_check_reactor() { } } -static sw_inline char *php_swoole_format_date(char *format, size_t format_len, time_t ts, int localtime) { - zend_string *time = php_format_date(format, format_len, ts, localtime); - char *return_str = estrndup(ZSTR_VAL(time), ZSTR_LEN(time)); - zend_string_release(time); - return return_str; +static sw_inline zend_string *php_swoole_format_date(char *format, size_t format_len, time_t ts, int localtime) { + return php_format_date(format, format_len, ts, localtime); } static sw_inline char *php_swoole_url_encode(const char *value, size_t value_len, size_t *exten) { diff --git a/ext-src/swoole_http2_server.cc b/ext-src/swoole_http2_server.cc index 37acbbecdf3..3288677ca6e 100644 --- a/ext-src/swoole_http2_server.cc +++ b/ext-src/swoole_http2_server.cc @@ -306,11 +306,11 @@ static void http2_server_set_date_header(Http2::HeaderSet *headers) { time_t now = time(nullptr); if (now != cache.time) { - char *date_str = php_swoole_format_date((char *) ZEND_STRL(SW_HTTP_DATE_FORMAT), now, 0); - cache.len = strlen(date_str); - memcpy(cache.buf, date_str, cache.len); + zend_string *date = php_swoole_format_date((char *) ZEND_STRL(SW_HTTP_DATE_FORMAT), now, 0); + memcpy(cache.buf, ZSTR_VAL(date), ZSTR_LEN(date)); + cache.len = ZSTR_LEN(date); cache.time = now; - efree(date_str); + zend_string_release(date); } headers->add(ZEND_STRL("date"), cache.buf, cache.len); } diff --git a/ext-src/swoole_http_response.cc b/ext-src/swoole_http_response.cc index 9eb633d5e22..aa1730c83e3 100644 --- a/ext-src/swoole_http_response.cc +++ b/ext-src/swoole_http_response.cc @@ -38,6 +38,7 @@ using swoole::coroutine::Socket; using HttpResponse = swoole::http::Response; using HttpContext = swoole::http::Context; +using HttpByteBuffer = swoole::http::ByteBuffer; namespace WebSocket = swoole::websocket; namespace HttpServer = swoole::http_server; @@ -63,12 +64,12 @@ static inline void http_header_key_format(char *key, int length) { } } -String *HttpContext::get_write_buffer() { +String *HttpContext::get_write_buffer(size_t capacity) { if (co_socket) { return ((Socket *) private_data)->get_write_buffer(); } else { if (!write_buffer) { - write_buffer = new String(SW_BUFFER_SIZE_STD, sw_php_allocator()); + write_buffer = new String(capacity, sw_php_allocator()); } return write_buffer; } @@ -223,13 +224,10 @@ static PHP_METHOD(swoole_http_response, write) { #ifdef SW_HAVE_COMPRESSION ctx->accept_compression = 0; #endif - String *http_buffer = ctx->get_write_buffer(); - if (!ctx->send_header_) { ctx->send_chunked = 1; - http_buffer->clear(); - ctx->build_header(http_buffer, nullptr, 0); + ctx->build_header(nullptr, 0); if (!ctx->send(ctx, http_buffer->str, http_buffer->length)) { ctx->send_chunked = 0; ctx->send_header_ = 0; @@ -288,7 +286,7 @@ static int parse_header_name(const char *key, size_t keylen) { return 0; } -static void http_set_date_header(String *response) { +static void http_set_date_header(HttpByteBuffer *http_byte_buffer) { static struct { time_t time; size_t len; @@ -297,18 +295,16 @@ static void http_set_date_header(String *response) { time_t now = time(nullptr); if (now != cache.time) { - char *date_str = php_swoole_format_date((char *) ZEND_STRL(SW_HTTP_DATE_FORMAT), now, 0); - cache.len = strlen(date_str); - memcpy(cache.buf, date_str, cache.len); - efree(date_str); + zend_string *date = php_swoole_format_date((char *) ZEND_STRL(SW_HTTP_DATE_FORMAT), now, 0); + memcpy(cache.buf, ZSTR_VAL(date), ZSTR_LEN(date)); + cache.len = ZSTR_LEN(date); cache.time = now; + zend_string_release(date); } - response->append(ZEND_STRL("Date: ")); - response->append(cache.buf, cache.len); - response->append(ZEND_STRL("\r\n")); + http_byte_buffer->add_header(ZEND_STRL("Date"), cache.buf, cache.len); } -static void add_custom_header(String *response, const char *key, size_t l_key, zval *value) { +static void add_custom_header(HttpByteBuffer *http_byte_buffer, const char *key, size_t l_key, zval *value) { if (ZVAL_IS_NULL(value)) { return; } @@ -317,36 +313,21 @@ static void add_custom_header(String *response, const char *key, size_t l_key, z if (swoole_http_has_crlf(str_value.val(), str_value.len())) { return; } - response->append(key, l_key); - response->append(SW_STRL(": ")); - response->append(str_value.val(), str_value.len()); - response->append(SW_STRL("\r\n")); + + http_byte_buffer->add_header(key, l_key, str_value.val(), str_value.len()); } -void HttpContext::build_header(String *http_buffer, const char *body, size_t length) { +void HttpContext::build_header(const char *body, size_t length) { assert(send_header_ == 0); + ByteBuffer http_byte_buffer; + String *http_buffer = nullptr; - /** - * http status line - */ - if (!response.reason) { - const char *status = HttpServer::get_status_message(response.status); - http_buffer->append(ZEND_STRL("HTTP/1.1 ")); - http_buffer->append((char *) status, strlen(status)); - http_buffer->append(ZEND_STRL("\r\n")); - } else { - http_buffer->append(ZEND_STRL("HTTP/1.1 ")); - http_buffer->append(response.status); - http_buffer->append(ZEND_STRL(" ")); - http_buffer->append(response.reason, strlen(response.reason)); - http_buffer->append(ZEND_STRL("\r\n")); - } + // http status line + response.reason ? http_byte_buffer.add_status(response.status, response.reason) + : http_byte_buffer.add_status(HttpServer::get_status_message(response.status), nullptr); + // http header uint32_t header_flags = 0x0; - - /** - * http header - */ zval *zheader = sw_zend_read_property_ex(swoole_http_response_ce, response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_HEADER), 0); if (ZVAL_IS_ARRAY(zheader)) { @@ -398,11 +379,11 @@ void HttpContext::build_header(String *http_buffer, const char *body, size_t len if (ZVAL_IS_ARRAY(zvalue)) { zval *zvalue_2; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(zvalue), zvalue_2) { - add_custom_header(http_buffer, ZSTR_VAL(string_key), ZSTR_LEN(string_key), zvalue_2); + add_custom_header(&http_byte_buffer, ZSTR_VAL(string_key), ZSTR_LEN(string_key), zvalue_2); } SW_HASHTABLE_FOREACH_END(); } else { - add_custom_header(http_buffer, ZSTR_VAL(string_key), ZSTR_LEN(string_key), zvalue); + add_custom_header(&http_byte_buffer, ZSTR_VAL(string_key), ZSTR_LEN(string_key), zvalue); } } ZEND_HASH_FOREACH_END(); @@ -419,9 +400,7 @@ void HttpContext::build_header(String *http_buffer, const char *body, size_t len #endif } - /** - * http cookies - */ + // http cookies zval *zcookie = sw_zend_read_property_ex(swoole_http_response_ce, response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIE), 0); if (ZVAL_IS_ARRAY(zcookie)) { @@ -430,64 +409,70 @@ void HttpContext::build_header(String *http_buffer, const char *body, size_t len if (Z_TYPE_P(zvalue) != IS_STRING) { continue; } - http_buffer->append(ZEND_STRL("Set-Cookie: ")); - http_buffer->append(Z_STRVAL_P(zvalue), Z_STRLEN_P(zvalue)); - http_buffer->append(ZEND_STRL("\r\n")); + + http_byte_buffer.add_header(ZEND_STRL("Set-Cookie"), Z_STRVAL_P(zvalue), Z_STRLEN_P(zvalue)); } SW_HASHTABLE_FOREACH_END(); } + // http Server Name if (!(header_flags & HTTP_HEADER_SERVER)) { - http_buffer->append(ZEND_STRL("Server: " SW_HTTP_SERVER_SOFTWARE "\r\n")); + http_byte_buffer.add_header(ZEND_STRL("Server"), ZEND_STRL(SW_HTTP_SERVER_SOFTWARE)); } + + // http Date if (!(header_flags & HTTP_HEADER_DATE)) { - http_set_date_header(http_buffer); + http_set_date_header(&http_byte_buffer); } // websocket protocol (subsequent header info is unnecessary) if (upgrade == 1) { - http_buffer->append(ZEND_STRL("\r\n")); + http_buffer = get_write_buffer(http_byte_buffer.get_protocol_length()); + http_buffer->clear(); + http_byte_buffer.write_protocol(http_buffer, nullptr, 0); send_header_ = 1; return; } + + // http Connection if (!(header_flags & HTTP_HEADER_CONNECTION)) { - if (keepalive) { - http_buffer->append(ZEND_STRL("Connection: keep-alive\r\n")); - } else { - http_buffer->append(ZEND_STRL("Connection: close\r\n")); - } + keepalive ? http_byte_buffer.add_header(ZEND_STRL("Connection"), ZEND_STRL("keep-alive")) + : http_byte_buffer.add_header(ZEND_STRL("Connection"), ZEND_STRL("close")); } + + // http Content-Type if (!(header_flags & HTTP_HEADER_CONTENT_TYPE)) { - http_buffer->append(ZEND_STRL("Content-Type: " SW_HTTP_DEFAULT_CONTENT_TYPE "\r\n")); + http_byte_buffer.add_header(ZEND_STRL("Content-Type"), ZEND_STRL(SW_HTTP_DEFAULT_CONTENT_TYPE)); } + + // http Chunk if (send_chunked) { SW_ASSERT(length == 0); if (!(header_flags & HTTP_HEADER_TRANSFER_ENCODING)) { - http_buffer->append(ZEND_STRL("Transfer-Encoding: chunked\r\n")); + http_byte_buffer.add_header(ZEND_STRL("Transfer-Encoding"), ZEND_STRL("chunked")); } } + // Content-Length else if (length > 0 || parser.method != PHP_HTTP_HEAD) { #ifdef SW_HAVE_COMPRESSION if (compress(body, length)) { length = zlib_buffer->length; const char *content_encoding = get_content_encoding(); - http_buffer->append(ZEND_STRL("Content-Encoding: ")); - http_buffer->append((char *) content_encoding, strlen(content_encoding)); - http_buffer->append(ZEND_STRL("\r\n")); + http_byte_buffer.add_header(ZEND_STRL("Content-Encoding"), content_encoding, strlen(content_encoding)); } #endif if (!(header_flags & HTTP_HEADER_CONTENT_LENGTH)) { - http_buffer->append(ZEND_STRL("Content-Length: ")); - - char content_length2[128]; - int convert_result = swoole_itoa(content_length2, length); - http_buffer->append(content_length2, convert_result); - http_buffer->append(ZEND_STRL("\r\n")); + char content_length[25]; + int convert_result = swoole_itoa(content_length, length); + content_length[convert_result] = '\0'; + http_byte_buffer.add_header(ZEND_STRL("Content-Length"), content_length, convert_result); } } - http_buffer->append(ZEND_STRL("\r\n")); + http_buffer = get_write_buffer(http_byte_buffer.get_protocol_length(body ? length : 0)); + http_buffer->clear(); + http_byte_buffer.write_protocol(http_buffer, (content_compressed ? zlib_buffer->str : body), length); send_header_ = 1; } @@ -727,11 +712,8 @@ bool HttpContext::send_file(const char *file, uint32_t l_file, off_t offset, siz #ifdef SW_HAVE_COMPRESSION accept_compression = 0; #endif + build_header(nullptr, length); String *http_buffer = get_write_buffer(); - http_buffer->clear(); - - build_header(http_buffer, nullptr, length); - if (!send(this, http_buffer->str, http_buffer->length)) { send_header_ = 0; return false; @@ -777,9 +759,6 @@ void HttpContext::end(zval *zdata, zval *return_value) { } send_chunked = 0; } else { - String *http_buffer = get_write_buffer(); - http_buffer->clear(); - #ifdef SW_HAVE_ZLIB if (upgrade) { Server *serv = nullptr; @@ -807,39 +786,12 @@ void HttpContext::end(zval *zdata, zval *return_value) { } #endif - build_header(http_buffer, http_body.str, http_body.length); - - char *send_body_str; - size_t send_body_len; - + build_header(http_body.str, http_body.length); + String *http_buffer = get_write_buffer(); if (http_body.length > 0) { -#ifdef SW_HAVE_COMPRESSION - if (content_compressed) { - send_body_str = zlib_buffer->str; - send_body_len = zlib_buffer->length; - } else -#endif - { - send_body_str = http_body.str; - send_body_len = http_body.length; - } - // send twice to reduce memory copy - if (send_body_len < swoole_pagesize()) { - if (http_buffer->append(send_body_str, send_body_len) < 0) { - send_header_ = 0; - RETURN_FALSE; - } - } else { - if (!send(this, http_buffer->str, http_buffer->length)) { - send_header_ = 0; - RETURN_FALSE; - } - if (!send(this, send_body_str, send_body_len)) { - end_ = 1; - close(this); - RETURN_FALSE; - } - goto _skip_copy; + if (!send(this, http_buffer->str, http_buffer->length)) { + send_header_ = 0; + RETURN_FALSE; } } @@ -850,7 +802,6 @@ void HttpContext::end(zval *zdata, zval *return_value) { } } -_skip_copy: if (upgrade && !co_socket) { Server *serv = (Server *) private_data; Connection *conn = serv->get_connection_verify(fd); @@ -996,11 +947,12 @@ static void php_swoole_http_response_cookie(INTERNAL_FUNCTION_PARAMETERS, const RETURN_FALSE; } - char *cookie = nullptr, *date = nullptr; - size_t cookie_size = name_len + 1; // add 1 for null char - cookie_size += 50; // strlen("; expires=Fri, 31-Dec-9999 23:59:59 GMT; Max-Age=0") + char *cookie = nullptr; + zend_string *date = nullptr; + size_t cookie_size = name_len + 1; // add 1 for null char + cookie_size += 50; // strlen("; expires=Fri, 31-Dec-9999 23:59:59 GMT; Max-Age=0") if (value_len == 0) { - cookie_size += 8; // strlen("=deleted") + cookie_size += 8; // strlen("=deleted") } if (expires > 0) { // Max-Age will be no longer than 12 digits since the @@ -1008,29 +960,29 @@ static void php_swoole_http_response_cookie(INTERNAL_FUNCTION_PARAMETERS, const cookie_size += 11; } if (path_len > 0) { - cookie_size += path_len + 7; // strlen("; path=") + cookie_size += path_len + 7; // strlen("; path=") } if (domain_len > 0) { - cookie_size += domain_len + 9; // strlen("; domain=") + cookie_size += domain_len + 9; // strlen("; domain=") } if (secure) { - cookie_size += 8; // strlen("; secure") + cookie_size += 8; // strlen("; secure") } if (httponly) { - cookie_size += 10; // strlen("; httponly") + cookie_size += 10; // strlen("; httponly") } if (samesite_len > 0) { - cookie_size += samesite_len + 11; // strlen("; samesite=") + cookie_size += samesite_len + 11; // strlen("; samesite=") } if (priority_len > 0) { - cookie_size += priority_len + 11; // strlen("; priority=") + cookie_size += priority_len + 11; // strlen("; priority=") } if (value_len == 0) { cookie = (char *) emalloc(cookie_size); date = php_swoole_format_date((char *) ZEND_STRL("D, d-M-Y H:i:s T"), 1, 0); - snprintf(cookie, cookie_size, "%s=deleted; expires=%s", name, date); - efree(date); + sw_snprintf(cookie, cookie_size, "%s=deleted; expires=%s", name, ZSTR_VAL(date)); + zend_string_release(date); strlcat(cookie, "; Max-Age=0", cookie_size); } else { if (url_encode) { @@ -1049,15 +1001,15 @@ static void php_swoole_http_response_cookie(INTERNAL_FUNCTION_PARAMETERS, const if (expires > 0) { strlcat(cookie, "; expires=", cookie_size); date = php_swoole_format_date((char *) ZEND_STRL("D, d-M-Y H:i:s T"), expires, 0); - const char *p = (const char *) zend_memrchr(date, '-', strlen(date)); + const char *p = (const char *) zend_memrchr(ZSTR_VAL(date), '-', ZSTR_LEN(date)); if (!p || *(p + 5) != ' ') { php_swoole_error(E_WARNING, "Expiry date can't be a year greater than 9999"); - efree(date); + zend_string_release(date); efree(cookie); RETURN_FALSE; } - strlcat(cookie, date, cookie_size); - efree(date); + strlcat(cookie, ZSTR_VAL(date), cookie_size); + zend_string_release(date); strlcat(cookie, "; Max-Age=", cookie_size); diff --git a/include/swoole_config.h b/include/swoole_config.h index cd0936ab5d2..b266fb7bc19 100644 --- a/include/swoole_config.h +++ b/include/swoole_config.h @@ -183,7 +183,9 @@ #define SW_HTTP_ASCTIME_DATE "%a %b %e %T %Y" #define SW_HTTP_UPLOAD_FILE "Swoole-Upload-File" #define SW_HTTP_CHUNK_EOF "0\r\n\r\n" -#define SW_HTTP_DEFAULT_CONTENT_TYPE "text/html" +#define SW_HTTP_DEFAULT_CONTENT_TYPE "text/html; charset=utf-8" +#define SW_HTTP_APPLICATION_JSON "application/json" +#define SW_HTTP_TEXT_PLAIN "text/plain; charset=utf-8" // #define SW_HTTP_100_CONTINUE #define SW_HTTP_100_CONTINUE_PACKET "HTTP/1.1 100 Continue\r\n\r\n" From 011c6eb4216d92ed4773d2c192c80710438fcad0 Mon Sep 17 00:00:00 2001 From: NathanFreeman <1056159381@qq.com> Date: Fri, 16 Feb 2024 08:33:38 +0800 Subject: [PATCH 2/8] fix code --- ext-src/php_swoole_http.h | 144 +++++++++++++------------------- ext-src/swoole_http_response.cc | 32 ++++--- 2 files changed, 76 insertions(+), 100 deletions(-) diff --git a/ext-src/php_swoole_http.h b/ext-src/php_swoole_http.h index 18e001409dc..df4732de9ba 100644 --- a/ext-src/php_swoole_http.h +++ b/ext-src/php_swoole_http.h @@ -114,25 +114,22 @@ struct Response { zval _ztrailer; }; -struct Header { - const char *key = nullptr; - const char *value = nullptr; - Header *next = nullptr; - size_t length = 0; -}; +struct ByteBuffer { + const char *_protocol = nullptr; + const char *_status = nullptr; + const char *_reason = nullptr; -struct Status { - const char *protocol = nullptr; - const char *status = nullptr; - const char *reason = nullptr; - Header *next = nullptr; - size_t length = 0; -}; + size_t http_status_length = 0; + size_t http_headers_length = 0; -struct ByteBuffer { - Status *start = nullptr; - Header *current = nullptr; - size_t total = 0; + const char **headers; + size_t *lengths; + int index = 0; + + ByteBuffer(const char **_headers, size_t *_lengths) { + headers = _headers; + lengths = _lengths; + } inline void add_status(int status, const char *reason) { char buf[16]; @@ -142,135 +139,106 @@ struct ByteBuffer { } inline void add_status(const char *status, const char *reason) { - start = (Status *) emalloc(sizeof(Status)); - start->protocol = "HTTP/1.1 "; - start->status = status; - start->reason = reason; - start->next = nullptr; + _protocol = "HTTP/1.1 "; + _status = status; + _reason = reason; // calculate http status line length - if (!reason) { - start->length = strlen(start->protocol) + strlen(status) + SW_CRLF_LEN; + if (!_reason) { + http_status_length = strlen(_protocol) + strlen(_status) + SW_CRLF_LEN; } else { - start->length = strlen(start->protocol) + strlen(status) + strlen(status) + SW_CRLF_LEN + 1; + http_status_length = strlen(_protocol) + strlen(_status) + strlen(_reason) + SW_CRLF_LEN + 1; } } inline void add_header(const char *key, size_t key_length, const char *value, size_t value_length) { - Header *header = (Header *) emalloc(sizeof(Header)); - header->key = key; - header->value = value; - header->next = nullptr; - // calculate http response header length, 2 => strlen(": ") - header->length = key_length + value_length + SW_CRLF_LEN + 2; + headers[index] = key; + lengths[index] = key_length; + index++; - if (current) { - current->next = header; - } else { - start->next = header; - } - current = header; + headers[index] = value; + lengths[index] = value_length; + index++; + // calculate http response header length, 2 => strlen(": ") + http_headers_length += key_length + value_length + SW_CRLF_LEN + 2; } inline size_t get_protocol_length(size_t length = 0) { // calculate http protocol length - total = length + start->length + SW_CRLF_LEN; - Header *header = start->next; - - while (header) { - total += header->length; - header = header->next; - } - - return total; + return http_status_length + http_headers_length + length + SW_CRLF_LEN; } inline void write_protocol(String *http_buffer, const char *data, size_t length) { - http_buffer->append(start->protocol, strlen(start->protocol)); - http_buffer->append(start->status, strlen(start->status)); - if (start->reason) { + http_buffer->append(_protocol, strlen(_protocol)); + http_buffer->append(_status, strlen(_status)); + if (_reason) { http_buffer->append(" ", 1); - http_buffer->append(start->reason, strlen(start->reason)); + http_buffer->append(_reason, strlen(_reason)); } http_buffer->append(SW_CRLF, SW_CRLF_LEN); size_t key_length = 0; size_t value_length = 0; - Header *header = start->next; - while (header) { - key_length = strlen(header->key); - value_length = strlen(header->value); - - if (SW_STRCASEEQ(header->key, key_length, "Content-Type")) { - if (SW_STRCASEEQ(header->value, value_length, SW_HTTP_TEXT_PLAIN)) { + const char *key = nullptr; + const char *value = nullptr; + int i = 0; + + while (i < index) { + key_length = lengths[i]; + value_length = lengths[i + 1]; + key = headers[i++]; + value = headers[i++]; + + if (SW_STRCASEEQ(key, key_length, "Content-Type")) { + if (SW_STRCASEEQ(value, value_length, SW_HTTP_TEXT_PLAIN)) { http_buffer->append(ZEND_STRL("Content-Type: " SW_HTTP_TEXT_PLAIN "\r\n")); - header = header->next; continue; } - - if (SW_STRCASEEQ(header->value, value_length, SW_HTTP_DEFAULT_CONTENT_TYPE)) { + + if (SW_STRCASEEQ(value, value_length, SW_HTTP_DEFAULT_CONTENT_TYPE)) { http_buffer->append(ZEND_STRL("Content-Type: " SW_HTTP_DEFAULT_CONTENT_TYPE "\r\n")); - header = header->next; continue; } - if (SW_STRCASEEQ(header->value, value_length, SW_HTTP_APPLICATION_JSON)) { + if (SW_STRCASEEQ(value, value_length, SW_HTTP_APPLICATION_JSON)) { http_buffer->append(ZEND_STRL("Content-Type: " SW_HTTP_APPLICATION_JSON "\r\n")); - header = header->next; continue; } } - if (SW_STRCASEEQ(header->key, key_length, "Server") && - SW_STRCASEEQ(header->value, value_length, SW_HTTP_SERVER_SOFTWARE)) { + if (SW_STRCASEEQ(key, key_length, "Server") && + SW_STRCASEEQ(value, value_length, SW_HTTP_SERVER_SOFTWARE)) { http_buffer->append(ZEND_STRL("Server: " SW_HTTP_SERVER_SOFTWARE "\r\n")); - header = header->next; continue; } - if (SW_STRCASEEQ(header->key, key_length, "Transfer-Encoding") && - SW_STRCASEEQ(header->value, value_length, "chunked")) { + if (SW_STRCASEEQ(key, key_length, "Transfer-Encoding") && + SW_STRCASEEQ(value, value_length, "chunked")) { http_buffer->append(ZEND_STRL("Transfer-Encoding: chunked\r\n")); - header = header->next; continue; } - if (SW_STRCASEEQ(header->key, key_length, "Connection")) { - if (SW_STRCASEEQ(header->value, value_length, "keep-alive")) { + if (SW_STRCASEEQ(key, key_length, "Connection")) { + if (SW_STRCASEEQ(value, value_length, "keep-alive")) { http_buffer->append(ZEND_STRL("Connection: keep-alive\r\n")); } else { http_buffer->append(ZEND_STRL("Connection: close\r\n")); } - header = header->next; continue; } - http_buffer->append(header->key, key_length); + http_buffer->append(key, key_length); http_buffer->append(ZEND_STRL(": ")); - http_buffer->append(header->value, value_length); + http_buffer->append(value, value_length); http_buffer->append(SW_CRLF, SW_CRLF_LEN); - header = header->next; } http_buffer->append(SW_CRLF, SW_CRLF_LEN); - if (data) { http_buffer->append(data, length); } - assert(http_buffer->length == total); } - - ~ByteBuffer() { - Header *header = nullptr; - while (start->next) { - header = start->next; - start->next = header->next; - efree(header); - } - - efree(start); - } }; struct Context { diff --git a/ext-src/swoole_http_response.cc b/ext-src/swoole_http_response.cc index aa1730c83e3..19907c91fcb 100644 --- a/ext-src/swoole_http_response.cc +++ b/ext-src/swoole_http_response.cc @@ -319,17 +319,32 @@ static void add_custom_header(HttpByteBuffer *http_byte_buffer, const char *key, void HttpContext::build_header(const char *body, size_t length) { assert(send_header_ == 0); - ByteBuffer http_byte_buffer; String *http_buffer = nullptr; + int count = 6; + zval *zheader = + sw_zend_read_property_ex(swoole_http_response_ce, response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_HEADER), 0); + zval *zcookie = + sw_zend_read_property_ex(swoole_http_response_ce, response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIE), 0); + + if (ZVAL_IS_ARRAY(zheader)) { + count += zend_hash_num_elements(Z_ARRVAL_P(zheader)); + } + + if (ZVAL_IS_ARRAY(zcookie)) { + count += zend_hash_num_elements(Z_ARRVAL_P(zcookie)); + } + + const char *headers[count * 2]; + size_t lengths[count * 2]; + ByteBuffer http_byte_buffer(headers, lengths); + // http status line response.reason ? http_byte_buffer.add_status(response.status, response.reason) : http_byte_buffer.add_status(HttpServer::get_status_message(response.status), nullptr); // http header uint32_t header_flags = 0x0; - zval *zheader = - sw_zend_read_property_ex(swoole_http_response_ce, response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_HEADER), 0); if (ZVAL_IS_ARRAY(zheader)) { zval *zvalue; zend_string *string_key; @@ -401,8 +416,6 @@ void HttpContext::build_header(const char *body, size_t length) { } // http cookies - zval *zcookie = - sw_zend_read_property_ex(swoole_http_response_ce, response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIE), 0); if (ZVAL_IS_ARRAY(zcookie)) { zval *zvalue; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(zcookie), zvalue) { @@ -790,16 +803,11 @@ void HttpContext::end(zval *zdata, zval *return_value) { String *http_buffer = get_write_buffer(); if (http_body.length > 0) { if (!send(this, http_buffer->str, http_buffer->length)) { - send_header_ = 0; + end_ = 1; + close(this); RETURN_FALSE; } } - - if (!send(this, http_buffer->str, http_buffer->length)) { - end_ = 1; - close(this); - RETURN_FALSE; - } } if (upgrade && !co_socket) { From 0a41fbed32774a618bedff766b0c6e8e4c541066 Mon Sep 17 00:00:00 2001 From: NathanFreeman <1056159381@qq.com> Date: Fri, 16 Feb 2024 13:30:24 +0800 Subject: [PATCH 3/8] fix code --- ext-src/php_swoole_http.h | 85 ++++++++++----------------------- ext-src/swoole_http_response.cc | 22 +++++---- 2 files changed, 38 insertions(+), 69 deletions(-) diff --git a/ext-src/php_swoole_http.h b/ext-src/php_swoole_http.h index df4732de9ba..b58f314bc3d 100644 --- a/ext-src/php_swoole_http.h +++ b/ext-src/php_swoole_http.h @@ -115,9 +115,9 @@ struct Response { }; struct ByteBuffer { - const char *_protocol = nullptr; - const char *_status = nullptr; - const char *_reason = nullptr; + const char *protocol = nullptr; + const char *status = nullptr; + const char *reason = nullptr; size_t http_status_length = 0; size_t http_headers_length = 0; @@ -131,23 +131,15 @@ struct ByteBuffer { lengths = _lengths; } - inline void add_status(int status, const char *reason) { - char buf[16]; - int length = swoole_itoa(buf, status); - buf[length] = '\0'; - add_status(buf, reason); - } - - inline void add_status(const char *status, const char *reason) { - _protocol = "HTTP/1.1 "; - _status = status; - _reason = reason; - + inline void add_status(const char *_status, const char *_reason) { + protocol = "HTTP/1.1 "; + status = _status; + reason = _reason; // calculate http status line length - if (!_reason) { - http_status_length = strlen(_protocol) + strlen(_status) + SW_CRLF_LEN; + if (!reason) { + http_status_length = strlen(protocol) + strlen(status) + SW_CRLF_LEN; } else { - http_status_length = strlen(_protocol) + strlen(_status) + strlen(_reason) + SW_CRLF_LEN + 1; + http_status_length = strlen(protocol) + strlen(status) + strlen(reason) + SW_CRLF_LEN + 1; } } @@ -159,8 +151,13 @@ struct ByteBuffer { headers[index] = value; lengths[index] = value_length; index++; - // calculate http response header length, 2 => strlen(": ") - http_headers_length += key_length + value_length + SW_CRLF_LEN + 2; + + if (value) { + http_headers_length += key_length + value_length + SW_CRLF_LEN + 2; + } else { + // When the value is a nullptr, it means that this response header has a fixed value. + http_headers_length += key_length; + } } inline size_t get_protocol_length(size_t length = 0) { @@ -168,12 +165,12 @@ struct ByteBuffer { return http_status_length + http_headers_length + length + SW_CRLF_LEN; } - inline void write_protocol(String *http_buffer, const char *data, size_t length) { - http_buffer->append(_protocol, strlen(_protocol)); - http_buffer->append(_status, strlen(_status)); - if (_reason) { + void write_protocol(String *http_buffer, const char *data, size_t length) { + http_buffer->append(protocol, strlen(protocol)); + http_buffer->append(status, strlen(status)); + if (reason) { http_buffer->append(" ", 1); - http_buffer->append(_reason, strlen(_reason)); + http_buffer->append(reason, strlen(reason)); } http_buffer->append(SW_CRLF, SW_CRLF_LEN); @@ -188,42 +185,9 @@ struct ByteBuffer { value_length = lengths[i + 1]; key = headers[i++]; value = headers[i++]; - - if (SW_STRCASEEQ(key, key_length, "Content-Type")) { - if (SW_STRCASEEQ(value, value_length, SW_HTTP_TEXT_PLAIN)) { - http_buffer->append(ZEND_STRL("Content-Type: " SW_HTTP_TEXT_PLAIN "\r\n")); - continue; - } - - if (SW_STRCASEEQ(value, value_length, SW_HTTP_DEFAULT_CONTENT_TYPE)) { - http_buffer->append(ZEND_STRL("Content-Type: " SW_HTTP_DEFAULT_CONTENT_TYPE "\r\n")); - continue; - } - - if (SW_STRCASEEQ(value, value_length, SW_HTTP_APPLICATION_JSON)) { - http_buffer->append(ZEND_STRL("Content-Type: " SW_HTTP_APPLICATION_JSON "\r\n")); - continue; - } - } - - if (SW_STRCASEEQ(key, key_length, "Server") && - SW_STRCASEEQ(value, value_length, SW_HTTP_SERVER_SOFTWARE)) { - http_buffer->append(ZEND_STRL("Server: " SW_HTTP_SERVER_SOFTWARE "\r\n")); - continue; - } - - if (SW_STRCASEEQ(key, key_length, "Transfer-Encoding") && - SW_STRCASEEQ(value, value_length, "chunked")) { - http_buffer->append(ZEND_STRL("Transfer-Encoding: chunked\r\n")); - continue; - } - if (SW_STRCASEEQ(key, key_length, "Connection")) { - if (SW_STRCASEEQ(value, value_length, "keep-alive")) { - http_buffer->append(ZEND_STRL("Connection: keep-alive\r\n")); - } else { - http_buffer->append(ZEND_STRL("Connection: close\r\n")); - } + if (value == nullptr) { + http_buffer->append(key, key_length); continue; } @@ -237,7 +201,6 @@ struct ByteBuffer { if (data) { http_buffer->append(data, length); } - assert(http_buffer->length == total); } }; diff --git a/ext-src/swoole_http_response.cc b/ext-src/swoole_http_response.cc index 19907c91fcb..ab5a1bcda92 100644 --- a/ext-src/swoole_http_response.cc +++ b/ext-src/swoole_http_response.cc @@ -340,8 +340,14 @@ void HttpContext::build_header(const char *body, size_t length) { ByteBuffer http_byte_buffer(headers, lengths); // http status line - response.reason ? http_byte_buffer.add_status(response.status, response.reason) - : http_byte_buffer.add_status(HttpServer::get_status_message(response.status), nullptr); + char status_to_string[16]; + if (!response.reason) { + http_byte_buffer.add_status(HttpServer::get_status_message(response.status), nullptr); + } else { + int length = swoole_itoa(status_to_string, response.status); + status_to_string[length] = '\0'; + http_byte_buffer.add_status(status_to_string, response.reason); + } // http header uint32_t header_flags = 0x0; @@ -430,7 +436,7 @@ void HttpContext::build_header(const char *body, size_t length) { // http Server Name if (!(header_flags & HTTP_HEADER_SERVER)) { - http_byte_buffer.add_header(ZEND_STRL("Server"), ZEND_STRL(SW_HTTP_SERVER_SOFTWARE)); + http_byte_buffer.add_header(ZEND_STRL("Server: " SW_HTTP_SERVER_SOFTWARE "\r\n"), nullptr, 0); } // http Date @@ -449,20 +455,20 @@ void HttpContext::build_header(const char *body, size_t length) { // http Connection if (!(header_flags & HTTP_HEADER_CONNECTION)) { - keepalive ? http_byte_buffer.add_header(ZEND_STRL("Connection"), ZEND_STRL("keep-alive")) - : http_byte_buffer.add_header(ZEND_STRL("Connection"), ZEND_STRL("close")); + keepalive ? http_byte_buffer.add_header(ZEND_STRL("Connection: keep-alive\r\n"), nullptr, 0) + : http_byte_buffer.add_header(ZEND_STRL("Connection: close\r\n"), nullptr, 0); } // http Content-Type if (!(header_flags & HTTP_HEADER_CONTENT_TYPE)) { - http_byte_buffer.add_header(ZEND_STRL("Content-Type"), ZEND_STRL(SW_HTTP_DEFAULT_CONTENT_TYPE)); + http_byte_buffer.add_header(ZEND_STRL("Content-Type: " SW_HTTP_DEFAULT_CONTENT_TYPE "\r\n"), nullptr, 0); } // http Chunk if (send_chunked) { SW_ASSERT(length == 0); if (!(header_flags & HTTP_HEADER_TRANSFER_ENCODING)) { - http_byte_buffer.add_header(ZEND_STRL("Transfer-Encoding"), ZEND_STRL("chunked")); + http_byte_buffer.add_header(ZEND_STRL("Transfer-Encoding: chunked\r\n"), nullptr, 0); } } @@ -475,8 +481,8 @@ void HttpContext::build_header(const char *body, size_t length) { http_byte_buffer.add_header(ZEND_STRL("Content-Encoding"), content_encoding, strlen(content_encoding)); } #endif + char content_length[25]; if (!(header_flags & HTTP_HEADER_CONTENT_LENGTH)) { - char content_length[25]; int convert_result = swoole_itoa(content_length, length); content_length[convert_result] = '\0'; http_byte_buffer.add_header(ZEND_STRL("Content-Length"), content_length, convert_result); From 832053888279cc38de2efd0e3e6c4be0041903d6 Mon Sep 17 00:00:00 2001 From: NathanFreeman <1056159381@qq.com> Date: Fri, 16 Feb 2024 20:29:57 +0800 Subject: [PATCH 4/8] fix bug --- ext-src/swoole_http_response.cc | 10 ++++------ include/swoole_config.h | 2 -- tests/swoole_http_client_coro/connection_close.phpt | 4 ++-- tests/swoole_http_server/bug_4857.phpt | 6 +++--- tests/swoole_http_server/bug_5107.phpt | 2 +- 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/ext-src/swoole_http_response.cc b/ext-src/swoole_http_response.cc index ab5a1bcda92..ed582b59fdb 100644 --- a/ext-src/swoole_http_response.cc +++ b/ext-src/swoole_http_response.cc @@ -807,12 +807,10 @@ void HttpContext::end(zval *zdata, zval *return_value) { build_header(http_body.str, http_body.length); String *http_buffer = get_write_buffer(); - if (http_body.length > 0) { - if (!send(this, http_buffer->str, http_buffer->length)) { - end_ = 1; - close(this); - RETURN_FALSE; - } + if (!send(this, http_buffer->str, http_buffer->length)) { + end_ = 1; + close(this); + RETURN_FALSE; } } diff --git a/include/swoole_config.h b/include/swoole_config.h index b266fb7bc19..6a1819832f8 100644 --- a/include/swoole_config.h +++ b/include/swoole_config.h @@ -184,8 +184,6 @@ #define SW_HTTP_UPLOAD_FILE "Swoole-Upload-File" #define SW_HTTP_CHUNK_EOF "0\r\n\r\n" #define SW_HTTP_DEFAULT_CONTENT_TYPE "text/html; charset=utf-8" -#define SW_HTTP_APPLICATION_JSON "application/json" -#define SW_HTTP_TEXT_PLAIN "text/plain; charset=utf-8" // #define SW_HTTP_100_CONTINUE #define SW_HTTP_100_CONTINUE_PACKET "HTTP/1.1 100 Continue\r\n\r\n" diff --git a/tests/swoole_http_client_coro/connection_close.phpt b/tests/swoole_http_client_coro/connection_close.phpt index 917ae15b296..48a84378ac3 100644 --- a/tests/swoole_http_client_coro/connection_close.phpt +++ b/tests/swoole_http_client_coro/connection_close.phpt @@ -56,7 +56,7 @@ array(5) { ["date"]=> string(%d) "%s" ["content-type"]=> - string(9) "text/html" + string(24) "text/html; charset=utf-8" ["content-length"]=> string(1) "0" } @@ -69,7 +69,7 @@ array(5) { ["connection"]=> string(10) "keep-alive" ["content-type"]=> - string(9) "text/html" + string(24) "text/html; charset=utf-8" ["content-length"]=> string(1) "0" } diff --git a/tests/swoole_http_server/bug_4857.phpt b/tests/swoole_http_server/bug_4857.phpt index 6abded5af23..010ffca35b9 100644 --- a/tests/swoole_http_server/bug_4857.phpt +++ b/tests/swoole_http_server/bug_4857.phpt @@ -67,7 +67,7 @@ array(6) { ["connection"]=> string(10) "keep-alive" ["content-type"]=> - string(9) "text/html" + string(24) "text/html; charset=utf-8" ["content-encoding"]=> string(%d) %s ["content-length"]=> @@ -82,7 +82,7 @@ array(5) { ["connection"]=> string(10) "keep-alive" ["content-type"]=> - string(9) "text/html" + string(24) "text/html; charset=utf-8" ["transfer-encoding"]=> string(7) "chunked" } @@ -95,7 +95,7 @@ array(6) { ["connection"]=> string(10) "keep-alive" ["content-type"]=> - string(9) "text/html" + string(24) "text/html; charset=utf-8" ["content-encoding"]=> string(%d) %s ["content-length"]=> diff --git a/tests/swoole_http_server/bug_5107.phpt b/tests/swoole_http_server/bug_5107.phpt index 5e071f96071..a3672cb85a2 100644 --- a/tests/swoole_http_server/bug_5107.phpt +++ b/tests/swoole_http_server/bug_5107.phpt @@ -41,7 +41,7 @@ array(5) { ["connection"]=> string(10) "keep-alive" ["content-type"]=> - string(9) "text/html" + string(24) "text/html; charset=utf-8" ["content-length"]=> string(2) "11" } From c570104d1d4a34df1c66a1ce3dae4be48859c058 Mon Sep 17 00:00:00 2001 From: NathanFreeman <1056159381@qq.com> Date: Fri, 16 Feb 2024 22:51:32 +0800 Subject: [PATCH 5/8] fix bug --- ext-src/swoole_http_response.cc | 7 +++++++ tests/swoole_http_server/duplicate_header.phpt | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ext-src/swoole_http_response.cc b/ext-src/swoole_http_response.cc index ed582b59fdb..8b6a9f62102 100644 --- a/ext-src/swoole_http_response.cc +++ b/ext-src/swoole_http_response.cc @@ -329,6 +329,13 @@ void HttpContext::build_header(const char *body, size_t length) { if (ZVAL_IS_ARRAY(zheader)) { count += zend_hash_num_elements(Z_ARRVAL_P(zheader)); + zval *zvalue = nullptr; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zheader), zvalue) { + if (ZVAL_IS_ARRAY(zvalue)) { + count += zend_hash_num_elements(Z_ARRVAL_P(zvalue)) - 1; + } + } + ZEND_HASH_FOREACH_END(); } if (ZVAL_IS_ARRAY(zcookie)) { diff --git a/tests/swoole_http_server/duplicate_header.phpt b/tests/swoole_http_server/duplicate_header.phpt index 37743d67154..6297761904c 100644 --- a/tests/swoole_http_server/duplicate_header.phpt +++ b/tests/swoole_http_server/duplicate_header.phpt @@ -60,6 +60,6 @@ Test-Value: 3.1415926 Server: swoole-http-server Date: %s Connection: keep-alive -Content-Type: text/html +Content-Type: text/html; charset=utf-8 hello world From 0dc3e8acbe65a106533951c58529450bd4c730fb Mon Sep 17 00:00:00 2001 From: NathanFreeman <1056159381@qq.com> Date: Fri, 16 Feb 2024 22:58:19 +0800 Subject: [PATCH 6/8] fix test --- tests/swoole_curl/guzzle.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/swoole_curl/guzzle.phpt b/tests/swoole_curl/guzzle.phpt index 52da957cb6c..cc25f8c30d5 100644 --- a/tests/swoole_curl/guzzle.phpt +++ b/tests/swoole_curl/guzzle.phpt @@ -37,7 +37,7 @@ run(function () { ]; $responses = Promise\Utils::unwrap($promises); Assert::contains($responses['baidu']->getBody(), '百度'); - Assert::contains(iconv('gbk', 'utf-8', $responses['qq']->getBody()), '腾讯'); + Assert::contains($responses['qq']->getBody(), '腾讯'); Assert::contains($responses['zhihu']->getBody(), '知乎'); $result['task_1'] = 'OK'; }); From 4af40828e0c357d46f5cfc9d596b6368133bea60 Mon Sep 17 00:00:00 2001 From: NathanFreeman <1056159381@qq.com> Date: Fri, 16 Feb 2024 23:27:05 +0800 Subject: [PATCH 7/8] fix test --- tests/swoole_curl/multi/bug4393.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/swoole_curl/multi/bug4393.phpt b/tests/swoole_curl/multi/bug4393.phpt index 95cb17a9e6e..aa09b5755ad 100644 --- a/tests/swoole_curl/multi/bug4393.phpt +++ b/tests/swoole_curl/multi/bug4393.phpt @@ -40,7 +40,7 @@ run(function () { if (IS_IN_CI) { Assert::contains($responses['baidu']->getBody(), '百度'); - Assert::contains(iconv('gbk', 'utf-8', $responses['qq']->getBody()), '腾讯'); + Assert::contains($responses['qq']->getBody(), '腾讯'); } else { Assert::contains($responses['httpbin']->getBody(), 'httpbin'); Assert::contains($responses['nghttp2']->getBody(), 'nghttp2'); From cbbfabcc423e70a27b35bb54069e761ee1c3f91b Mon Sep 17 00:00:00 2001 From: NathanFreeman <1056159381@qq.com> Date: Sat, 17 Feb 2024 18:12:10 +0800 Subject: [PATCH 8/8] We need to convert the key and value of numeric types into strings so that we can write them into a buffer. However, after the conversion, we need to manually release the resulting strings to avoid automatic release before writing them into the buffer. --- ext-src/php_swoole_http.h | 27 +++++++++++++++++++++++---- ext-src/swoole_http_response.cc | 31 ++++++++++++++++++------------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/ext-src/php_swoole_http.h b/ext-src/php_swoole_http.h index b58f314bc3d..1c8107e63d4 100644 --- a/ext-src/php_swoole_http.h +++ b/ext-src/php_swoole_http.h @@ -122,13 +122,24 @@ struct ByteBuffer { size_t http_status_length = 0; size_t http_headers_length = 0; - const char **headers; - size_t *lengths; int index = 0; + size_t *lengths; + const char **headers; - ByteBuffer(const char **_headers, size_t *_lengths) { - headers = _headers; + int free_num = 0; + zend_string **free_list; + + ByteBuffer(size_t *_lengths, const char **_headers, zend_string **_free_list) { lengths = _lengths; + headers = _headers; + free_list = _free_list; + } + + ~ByteBuffer() { + int i = 0; + while (i < free_num) { + zend_string_release(free_list[i++]); + } } inline void add_status(const char *_status, const char *_reason) { @@ -143,6 +154,14 @@ struct ByteBuffer { } } + inline void add_header(zend_string *key, zend_string *value) { + zend_string_addref(key); + zend_string_addref(value); + free_list[free_num++] = key; + free_list[free_num++] = value; + add_header(ZSTR_VAL(key), ZSTR_LEN(key), ZSTR_VAL(value), ZSTR_LEN(value)); + } + inline void add_header(const char *key, size_t key_length, const char *value, size_t value_length) { headers[index] = key; lengths[index] = key_length; diff --git a/ext-src/swoole_http_response.cc b/ext-src/swoole_http_response.cc index 8b6a9f62102..6fbb200b206 100644 --- a/ext-src/swoole_http_response.cc +++ b/ext-src/swoole_http_response.cc @@ -304,7 +304,7 @@ static void http_set_date_header(HttpByteBuffer *http_byte_buffer) { http_byte_buffer->add_header(ZEND_STRL("Date"), cache.buf, cache.len); } -static void add_custom_header(HttpByteBuffer *http_byte_buffer, const char *key, size_t l_key, zval *value) { +static void add_custom_header(HttpByteBuffer *http_byte_buffer, zend_string *key, zval *value) { if (ZVAL_IS_NULL(value)) { return; } @@ -314,7 +314,7 @@ static void add_custom_header(HttpByteBuffer *http_byte_buffer, const char *key, return; } - http_byte_buffer->add_header(key, l_key, str_value.val(), str_value.len()); + http_byte_buffer->add_header(key, str_value.get()); } void HttpContext::build_header(const char *body, size_t length) { @@ -324,9 +324,6 @@ void HttpContext::build_header(const char *body, size_t length) { int count = 6; zval *zheader = sw_zend_read_property_ex(swoole_http_response_ce, response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_HEADER), 0); - zval *zcookie = - sw_zend_read_property_ex(swoole_http_response_ce, response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIE), 0); - if (ZVAL_IS_ARRAY(zheader)) { count += zend_hash_num_elements(Z_ARRVAL_P(zheader)); zval *zvalue = nullptr; @@ -338,13 +335,22 @@ void HttpContext::build_header(const char *body, size_t length) { ZEND_HASH_FOREACH_END(); } + zval *zcookie = + sw_zend_read_property_ex(swoole_http_response_ce, response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIE), 0); if (ZVAL_IS_ARRAY(zcookie)) { count += zend_hash_num_elements(Z_ARRVAL_P(zcookie)); } - const char *headers[count * 2]; - size_t lengths[count * 2]; - ByteBuffer http_byte_buffer(headers, lengths); + int total = count * 2; + size_t lengths[total]; + const char *headers[total]; + /** + * We need to convert the key and value of numeric types into strings so that we can write them into a buffer. + * However, after the conversion, we need to manually release the resulting strings to avoid automatic release + * before writing them into the buffer. + */ + zend_string *free_list[total]; + ByteBuffer http_byte_buffer(lengths, headers, free_list); // http status line char status_to_string[16]; @@ -369,10 +375,9 @@ void HttpContext::build_header(const char *body, size_t length) { ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(zheader), num_key, string_key, zvalue) { if (!string_key) { string_key = zend_long_to_str(num_key); - } else { - zend_string_addref(string_key); + zend_string_delref(string_key); } - zend::String key(string_key, false); + int key_header = parse_header_name(ZSTR_VAL(string_key), ZSTR_LEN(string_key)); if (key_header > 0) { #ifdef SW_HAVE_COMPRESSION @@ -407,11 +412,11 @@ void HttpContext::build_header(const char *body, size_t length) { if (ZVAL_IS_ARRAY(zvalue)) { zval *zvalue_2; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(zvalue), zvalue_2) { - add_custom_header(&http_byte_buffer, ZSTR_VAL(string_key), ZSTR_LEN(string_key), zvalue_2); + add_custom_header(&http_byte_buffer, string_key, zvalue_2); } SW_HASHTABLE_FOREACH_END(); } else { - add_custom_header(&http_byte_buffer, ZSTR_VAL(string_key), ZSTR_LEN(string_key), zvalue); + add_custom_header(&http_byte_buffer, string_key, zvalue); } } ZEND_HASH_FOREACH_END();