HEX
Server: Apache/2.4.59 (Debian)
System: Linux keymana 4.19.0-21-cloud-amd64 #1 SMP Debian 4.19.249-2 (2022-06-30) x86_64
User: lijunjie (1003)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //home/lijunjie/swoole-cli/swoole-src-4.8.13/src/protocol/http.cc
/*
 +----------------------------------------------------------------------+
 | 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  <rango@swoole.com>                             |
 +----------------------------------------------------------------------+
 */

#include "swoole_http.h"
#include "swoole_server.h"

#include <string>
#include <sstream>

#include "swoole_util.h"
#include "swoole_http2.h"
#include "swoole_websocket.h"
#include "swoole_static_handler.h"

using std::string;
using swoole::http_server::Request;
using swoole::http_server::StaticHandler;
using swoole::network::SendfileTask;
using swoole::network::Socket;

// clang-format off
static const char *method_strings[] = {
    "DELETE", "GET", "HEAD", "POST", "PUT", "PATCH", "CONNECT", "OPTIONS", "TRACE", "COPY", "LOCK", "MKCOL", "MOVE",
    "PROPFIND", "PROPPATCH", "UNLOCK", "REPORT", "MKACTIVITY", "CHECKOUT", "MERGE", "M-SEARCH", "NOTIFY",
    "SUBSCRIBE", "UNSUBSCRIBE", "PURGE", "PRI",
};
// clang-format on

