/* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole.h" #include "swoole_http.h" #ifdef SW_USE_HTTP2 #include "http2.h" #include
static sw_inline void http2_add_header(nghttp2_nv *headers, char *k, int kl, char *v, int vl) { headers->name = (uchar*) k; headers->namelen = kl; headers->value = (uchar*) v; headers->valuelen = vl; } static int http_build_trailer(http_context *ctx, uchar *buffer TSRMLS_DC) { int ret; nghttp2_nv nv[128]; int index = 0; zval *trailer = ctx->response.ztrailer; if (trailer) { HashTable *ht = Z_ARRVAL_P(trailer); zval *value = NULL; char *key = NULL; uint32_t keylen = 0; int type; SW_HASHTABLE_FOREACH_START2(ht, key, keylen, type, value) { if (!key) { break; } http2_add_header(&nv[index++], key, keylen, Z_STRVAL_P(value), Z_STRLEN_P(value)); (void) type; } SW_HASHTABLE_FOREACH_END(); } ssize_t rv; size_t buflen; size_t i; size_t sum = 0; nghttp2_hd_deflater *deflater; ret = nghttp2_hd_deflate_new(&deflater, 4096); if (ret != 0) { swoole_php_error(E_WARNING, "nghttp2_hd_deflate_init failed with error: %s\n", nghttp2_strerror(ret)); return SW_ERR; } for (i = 0; i < index; ++i) { sum += nv[i].namelen + nv[i].valuelen; } buflen = nghttp2_hd_deflate_bound(deflater, nv, index); rv = nghttp2_hd_deflate_hd(deflater, (uchar *) buffer, buflen, nv, index); if (rv < 0) { swoole_php_error(E_WARNING, "nghttp2_hd_deflate_hd() failed with error: %s\n", nghttp2_strerror((int ) rv)); return SW_ERR; } nghttp2_hd_deflate_del(deflater); return rv; } static sw_inline void http2_onRequest(http_context *ctx, int server_fd TSRMLS_DC) { zval *retval; zval **args[2]; zval *zrequest_object = ctx->request.zobject; zval *zresponse_object = ctx->response.zobject; SW_SEPARATE_ZVAL(zrequest_object); SW_SEPARATE_ZVAL(zresponse_object); args[0] = &zrequest_object; args[1] = &zresponse_object; zval *zcallback = php_swoole_server_get_callback(SwooleG.serv, server_fd, SW_SERVER_CB_onRequest); if (sw_call_user_function_ex(EG(function_table), NULL, zcallback, &retval, 2, args, 0, NULL TSRMLS_CC) == FAILURE) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "onRequest handler error"); } if (EG(exception)) { zend_exception_error(EG(exception), E_ERROR TSRMLS_CC); } if (retval) { sw_zval_ptr_dtor(&retval); } sw_zval_ptr_dtor(&zrequest_object); sw_zval_ptr_dtor(&zresponse_object); } static int http2_build_header(http_context *ctx, uchar *buffer, int body_length TSRMLS_DC) { assert(ctx->send_header == 0); swServer *serv = SwooleG.serv; char buf[SW_HTTP_HEADER_MAX_SIZE]; char *date_str = NULL; char intbuf[2][16]; int ret; /** * http header */ zval *zheader = ctx->response.zheader; int index = 0; nghttp2_nv nv[128]; /** * http status code */ if (ctx->response.status == 0) { ctx->response.status = 200; } ret = swoole_itoa(intbuf[0], ctx->response.status); http2_add_header(&nv[index++], ZEND_STRL(":status"), intbuf[0], ret); if (zheader) { int flag = 0x0; HashTable *ht = Z_ARRVAL_P(zheader); zval *value = NULL; char *key = NULL; uint32_t keylen = 0; int type; SW_HASHTABLE_FOREACH_START2(ht, key, keylen, type, value) { if (!key) { break; } if (strncmp(key, "server", keylen) == 0) { flag |= HTTP_RESPONSE_SERVER; } else if (strncmp(key, "content-length", keylen) == 0) { flag |= HTTP_RESPONSE_CONTENT_LENGTH; } else if (strncmp(key, "date", keylen) == 0) { flag |= HTTP_RESPONSE_DATE; } else if (strncmp(key, "content-type", keylen) == 0) { flag |= HTTP_RESPONSE_CONTENT_TYPE; } http2_add_header(&nv[index++], key, keylen, Z_STRVAL_P(value), Z_STRLEN_P(value)); } SW_HASHTABLE_FOREACH_END(); (void)type; if (!(flag & HTTP_RESPONSE_SERVER)) { http2_add_header(&nv[index++], ZEND_STRL("server"), ZEND_STRL(SW_HTTP_SERVER_SOFTWARE)); } if (!(flag & HTTP_RESPONSE_CONTENT_LENGTH) && body_length >= 0) { #ifdef SW_HAVE_ZLIB if (ctx->gzip_enable) { body_length = swoole_zlib_buffer->length; } #endif ret = swoole_itoa(intbuf[1], body_length); http2_add_header(&nv[index++], ZEND_STRL("content-length"), intbuf[1], ret); } if (!(flag & HTTP_RESPONSE_DATE)) { date_str = sw_php_format_date(ZEND_STRL(SW_HTTP_DATE_FORMAT), serv->gs->now, 0 TSRMLS_CC); http2_add_header(&nv[index++], ZEND_STRL("date"), date_str, strlen(date_str)); } if (!(flag & HTTP_RESPONSE_CONTENT_TYPE)) { http2_add_header(&nv[index++], ZEND_STRL("content-type"), ZEND_STRL("text/html")); } } else { http2_add_header(&nv[index++], ZEND_STRL("server"), ZEND_STRL(SW_HTTP_SERVER_SOFTWARE)); http2_add_header(&nv[index++], ZEND_STRL("content-type"), ZEND_STRL("text/html")); date_str = sw_php_format_date(ZEND_STRL(SW_HTTP_DATE_FORMAT), serv->gs->now, 0 TSRMLS_CC); http2_add_header(&nv[index++], ZEND_STRL("date"), date_str, strlen(date_str)); #ifdef SW_HAVE_ZLIB if (ctx->gzip_enable) { body_length = swoole_zlib_buffer->length; } #endif ret = swoole_itoa(buf, body_length); http2_add_header(&nv[index++], ZEND_STRL("content-length"), buf, ret); } //http cookies if (ctx->response.zcookie) { zval *value; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(ctx->response.zcookie), value) { if (Z_TYPE_P(value) != IS_STRING) { continue; } http2_add_header(&nv[index++], ZEND_STRL("set-cookie"), Z_STRVAL_P(value), Z_STRLEN_P(value)); } SW_HASHTABLE_FOREACH_END(); } //http compress if (ctx->gzip_enable) { #ifdef SW_HTTP_COMPRESS_GZIP http2_add_header(&nv[index++], ZEND_STRL("content-encoding"), ZEND_STRL("gzip")); #else http2_add_header(&nv[index++], ZEND_STRL("content-encoding"), ZEND_STRL("deflate")); #endif } ctx->send_header = 1; ssize_t rv; size_t buflen; size_t i; size_t sum = 0; nghttp2_hd_deflater *deflater; ret = nghttp2_hd_deflate_new(&deflater, 4096); if (ret != 0) { swoole_php_error(E_WARNING, "nghttp2_hd_deflate_init failed with error: %s\n", nghttp2_strerror(ret)); return SW_ERR; } for (i = 0; i < index; ++i) { sum += nv[i].namelen + nv[i].valuelen; } buflen = nghttp2_hd_deflate_bound(deflater, nv, index); rv = nghttp2_hd_deflate_hd(deflater, (uchar *) buffer, buflen, nv, index); if (rv < 0) { swoole_php_error(E_WARNING, "nghttp2_hd_deflate_hd() failed with error: %s\n", nghttp2_strerror((int ) rv)); return SW_ERR; } if (date_str) { efree(date_str); } nghttp2_hd_deflate_del(deflater); return rv; } int swoole_http2_do_response(http_context *ctx, swString *body) { #if PHP_MAJOR_VERSION < 7 TSRMLS_FETCH_FROM_CTX(sw_thread_ctx ? sw_thread_ctx : NULL); #endif swoole_http_client *client = ctx->client; char header_buffer[8192]; int ret; ret = http2_build_header(ctx, (uchar *) header_buffer, body->length TSRMLS_CC); swString_clear(swoole_http_buffer); /** +---------------+ |Pad Length? (8)| +-+-------------+-----------------------------------------------+ |E| Stream Dependency? (31) | +-+-------------+-----------------------------------------------+ | Weight? (8) | +-+-------------+-----------------------------------------------+ | Header Block Fragment (*) ... +---------------------------------------------------------------+ | Padding (*) ... +---------------------------------------------------------------+ */ char frame_header[9]; zval *trailer = ctx->response.ztrailer; if (trailer == NULL && body->length == 0) { swHttp2_set_frame_header(frame_header, SW_HTTP2_TYPE_HEADERS, ret, SW_HTTP2_FLAG_END_HEADERS | SW_HTTP2_FLAG_END_STREAM, ctx->stream_id); } else { swHttp2_set_frame_header(frame_header, SW_HTTP2_TYPE_HEADERS, ret, SW_HTTP2_FLAG_END_HEADERS, ctx->stream_id); } swString_append_ptr(swoole_http_buffer, frame_header, 9); swString_append_ptr(swoole_http_buffer, header_buffer, ret); int flag = SW_HTTP2_FLAG_END_STREAM; if (trailer) { flag = SW_HTTP2_FLAG_NONE; } ret = swServer_tcp_send(SwooleG.serv, ctx->fd, swoole_http_buffer->str, swoole_http_buffer->length); if (ret < 0) { ctx->send_header = 0; return SW_ERR; } ctx->send_header = 1; if (trailer == NULL && body->length == 0) { goto _end; } char *p = body->str; size_t l = body->length; size_t send_n; while (l > 0) { int _send_flag; swString_clear(swoole_http_buffer); if (l > SW_HTTP2_MAX_FRAME_SIZE) { send_n = SW_HTTP2_MAX_FRAME_SIZE; _send_flag = 0; } else { send_n = l; _send_flag = flag; } swHttp2_set_frame_header(frame_header, SW_HTTP2_TYPE_DATA, send_n, _send_flag, ctx->stream_id); swString_append_ptr(swoole_http_buffer, frame_header, 9); swString_append_ptr(swoole_http_buffer, p, send_n); if (swServer_tcp_send(SwooleG.serv, ctx->fd, swoole_http_buffer->str, swoole_http_buffer->length) < 0) { return SW_ERR; } else { l -= send_n; p += send_n; } } if (trailer) { swString_clear(swoole_http_buffer); memset(header_buffer, 0, sizeof(header_buffer)); ret = http_build_trailer(ctx, (uchar *) header_buffer TSRMLS_CC); swHttp2_set_frame_header(frame_header, SW_HTTP2_TYPE_HEADERS, ret, SW_HTTP2_FLAG_END_HEADERS | SW_HTTP2_FLAG_END_STREAM, ctx->stream_id); swString_append_ptr(swoole_http_buffer, frame_header, 9); swString_append_ptr(swoole_http_buffer, header_buffer, ret); if (swServer_tcp_send(SwooleG.serv, ctx->fd, swoole_http_buffer->str, swoole_http_buffer->length) < 0) { return SW_ERR; } } _end: if (body->length > 0) { client->window_size -= body->length; // TODO:flow control? } if (client->streams) { swHashMap_del_int(client->streams, ctx->stream_id); } swoole_http_context_free(ctx TSRMLS_CC); return SW_OK; } static int http2_parse_header(swoole_http_client *client, http_context *ctx, int flags, char *in, size_t inlen) { #if PHP_MAJOR_VERSION < 7 TSRMLS_FETCH_FROM_CTX(sw_thread_ctx ? sw_thread_ctx : NULL); #endif nghttp2_hd_inflater *inflater = client->inflater; if (!inflater) { int ret = nghttp2_hd_inflate_new(&inflater); if (ret != 0) { swoole_php_error(E_WARNING, "nghttp2_hd_inflate_init() failed, Error: %s[%d].", nghttp2_strerror(ret), ret); return SW_ERR; } client->inflater = inflater; } if (flags & SW_HTTP2_FLAG_PRIORITY) { //int stream_deps = ntohl(*(int *) (in)); //uint8_t weight = in[4]; in += 5; inlen -= 5; } zval *zheader = ctx->request.zheader; zval *zserver = ctx->request.zserver; ssize_t rv; for (;;) { nghttp2_nv nv; int inflate_flags = 0; size_t proclen; rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, (uchar *) in, inlen, 1); if (rv < 0) { swoole_php_error(E_WARNING, "inflate failed, Error: %s[%zd].", nghttp2_strerror(rv), rv); return -1; } proclen = (size_t) rv; in += proclen; inlen -= proclen; swTraceLog(SW_TRACE_HTTP2, "Header: %s[%d]: %s[%d]", nv.name, nv.namelen, nv.value, nv.valuelen); if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { if (nv.name[0] == ':') { if (strncasecmp((char *) nv.name + 1, "method", nv.namelen -1) == 0) { sw_add_assoc_stringl_ex(zserver, ZEND_STRS("request_method"), (char *) nv.value, nv.valuelen, 1); } else if (strncasecmp((char *) nv.name + 1, "path", nv.namelen -1) == 0) { char pathbuf[SW_HTTP_HEADER_MAX_SIZE]; char *v_str = strchr((char *) nv.value, '?'); if (v_str) { v_str++; int k_len = v_str - (char *) nv.value - 1; int v_len = nv.valuelen - k_len - 1; memcpy(pathbuf, nv.value, k_len); pathbuf[k_len] = 0; sw_add_assoc_stringl_ex(zserver, ZEND_STRS("query_string"), v_str, v_len, 1); sw_add_assoc_stringl_ex(zserver, ZEND_STRS("request_uri"), pathbuf, k_len, 1); zval *zget; zval *zrequest_object = ctx->request.zobject; swoole_http_server_array_init(get, request); //no need free, will free by treat_data char *query = estrndup(v_str, v_len); //parse url params sapi_module.treat_data(PARSE_STRING, query, zget TSRMLS_CC); } else { sw_add_assoc_stringl_ex(zserver, ZEND_STRS("request_uri"), (char *) nv.value, nv.valuelen, 1); } } else if (strncasecmp((char *) nv.name + 1, "authority", nv.namelen -1) == 0) { sw_add_assoc_stringl_ex(zheader, ZEND_STRS("host"), (char * ) nv.value, nv.valuelen, 1); } } else { if (strncasecmp((char *) nv.name, "content-type", nv.namelen) == 0) { if (http_strncasecmp("application/x-www-form-urlencoded", (char *) nv.value, nv.valuelen)) { ctx->request.post_form_urlencoded = 1; } else if (http_strncasecmp("multipart/form-data", (char *) nv.value, nv.valuelen)) { int boundary_len = nv.valuelen - strlen("multipart/form-data; boundary="); if (boundary_len <= 0) { swWarn("invalid multipart/form-data body.", ctx->fd); return 0; } swoole_http_parse_form_data(ctx, (char*) nv.value + nv.valuelen - boundary_len, boundary_len TSRMLS_CC); ctx->parser.data = ctx; } } else if (strncasecmp((char *) nv.name, "cookie", nv.namelen) == 0) { zval *zcookie = ctx->request.zcookie; zval *zrequest_object = ctx->request.zobject; if (!zcookie) { swoole_http_server_array_init(cookie, request); } char keybuf[SW_HTTP_COOKIE_KEYLEN]; char *v_str = strchr((char *) nv.value, '=') + 1; int k_len = v_str - (char *) nv.value - 1; int v_len = nv.valuelen - k_len - 1; memcpy(keybuf, nv.value, k_len); keybuf[k_len] = 0; sw_add_assoc_stringl_ex(zcookie, keybuf, k_len + 1, v_str, v_len, 1); continue; } sw_add_assoc_stringl_ex(zheader, (char *) nv.name, nv.namelen + 1, (char *) nv.value, nv.valuelen, 1); } } if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { nghttp2_hd_inflate_end_headers(inflater); break; } if ((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && inlen == 0) { break; } } rv = nghttp2_hd_inflate_change_table_size(inflater, 4096); if (rv != 0) { return rv; } return SW_OK; } /** * Http2 */ int swoole_http2_onFrame(swoole_http_client *client, swEventData *req) { #if PHP_MAJOR_VERSION < 7 TSRMLS_FETCH_FROM_CTX(sw_thread_ctx ? sw_thread_ctx : NULL); #endif if (!client->init) { client->window_size = SW_HTTP2_DEFAULT_WINDOW; client->remote_window_size = SW_HTTP2_DEFAULT_WINDOW; client->init = 1; } int fd = req->info.fd; http_context *ctx; swServer *serv = SwooleG.serv; zval *zdata; SW_MAKE_STD_ZVAL(zdata); php_swoole_get_recv_data(zdata, req, NULL, 0); char *buf = Z_STRVAL_P(zdata); int type = buf[3]; int flags = buf[4]; int stream_id = ntohl((*(int *) (buf + 5))) & 0x7fffffff; uint32_t length = swHttp2_get_length(buf); swTraceLog(SW_TRACE_HTTP2, "[%s]\tflags=%d, stream_id=%d, length=%d", swHttp2_get_type(type), flags, stream_id, length); if (type == SW_HTTP2_TYPE_HEADERS) { ctx = swoole_http_context_new(client TSRMLS_CC); if (!ctx) { sw_zval_ptr_dtor(&zdata); swoole_error_log(SW_LOG_WARNING, SW_ERROR_HTTP2_STREAM_NO_HEADER, "http2 error stream."); return SW_ERR; } ctx->http2 = 1; ctx->stream_id = stream_id; http2_parse_header(client, ctx, flags, buf + SW_HTTP2_FRAME_HEADER_SIZE, length); swConnection *conn = swWorker_get_connection(SwooleG.serv, fd); if (!conn) { sw_zval_ptr_dtor(&zdata); swWarn("connection[%d] is closed.", fd); return SW_ERR; } zval *zserver = ctx->request.zserver; sw_add_assoc_long_ex(zserver, ZEND_STRS("request_time"), serv->gs->now); // Add REQUEST_TIME_FLOAT double now_float = swoole_microtime(); sw_add_assoc_double_ex(zserver, ZEND_STRS("request_time_float"), now_float); add_assoc_long(zserver, "server_port", swConnection_get_port(&SwooleG.serv->connection_list[conn->from_fd])); add_assoc_long(zserver, "remote_port", swConnection_get_port(conn)); sw_add_assoc_string(zserver, "remote_addr", swConnection_get_ip(conn), 1); sw_add_assoc_string(zserver, "server_protocol", "HTTP/2", 1); sw_add_assoc_string(zserver, "server_software", SW_HTTP_SERVER_SOFTWARE, 1); if (flags & SW_HTTP2_FLAG_END_STREAM) { http2_onRequest(ctx, req->info.from_fd TSRMLS_CC); } else { if (!client->streams) { client->streams = swHashMap_new(SW_HTTP2_MAX_CONCURRENT_STREAMS, NULL); } swHashMap_add_int(client->streams, stream_id, ctx); } } else if (type == SW_HTTP2_TYPE_DATA) { ctx = swHashMap_find_int(client->streams, stream_id); if (!ctx) { sw_zval_ptr_dtor(&zdata); swoole_error_log(SW_LOG_WARNING, SW_ERROR_HTTP2_STREAM_NO_HEADER, "http2 error stream."); return SW_ERR; } swString *buffer = ctx->request.post_buffer; if (!buffer) { buffer = swString_new(SW_HTTP2_DATA_BUFFSER_SIZE); ctx->request.post_buffer = buffer; } swString_append_ptr(buffer, buf + SW_HTTP2_FRAME_HEADER_SIZE, length); if (flags & SW_HTTP2_FLAG_END_STREAM) { if (SwooleG.serv->http_parse_post && ctx->request.post_form_urlencoded) { zval *zpost; zval *zrequest_object = ctx->request.zobject; swoole_http_server_array_init(post, request); char *post_content = estrndup(buffer->str, buffer->length); sapi_module.treat_data(PARSE_STRING, post_content, zpost TSRMLS_CC); } else if (ctx->mt_parser != NULL) { multipart_parser *multipart_parser = ctx->mt_parser; size_t n = multipart_parser_execute(multipart_parser, buffer->str, buffer->length); if (n != length) { swoole_php_fatal_error(E_WARNING, "parse multipart body failed."); } } http2_onRequest(ctx, req->info.from_fd TSRMLS_CC); } client->remote_window_size -= length; if (length > 0 && client->remote_window_size < SW_HTTP2_MAX_WINDOW / 4) { char window_update_frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_WINDOW_UPDATE_SIZE]; uint32_t increment_size = SW_HTTP2_MAX_WINDOW - client->remote_window_size; window_update_frame[0 + SW_HTTP2_FRAME_HEADER_SIZE] = increment_size >> 24; window_update_frame[1 + SW_HTTP2_FRAME_HEADER_SIZE] = increment_size >> 16; window_update_frame[2 + SW_HTTP2_FRAME_HEADER_SIZE] = increment_size >> 8; window_update_frame[3 + SW_HTTP2_FRAME_HEADER_SIZE] = increment_size; swHttp2_set_frame_header(window_update_frame, SW_HTTP2_TYPE_WINDOW_UPDATE, SW_HTTP2_WINDOW_UPDATE_SIZE, 0, 0); swServer_tcp_send(SwooleG.serv, fd, window_update_frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_WINDOW_UPDATE_SIZE); client->remote_window_size = SW_HTTP2_MAX_WINDOW; } } else if (type == SW_HTTP2_TYPE_PING) { char ping_frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE]; swHttp2_set_frame_header(ping_frame, SW_HTTP2_TYPE_PING, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE, SW_HTTP2_FLAG_ACK, stream_id); memcpy(ping_frame + SW_HTTP2_FRAME_HEADER_SIZE, buf + SW_HTTP2_FRAME_HEADER_SIZE, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE); swServer_tcp_send(SwooleG.serv, fd, ping_frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE); } else if (type == SW_HTTP2_TYPE_WINDOW_UPDATE) { client->window_size += swHttp2_get_increment_size(buf); } sw_zval_ptr_dtor(&zdata); return SW_OK; } void swoole_http2_free(swoole_http_client *client) { if (client->inflater) { nghttp2_hd_inflate_del(client->inflater); client->inflater = NULL; } client->init = 0; client->remote_window_size = SW_HTTP2_DEFAULT_WINDOW; client->window_size = SW_HTTP2_DEFAULT_WINDOW; } #endif