namespace swoole {

bool Server::select_static_handler(http_server::Request *request, Connection *conn) {
    const char *url = request->buffer_->str + request->url_offset_;
    size_t url_length = request->url_length_;

    StaticHandler handler(this, url, url_length);
    if (!handler.hit()) {
        return false;
    }

    char header_buffer[1024];
    SendData response;
    response.info.fd = conn->session_id;
    response.info.type = SW_SERVER_EVENT_SEND_DATA;

    if (handler.status_code == SW_HTTP_NOT_FOUND) {
        response.info.len = sw_snprintf(header_buffer,
                                        sizeof(header_buffer),
                                        "HTTP/1.1 %s\r\n"
                                        "Server: " SW_HTTP_SERVER_SOFTWARE "\r\n"
                                        "Content-Length: %zu\r\n"
                                        "\r\n%s",
                                        http_server::get_status_message(SW_HTTP_NOT_FOUND),
                                        sizeof(SW_HTTP_PAGE_404) - 1,
                                        SW_HTTP_PAGE_404);
        response.data = header_buffer;
        send_to_connection(&response);

        return true;
    }

    auto date_str = handler.get_date();
    auto date_str_last_modified = handler.get_date_last_modified();

    string date_if_modified_since = request->get_header("If-Modified-Since");
    if (!date_if_modified_since.empty() && handler.is_modified(date_if_modified_since)) {
        response.info.len = sw_snprintf(header_buffer,
                                        sizeof(header_buffer),
                                        "HTTP/1.1 304 Not Modified\r\n"
                                        "Connection: %s\r\n"
                                        "Date: %s\r\n"
                                        "Last-Modified: %s\r\n"
                                        "Server: %s\r\n\r\n",
                                        request->keep_alive ? "keep-alive" : "close",
                                        date_str.c_str(),
                                        date_str_last_modified.c_str(),
                                        SW_HTTP_SERVER_SOFTWARE);
        response.data = header_buffer;
        send_to_connection(&response);

        return true;
    }

    /**
     * if http_index_files is enabled, need to search the index file first.
     * if the index file is found, set filename to index filename.
     */
    if (!handler.hit_index_file()) {
        return false;
    }

    /**
     * the index file was not found in the current directory,
     * if http_autoindex is enabled, should show the list of files in the current directory.
     */
    if (!handler.has_index_file() && handler.is_enabled_auto_index() && handler.is_dir()) {
        sw_tg_buffer()->clear();
        size_t body_length = handler.make_index_page(sw_tg_buffer());

        response.info.len = sw_snprintf(header_buffer,
                                        sizeof(header_buffer),
                                        "HTTP/1.1 200 OK\r\n"
                                        "Connection: %s\r\n"
                                        "Content-Length: %ld\r\n"
                                        "Content-Type: text/html\r\n"
                                        "Date: %s\r\n"
                                        "Last-Modified: %s\r\n"
                                        "Server: %s\r\n\r\n",
                                        request->keep_alive ? "keep-alive" : "close",
                                        (long) body_length,
                                        date_str.c_str(),
                                        date_str_last_modified.c_str(),
                                        SW_HTTP_SERVER_SOFTWARE);
        response.data = header_buffer;
        send_to_connection(&response);

        response.info.len = body_length;
        response.data = sw_tg_buffer()->str;
        send_to_connection(&response);
        return true;
    }

    handler.parse_range(request->get_header("Range").c_str(), request->get_header("If-Range").c_str());
    auto tasks = handler.get_tasks();

    std::stringstream header_stream;
    if (1 == tasks.size()) {
        if (0 == tasks[0].offset && tasks[0].length == handler.get_filesize()) {
            header_stream << "Accept-Ranges: bytes\r\n";
        } else {
            header_stream << "Content-Range: bytes";
            if (tasks[0].length != handler.get_filesize()) {
                header_stream << " " << tasks[0].offset << "-" << (tasks[0].length + tasks[0].offset - 1) << "/"
                            << handler.get_filesize();
            }
            header_stream << "\r\n";
        }
    }

    response.info.len =
        sw_snprintf(header_buffer,
                    sizeof(header_buffer),
                    "HTTP/1.1 %s\r\n"
                    "Connection: %s\r\n"
                    "Content-Length: %ld\r\n"
                    "Content-Type: %s\r\n"
                    "%s"
                    "Date: %s\r\n"
                    "Last-Modified: %s\r\n"
                    "Server: %s\r\n\r\n",
                    http_server::get_status_message(handler.status_code),
                    request->keep_alive ? "keep-alive" : "close",
                    SW_HTTP_HEAD == request->method ? 0 : handler.get_content_length(),
                    SW_HTTP_HEAD == request->method ? handler.get_mimetype() : handler.get_content_type(),
                    header_stream.str().c_str(),
                    date_str.c_str(),
                    date_str_last_modified.c_str(),
                    SW_HTTP_SERVER_SOFTWARE);

    response.data = header_buffer;

    // Use tcp_nopush to improve sending efficiency
    conn->socket->cork();

    // Send HTTP header
    send_to_connection(&response);

    // Send HTTP body
    if (SW_HTTP_HEAD != request->method) {
        if (!tasks.empty()) {
            size_t task_size = sizeof(network::SendfileTask) + strlen(handler.get_filename()) + 1;
            network::SendfileTask *task = (network::SendfileTask *) sw_malloc(task_size);
            strcpy(task->filename, handler.get_filename());
            if (tasks.size() > 1) {
                for (auto i = tasks.begin(); i != tasks.end(); i++) {
                    response.info.type = SW_SERVER_EVENT_SEND_DATA;
                    response.info.len = strlen(i->part_header);
                    response.data = i->part_header;
                    send_to_connection(&response);

                    task->offset = i->offset;
                    task->length = i->length;
                    response.info.type = SW_SERVER_EVENT_SEND_FILE;
                    response.info.len = task_size;
                    response.data = (char *) task;
                    send_to_connection(&response);
                }

                response.info.type = SW_SERVER_EVENT_SEND_DATA;
                response.info.len = strlen(handler.get_end_part());
                response.data = handler.get_end_part();
                send_to_connection(&response);
            } else if (tasks[0].length > 0) {
                task->offset = tasks[0].offset;
                task->length = tasks[0].length;
                response.info.type = SW_SERVER_EVENT_SEND_FILE;
                response.info.len = task_size;
                response.data = (char *) task;
                send_to_connection(&response);
            }
            sw_free(task);
        }
    }

    // Close the connection if keepalive is not used
    if (!request->keep_alive) {
        response.info.type = SW_SERVER_EVENT_CLOSE;
        response.info.len = 0;
        response.data = nullptr;
        send_to_connection(&response);
    }

    return true;
}

void Server::destroy_http_request(Connection *conn) {
    auto request = reinterpret_cast<swoole::http_server::Request *>(conn->object);
    if (!request) {
        return;
    }
    delete request;
    conn->object = nullptr;
}

namespace http_server {
//-----------------------------------------------------------------

const char *get_status_message(int code) {
    switch (code) {
    case 100:
        return "100 Continue";
    case 101:
        return "101 Switching Protocols";
    case 201:
        return "201 Created";
    case 202:
        return "202 Accepted";
    case 203:
        return "203 Non-Authoritative Information";
    case 204:
        return "204 No Content";
    case 205:
        return "205 Reset Content";
    case 206:
        return "206 Partial Content";
    case 207:
        return "207 Multi-Status";
    case 208:
        return "208 Already Reported";
    case 226:
        return "226 IM Used";
    case 300:
        return "300 Multiple Choices";
    case 301:
        return "301 Moved Permanently";
    case 302:
        return "302 Found";
    case 303:
        return "303 See Other";
    case 304:
        return "304 Not Modified";
    case 305:
        return "305 Use Proxy";
    case 307:
        return "307 Temporary Redirect";
    case 400:
        return "400 Bad Request";
    case 401:
        return "401 Unauthorized";
    case 402:
        return "402 Payment Required";
    case 403:
        return "403 Forbidden";
    case 404:
        return "404 Not Found";
    case 405:
        return "405 Method Not Allowed";
    case 406:
        return "406 Not Acceptable";
    case 407:
        return "407 Proxy Authentication Required";
    case 408:
        return "408 Request Timeout";
    case 409:
        return "409 Conflict";
    case 410:
        return "410 Gone";
    case 411:
        return "411 Length Required";
    case 412:
        return "412 Precondition Failed";
    case 413:
        return "413 Request Entity Too Large";
    case 414:
        return "414 Request URI Too Long";
    case 415:
        return "415 Unsupported Media Type";
    case 416:
        return "416 Requested Range Not Satisfiable";
    case 417:
        return "417 Expectation Failed";
    case 418:
        return "418 I'm a teapot";
    case 421:
        return "421 Misdirected Request";
    case 422:
        return "422 Unprocessable Entity";
    case 423:
        return "423 Locked";
    case 424:
        return "424 Failed Dependency";
    case 426:
        return "426 Upgrade Required";
    case 428:
        return "428 Precondition Required";
    case 429:
        return "429 Too Many Requests";
    case 431:
        return "431 Request Header Fields Too Large";
    case 500:
        return "500 Internal Server Error";
    case 501:
        return "501 Method Not Implemented";
    case 502:
        return "502 Bad Gateway";
    case 503:
        return "503 Service Unavailable";
    case 504:
        return "504 Gateway Timeout";
    case 505:
        return "505 HTTP Version Not Supported";
    case 506:
        return "506 Variant Also Negotiates";
    case 507:
        return "507 Insufficient Storage";
    case 508:
        return "508 Loop Detected";
    case 510:
        return "510 Not Extended";
    case 511:
        return "511 Network Authentication Required";
    case 200:
    default:
        return "200 OK";
    }
}

static int url_htoi(char *s) {
    int value;
    int c;

    c = ((unsigned char *) s)[0];
    if (isupper(c)) {
        c = tolower(c);
    }
    value = (c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16;

    c = ((unsigned char *) s)[1];
    if (isupper(c)) {
        c = tolower(c);
    }
    value += c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10;

    return (value);
}

/* return value: length of decoded string */
size_t url_decode(char *str, size_t len) {
    char *dest = str;
    char *data = str;

    while (len--) {
        if (*data == '+') {
            *dest = ' ';
        } else if (*data == '%' && len >= 2 && isxdigit((int) *(data + 1)) && isxdigit((int) *(data + 2))) {
            *dest = (char) url_htoi(data + 1);
            data += 2;
            len -= 2;
        } else {
            *dest = *data;
        }
        data++;
        dest++;
    }
    *dest = '\0';

    return dest - str;
}

char *url_encode(char const *str, size_t len) {
    static uchar hexchars[] = "0123456789ABCDEF";

    size_t x, y;
    char *ret = (char *) sw_malloc(len * 3);

    for (x = 0, y = 0; len--; x++, y++) {
        char c = str[x];

        ret[y] = c;
        if ((c < '0' && c != '-' && c != '.') || (c < 'A' && c > '9') || (c > 'Z' && c < 'a' && c != '_') ||
            (c > 'z' && c != '~')) {
            ret[y++] = '%';
            ret[y++] = hexchars[(unsigned char) c >> 4];
            ret[y] = hexchars[(unsigned char) c & 15];
        }
    }
    ret[y] = '\0';

    do {
        size_t size = y + 1;
        char *tmp = (char *) sw_malloc(size);
        memcpy(tmp, ret, size);
        sw_free(ret);
        ret = tmp;
    } while (0);

    return ret;
}

/**
 * only GET/POST
 */
int Request::get_protocol() {
    char *p = buffer_->str;
    char *pe = p + buffer_->length;

    if (buffer_->length < (sizeof("GET / HTTP/1.x\r\n") - 1)) {
        return SW_ERR;
    }

    // http method
    if (memcmp(p, SW_STRL("GET")) == 0) {
        method = SW_HTTP_GET;
        p += 3;
    } else if (memcmp(p, SW_STRL("POST")) == 0) {
        method = SW_HTTP_POST;
        p += 4;
    } else if (memcmp(p, SW_STRL("PUT")) == 0) {
        method = SW_HTTP_PUT;
        p += 3;
    } else if (memcmp(p, SW_STRL("PATCH")) == 0) {
        method = SW_HTTP_PATCH;
        p += 5;
    } else if (memcmp(p, SW_STRL("DELETE")) == 0) {
        method = SW_HTTP_DELETE;
        p += 6;
    } else if (memcmp(p, SW_STRL("HEAD")) == 0) {
        method = SW_HTTP_HEAD;
        p += 4;
    } else if (memcmp(p, SW_STRL("OPTIONS")) == 0) {
        method = SW_HTTP_OPTIONS;
        p += 7;
    } else if (memcmp(p, SW_STRL("COPY")) == 0) {
        method = SW_HTTP_COPY;
        p += 4;
    } else if (memcmp(p, SW_STRL("LOCK")) == 0) {
        method = SW_HTTP_LOCK;
        p += 4;
    } else if (memcmp(p, SW_STRL("MKCOL")) == 0) {
        method = SW_HTTP_MKCOL;
        p += 5;
    } else if (memcmp(p, SW_STRL("MOVE")) == 0) {
        method = SW_HTTP_MOVE;
        p += 4;
    } else if (memcmp(p, SW_STRL("PROPFIND")) == 0) {
        method = SW_HTTP_PROPFIND;
        p += 8;
    } else if (memcmp(p, SW_STRL("PROPPATCH")) == 0) {
        method = SW_HTTP_PROPPATCH;
        p += 9;
    } else if (memcmp(p, SW_STRL("UNLOCK")) == 0) {
        method = SW_HTTP_UNLOCK;
        p += 6;
    } else if (memcmp(p, SW_STRL("REPORT")) == 0) {
        method = SW_HTTP_REPORT;
        p += 6;
    } else if (memcmp(p, SW_STRL("PURGE")) == 0) {
        method = SW_HTTP_PURGE;
        p += 5;
    }
#ifdef SW_USE_HTTP2
    // HTTP2 Connection Preface
    else if (memcmp(p, SW_STRL("PRI")) == 0) {
        method = SW_HTTP_PRI;
        if (buffer_->length >= (sizeof(SW_HTTP2_PRI_STRING) - 1) && memcmp(p, SW_STRL(SW_HTTP2_PRI_STRING)) == 0) {
            buffer_->offset = sizeof(SW_HTTP2_PRI_STRING) - 1;
            return SW_OK;
        } else {
            goto _excepted;
        }
    }
#endif
    else {
    _excepted:
        excepted = 1;
        return SW_ERR;
    }

    // http version
    char state = 0;
    for (; p < pe; p++) {
        switch (state) {
        case 0:
            if (isspace(*p)) {
                continue;
            }
            state = 1;
            url_offset_ = p - buffer_->str;
            break;
        case 1:
            if (isspace(*p)) {
                state = 2;
                url_length_ = p - buffer_->str - url_offset_;
                continue;
            }
            break;
        case 2:
            if (isspace(*p)) {
                continue;
            }
            if ((size_t)(pe - p) < (sizeof("HTTP/1.x") - 1)) {
                return SW_ERR;
            }
            if (memcmp(p, SW_STRL("HTTP/1.1")) == 0) {
                version = SW_HTTP_VERSION_11;
                goto _end;
            } else if (memcmp(p, SW_STRL("HTTP/1.0")) == 0) {
                version = SW_HTTP_VERSION_10;
                goto _end;
            } else {
                goto _excepted;
            }
        default:
            break;
        }
    }
_end:
    p += sizeof("HTTP/1.x") - 1;
    request_line_length_ = buffer_->offset = p - buffer_->str;
    return SW_OK;
}

/**
 * simple get headers info
 */
void Request::parse_header_info() {
    // header field start
    char *p = buffer_->str + request_line_length_ + (sizeof("\r\n") - 1);
    // point-end: start + strlen(all-header) without strlen("\r\n\r\n")
    char *pe = buffer_->str + header_length_ - (sizeof("\r\n\r\n") - 1);

    for (; p < pe; p++) {
        if (*(p - 1) == '\n' && *(p - 2) == '\r') {
            if (SW_STRCASECT(p, pe - p, "Content-Length:")) {
                // strlen("Content-Length:")
                p += (sizeof("Content-Length:") - 1);
                // skip spaces
                while (*p == ' ') {
                    p++;
                }
                content_length_ = strtoull(p, nullptr, 10);
                known_length = 1;
            } else if (SW_STRCASECT(p, pe - p, "Connection:")) {
                // strlen("Connection:")
                p += (sizeof("Connection:") - 1);
                // skip spaces
                while (*p == ' ') {
                    p++;
                }
                if (SW_STRCASECT(p, pe - p, "keep-alive")) {
                    keep_alive = 1;
                }
            } else if (SW_STRCASECT(p, pe - p, "Transfer-Encoding:")) {
                // strlen("Transfer-Encoding:")
                p += (sizeof("Transfer-Encoding:") - 1);
                // skip spaces
                while (*p == ' ') {
                    p++;
                }
                if (SW_STRCASECT(p, pe - p, "chunked")) {
                    chunked = 1;
                }
            }
        }
    }

    header_parsed = 1;
    if (chunked && known_length && content_length_ == 0) {
        nobody_chunked = 1;
    }
}

#ifdef SW_HTTP_100_CONTINUE
bool Request::has_expect_header() {
    // char *buf = buffer->str + buffer->offset;
    char *buf = buffer_->str;
    // int len = buffer->length - buffer->offset;
    size_t len = buffer_->length;

    char *pe = buf + len;
    char *p;

    for (p = buf; p < pe; p++) {
        if (*p == '\r' && (size_t)(pe - p) > sizeof("\r\nExpect")) {
            p += 2;
            if (SW_STRCASECT(p, pe - p, "Expect: ")) {
                p += sizeof("Expect: ") - 1;
                if (SW_STRCASECT(p, pe - p, "100-continue")) {
                    return true;
                } else {
                    return false;
                }
            } else {
                p++;
            }
        }
    }
    return false;
}
#endif

int Request::get_header_length() {
    char *p = buffer_->str + buffer_->offset;
    char *pe = buffer_->str + buffer_->length;

    for (; p <= pe - (sizeof("\r\n\r\n") - 1); p++) {
        if (memcmp(p, SW_STRL("\r\n\r\n")) == 0) {
            // strlen(header) + strlen("\r\n\r\n")
            header_length_ = buffer_->offset = p - buffer_->str + (sizeof("\r\n\r\n") - 1);
            return SW_OK;
        }
    }

    buffer_->offset = p - buffer_->str;
    return SW_ERR;
}

int Request::get_chunked_body_length() {
    char *p = buffer_->str + buffer_->offset;
    char *pe = buffer_->str + buffer_->length;

    while (1) {
        if ((size_t)(pe - p) < (1 + (sizeof("\r\n") - 1))) {
            /* need the next chunk */
            return SW_ERR;
        }
        char *head = p;
        size_t n_parsed;
        size_t chunk_length = swoole_hex2dec(head, &n_parsed);
        head += n_parsed;
        if (*head != '\r') {
            excepted = 1;
            return SW_ERR;
        }
        p = head + (sizeof("\r\n") - 1) + chunk_length + (sizeof("\r\n") - 1);
        /* used to check package_max_length */
        content_length_ = p - (buffer_->str + header_length_);
        if (p > pe) {
            /* need recv chunk body again */
            return SW_ERR;
        }
        buffer_->offset = p - buffer_->str;
        if (chunk_length == 0) {
            break;
        }
    }
    known_length = 1;

    return SW_OK;
}

std::string Request::get_header(const char *name) {
    size_t name_len = strlen(name);
    char *p = buffer_->str + url_offset_ + url_length_ + 10;
    char *pe = buffer_->str + header_length_;

    char *buffer = nullptr;
    char *colon = nullptr;

    int state = 0;
    int i = 0;

    bool is_error_header_name = false;

    for (; p < pe; p++) {
        switch (state) {
        case 0:
            if (SW_STRCASECT(p, pe - p, "\r\n")) {
                i = 0;
                is_error_header_name = false;
                break;
            }

            if (!is_error_header_name && swoole_strcasect(p, pe - p, name, name_len)) {
                colon = p + name_len;
                if (colon[0] != ':' || i > 1) {
                    is_error_header_name = true;
                    break;
                }

                p += name_len;
                state = 1;
            }

            i++;
            break;
        case 1:
            if (!isspace(*p)) {
                buffer = p;
                state = 2;
            }
            break;
        case 2:
            if (SW_STRCASECT(p, pe - p, "\r\n")) {
                return string(buffer, p - buffer);
            }
            break;
        default:
            break;
        }
    }

    return string();
}

int get_method(const char *method_str, size_t method_len) {
    int i = 0;
    for (; i < SW_HTTP_PRI; i++) {
        if (swoole_strcaseeq(method_strings[i], strlen(method_strings[i]), method_str, method_len)) {
            return i + 1;
        }
    }
    return -1;
}

const char *get_method_string(int method) {
    if (method < 0 || method > SW_HTTP_PRI) {
        return nullptr;
    }
    return method_strings[method - 1];
}

//-----------------------------------------------------------------

#ifdef SW_USE_HTTP2

static void protocol_status_error(Socket *socket, Connection *conn) {
    swoole_error_log(SW_LOG_WARNING,
                     SW_ERROR_PROTOCOL_ERROR,
                     "unexpected protocol status of session#%ld<%s:%d>",
                     conn->session_id,
                     conn->info.get_ip(),
                     conn->info.get_port());
}

ssize_t get_package_length(const Protocol *protocol, Socket *socket, PacketLength *pl) {
    Connection *conn = (Connection *) socket->object;
    if (conn->websocket_status >= websocket::STATUS_HANDSHAKE) {
        return websocket::get_package_length(protocol, socket, pl);
    } else if (conn->http2_stream) {
        return http2::get_frame_length(protocol, socket, pl);
    } else {
        protocol_status_error(socket, conn);
        return SW_ERR;
    }
}

uint8_t get_package_length_size(Socket *socket) {
    Connection *conn = (Connection *) socket->object;
    if (conn->websocket_status >= websocket::STATUS_HANDSHAKE) {
        return SW_WEBSOCKET_MESSAGE_HEADER_SIZE;
    } else if (conn->http2_stream) {
        return SW_HTTP2_FRAME_HEADER_SIZE;
    } else {
        protocol_status_error(socket, conn);
        return 0;
    }
}

int dispatch_frame(const Protocol *proto, Socket *socket, const RecvData *rdata) {
    Connection *conn = (Connection *) socket->object;
    if (conn->websocket_status >= websocket::STATUS_HANDSHAKE) {
        return websocket::dispatch_frame(proto, socket, rdata);
    } else if (conn->http2_stream) {
        return Server::dispatch_task(proto, socket, rdata);
    } else {
        protocol_status_error(socket, conn);
        return SW_ERR;
    }
}
#endif
}  // namespace http_server
}  // namespace swoole